About change and avoidance

Geometry and diagrams are incredibly helpful in building and architecture. Pictures show form, and we have an innate ability to see whether a design looks right from at least one perspective. This is not just because we have seen so many examples before but because we recognise good shapes inherently. The biological sense of beauty.

However, diagrams of the real world are only helpful because they simulate the world we inhabit. The diagrams of code don’t provide the same level of recognisable goodness of fit because they simulate an abstract world.

A diagram of the code’s geometry must represent the code’s goodness. Diagrams can give us a sense of awareness of failure points and unhealthy situations, but most don’t show us the relevance of the parts in any appreciable way. One way would be to diagram modules or classes and colour their API so you can see where they are used. Another would be to draw every usage as a line and look for messes or places that offer clean cuts.

Interaction lines

With this diagram, you can see the interactions between the elements and where aspects of the API currently overlap. Where there is overlap, we see complexity. Where there is not, there is the opportunity for a further split. We can see where components can be easily cleaved from the collection and those that rely on almost everything.

But this only captures the system as it is. This diagram, even if developed further, does not show how a system changes. Therefore, it only reveals the state before or after a step, not the complete dynamic picture.

Diagramming code

The interpreter pattern is complicated, and the lack of diagrams showing the true intent of it makes it hard to digest on even a third reading. But there are also good examples in the GoF book[GoF94]. If you look at the composite pattern’s object diagram, you can clearly see the intention to allow the creation of hierarchical structures. The UML1 diagram showing the recursive link is there too, but it does not clarify the intent.

UML diagrams were good, but trying to fit all programming paradigms into them is a fool’s errand. They were a natural choice for the GoF book, but people outside of object-oriented design don’t understand them, and they add friction. They also imply object modelling, which adds to the concern of all famous design patterns being object-oriented. From this evidence, many infer that object-oriented design is the only way to design software.

The UML diagrams most often seen are class diagrams, but most developers need visualised usage and instances. Seeing how things are meant to look when realised can allow for greater insights. One diagram shows how things will be at runtime, the other only shows a picture mirroring the code. In my opinion, a diagram should only show what you cannot represent more clearly in some other way.

If you have a copy of the book, look again at the presentation of the Composite. The first diagram is a UML class diagram. In it, we see the code relationships. This might be useful for developers wanting to understand how to implement the solution, but it’s not so good at exhorting the value of the pattern. It’s a diagram of the final code.

The second diagram, however, is a UML object diagram. There are duplicated elements because, in the working case, there will be multiple objects of the same class. This diagram helps us understand what will be happening at runtime. This diagram imparts the value of the pattern even if it does not show how the pattern can be implemented. It also immediately presents a counterexample to one misinterpretation. We have a concrete example showing it’s okay for a Picture object to contain another Picture object, whereas the class diagram only implicitly allows it. In other words, this type of diagram shows something that would otherwise be ambiguous.

Note the difference between the way the diagrams map neatly to the world of the developer and the world of the user. One is the state of the source code, the other is the state of the running program.

I was delighted to see that Pattern-Oriented Software Architecture Volume 5[POSA5-07] omitted UML diagrams and even mentioned how they might not have been the best choice when discussing how to define design patterns. Because even an object diagram is an image of a static final form, it suffers the same problems as the diagrams from A Pattern Language. But also, the UML does not benefit from the centuries of examples and counterexamples we find in our lived environment. If the GoF book were to be better understood, they would have needed to fill in for the deficient history with many counterexamples for their patterns.

Draw the rest of the owl

Diagrams for code patterns must be two things. First, they need to be examples of the right thing. Second, they have to be concrete examples of the process being applied to a context. Further diagrams must show where and why the pattern should not be used. We will need a lot of examples for this if we take each aspect in turn. But it’s worth thinking again about how we traditionally transfer wisdom between people and between generations. We pass on our knowledge through stories.

Anecdotes stick. The mystery of the moment and the excitement of learning the solution to the sticky situation is always a thrill. For code, the roller-coaster ride of problem and error to technical debt to the final sweet and robust solution should be no less enticing to the invested developer. But we need more woe. More setbacks. More failures to learn from. Stories of heroes always succeeding are boring. There must be a challenge.

Telling stories about patterns will work. Stories are always about change and adaptation to newly gained boons such as knowledge or power. Design patterns are definitely stories about freshly acquired knowledge, but they need the middle act where the antagonist throws their army of ‘gotchas’ at the developer, trying to wear them down with new situations they need to handle. We need conflict in the pattern story.

The existing design patterns are not stories. There is no conflict, and we haven’t structured them as unfolding sequences. They are more like news items than tales with meaning.

Sometimes the thing we want to show has a visual element, such as in UI patterns. In those cases, imagery and geometry work amazingly well. But we should keep the story aspect. We even have a pre-packaged solution for telling stories in image form. We can make comics for UX patterns. We might not be able to make a comic for the Strategy pattern, but we can for the mile-wide-button UX pattern. Choose the correct type of diagram for your domain. For code, we need structure diagrams and before and after code snippets.

Pattern diagrams currently show the final form, not even the process in many places, but we need the process to understand how codebases must change. A codebase is not built in one shot, so any technique or pattern should show the process of introducing the element as a change in a context. We need to show our intentional avoidance of certain forms through negative examples. When teaching wisdom, we must show what is good, but the bad is much more compelling. Pitfalls will always be more valuable than tricks, and ‘gotchas’ are more useful than advice on what and how to perform.

1

The unified modelling language is an attempt to create a visual language with which to model object-oriented code structure and dynamics. It was used throughout the GoF book to present the structures of design patterns.