design

A new way to think about software design

This year’s Saturn Conference at San Diego reflected an evolving landscape as macro trends such as cloud based architectures, Internet of Things (IoT), and devOps in an Agile world, continue to reshape the industry. How do we think about design and architecture in this changing landscape?

Professor Daniel Jackson of MIT, in a keynote at the Saturn Conference, gave us a fresh look on how to think about design. The idea is simple and elegant and one wonders why it took so long for somebody to come up with it. Simply put, Professor Jackson describes an application as a collection of coherent concepts that fulfill the purposes of the application. The beauty of this formulation is that it eliminates the clutter of implementation artifacts.


When we describe the design of a program in UML, we struggle to create structural and behavioral diagrams that accurately reflect program implementation. Sadly (and, perhaps, mercifully) we rarely succeed in this endeavor and even if we did, those diagrams would likely be just as hard to understand as the code (think of creating interaction diagrams to represent various method call chains). And if our implementation language happens to be a non-object oriented language then we are plain out of luck. On the other hand, this new kind of thinking has the potential to transcend implementation language and, perhaps, even technology. It also has ramifications on the architect vs developer debates that rage in the world of software engineering today.

Conceptual Design vs Representational Design: Reducing the clutter

Professor Jackson provided several examples of applications and the concepts they embody. For instance, an email application embodies concepts such as Email Address, Message and Folder while a word processor embodies concepts such as Paragraph, Format and Style. A considerable part of the presentation delved into the details that illustrated the sophistication that underlies these concepts and the confusion that befalls when these concepts are poorly defined.

So, how do we select concepts? Professor Jackson defines purposes that a concept fulfills. In a clean design, he said, a concept fulfills a single purpose. This has ramifications that I have yet to fully get my head around. It reminds me of the Single Responsibility Principle which is also a difficult concept to understand. In any case, I suspect that defining a coherent set of concepts is difficult and takes repeated iterations of implementations to get it right. In fact, the user of that software is likely to be a critical part of the process as concepts are pruned, split up or even eliminated to make them coherent and understandable.

And, how do we implement concepts? Does a concept map to a single class or multiple classes if implemented in an object oriented language? I will eagerly wait to see further work on this approach.

Go look up the slides of this thought provoking presentation here: Rethinking Software Design.

Analyzing ArgoUML

Johan van den Muijsenberg just published (in Dutch) his analysis of ArgoUML in a magazine published by the Java User Group in Netherlands. The brilliance of Johan's analysis is in how logically straightforward it is and how that analysis yields clearly identifiable architectural issues and fixes. It is yet another example of how easily architecture erodes from its intended design. If more teams were to focus on fixing "bugs" in architecture, they would reap rich dividends in improved quality and productivity.

My main complaint is why Dutch readers should be the only ones to benefit from this interesting and useful article. Here is a Google translation into English.

Modularity as a Portfolio of Options

I have been exploring the use of financial analogies with regard to programming and design. Ward Cunningham's Technical Debt metaphor has become well known. Prior to writing this blog entry, I looked a little deeper into Ward's metaphor and discovered that it has been interpreted and extended in multiple ways. Since this is my view of the different interpretations, I recommend that you go to the actual source to arrive at your own conclusion.

First let's examine how Ward used it originally. He used the metaphor to explain the need for going back and improving the code to incorporate what you learn after an actual implementation. I believe that there are a few good reasons for this suggestion:

  • We often don't know the best way of implementing something until we actually implement it.
  • The customers learn what they really want only after they have seen an implementation.

So for Ward, technical debt is a good thing, perhaps, even a necessary thing. An implementation, even a suboptimal one, allows you to learn what to implement and how to implement it. This is pretty much the original raison-d’être for Agile.

Uncle Bob applied the metaphor for an example of an implementation that is known to be sub-optimal in the long term but allows you to get things done in the short term. While Ward considers the debt as necessary for understanding the selection and design of the right system, Bob used the metaphor for a worthwhile engineering trade-off. Steve McConnell and Martin Fowler would also include poor coding or “quick and dirty” implementations as technical debt.

If the value of the metaphor is for explaining to others why improperly conceived code will extract a price during future development, just as a debt extracts an interest in future years, then I think that the metaphor works for all these different situations.

But now on to what this article is all about. It is about another metaphor - a metaphor that also goes quite far, I might add. This metaphor comes from Carliss Baldwin and Kim Clark, from their book, Design Rules: The Power of Modularity. It too deals with how the current design of a system impacts the cost and the value that can be realized from it in future.

According to them, a design yields a portfolio of options. For a modular system, the options are the opportunities to improve the design in a modular or piece meal fashion. A monolithic design, by contrast, gives you a single option. You have to redesign the entire system to improve it.

Baldwin and Clark point to a well known theorem in finance - it is more valuable to hold a basket of options for many different securities than it is to hold a single option on the entire portfolio. So it is with system design. A monolithic design gives you a single design “option” to improve the system while a modular system gives you multiple “options” to improve the modules of the system.

Consider the example of a PC. It is a highly modular. When I bought my laptop, it had an 80 GB disk. Later, I went out and bought a 300 GB disk and all I had to do was swap my disk with the new one. In the intervening period since I bought my laptop, the disk manufacturers were able to improve the disk design so that the same form factor gave me a bigger and faster disk. I was similarly able to upgrade my laptop with additional memory. Each of these modularity “options” represents an axis for further improvement that does not force the redesign of the entire system. The ease of improving the design of a modular system confers dramatic benefits to the customers of that system. This is why modular designs are so valuable.

Of course, it is important to keep in mind that the value of modularity exists only in the context of requirements. Because we need larger disks to store ever increasing amounts of data, it becomes valuable to improve the density of disks. In other words the “option” is only valuable because the value of the underlying “security” could increase. Just because you create a module it doesn’t mean that you have suddenly added value – it must help meet requirements that somebody actually needs or wants.

Modularity Parable and Software

In his seminal book, The Sciences of the Artificial, Herb Simon describes the parable of watchmakers named Hora and Tempus. They built watches out of 1000 parts. The watches were of the highest quality – as a result, they were often interrupted by customers calling up to place orders. However, they built watches using different techniques. Tempus created watches by putting all 1000 parts together in a monolithic fashion while Hora created it out of components which were assembled from the parts. Each of Hora’s watches was assembled with 10 components, each created out of 10 subcomponents, which, in turn, were assembled from 10 parts each. Whenever Tempus was interrupted by a call, he was forced to put down his work and had to start all over again. On the other hand, when Hora was interrupted he was forced to put down a subcomponent and had to re-assemble only that subcomponent. With a probability of interruption of 1%, Simon calculated that Hora would produce 4000 times more watches than Tempus. Hora’s modular assembly gave him an overwhelming advantage over Tempus.

Read Neeraj Sangal's latest blog on software architecture design

But how does this parable apply to programmers? While phone calls, instant messages, and even hallway conversation might be disruptive, they do not force rework. This parable is about interruptions that force rework. Programs change or evolve, mostly because there are requirements for new capabilities. In fact, most programs are written against a backdrop of a long list of features that is itself changing. The interruptions are new requirements that will require rework of parts that have been already been implemented.

For Hora the watchmaker, an interruption required the re-assembly of the component that he was working on. Other components and assemblies weren’t really affected. For Hora the programmer, things aren’t all that simple. If supporting one requirement affects the entire program then like Tempus, his team will spend all its time reworking what was already complete. On the other hand, to the extent that things could be arranged so that requirements affect a small part of the program, it is conceivable that the team could even service multiple requirements simultaneously.

Perhaps, Hora could split his software by dividing it into different logical grouping arranged by packages, by name spaces, by file and directories, by schema etc. But does this really guarantee that the impact of a new requirement will be limited? Alas, requirements are rooted in the real world and there is nothing that can ever give that ironclad guarantee. If this problem cannot be overcome in the absolute sense, is there something we can do to ameliorate it? What we do to ameliorate this problem is what modularity is all about. The modularity of a program is the ability to limit the scope of change. To understand modularity, it is worth looking into what Parnas called information hiding.

Information Hiding

Contrary to what some might think, “information hiding” has nothing to do with corporate management. Nor does it have anything to do with “open source” or “closed source” software. However, it does have a profound bearing on abstractions, that helps realize information hiding. One benefit of an abstraction is that the consumers of the abstraction don’t care about the implementation details of that abstraction – those details are “hidden” within the abstraction. To the extent that changes for a new requirement affect only the implementation details, the rest of the system isn’t affected.

To illustrate his reasoning, Parnas used a program which reads a collection of input lines into a storage system, generates multiple new lines for each input line using word shifts, and then outputs a sorted list of those new lines. By abstracting the details of the storage system into a module, Parnas showed how its implementation details could be hidden thereby making it easier to change and to maintain. As developers, we naturally group related programming elements. Object oriented programmers define classes and name spaces. Data architects use schemas to group related tables, stored procedures and other elements. Methods, files, directories, classes, schemas are all examples of abstractions.

If methods and classes represent abstraction wouldn’t every change affect an abstraction? Isn’t that what we are trying to avoid? What matters is the scope of the affected abstraction. If you change the body of a method while keeping its signature the same, you haven’t really affected any of the callers of that method. If you change a private method in a class, you don’t affect the external usage of the class. Software is a hierarchy of abstractions: just as a class contains methods, there are packages and name spaces that contain classes; and, packages and names spaces are themselves hierarchical. Generally, the smaller the scope of the abstractions affected by a requirement, the easier it is to support that requirement; and, if different requirements affect disjoint scopes, then team efficiency improves significantly.

Conclusion

I have learned that good modularity almost always maps to abstractions that are readily understandable. Just because you split up your program into modules, doesn’t mean that the benefit of modularity will accrue immediately. Indeed, the value of modularity should be judged primarily in the context of fulfilling new requirements.

Making systems modular requires experience and hard work. When systems are truly modular, the results are magical – it’s a pleasure to work on the system and productivity improves by leaps and bounds.