Visualizing Code

When writing software we’re working at two levels:

  1. Creating an executable specification of exactly what we want the machine to do
  2. Creating a living document that describes the intent of what we want the machine to do, to be read by humans

The first part is the easy part, the second part takes a lifetime to master. I read a really great post today pointing out signs that you’re a bad programmer. Whether you’re a bad programmer or just inexperienced, I think the biggest barrier is being able to quickly and accurately visualize code. You need to visualize what the application actually does, what happens at runtime; but all your IDE shows you is the raw, static, source code. From this static view of the world you have to infer the runtime behaviour, you have to infer the actual shape of the application and the patterns of interaction; while closely related, the two are separate. Given just source code, you need to be able to visualize what code does.

What does it mean to visualize code? At the simplest level, it’s understanding what individual statements do.

string a = "Hello":
string b = "world";
a = b;

It might sound trivial, but the first necessary step is being able to quickly parse code and mentally step through what will happen. First for basic statements, then for code that iterates:

while (stack.Count() > 1)
    var operation = stack.Pop() as IOperation;
    var result = operation.Execute(stack.Pop(), stack.Pop());

Where you need to understand looping mechanics and mentally model what happens overall not just each iteration. Then you need to be able to parse recursive code:

int Depth(ITreeNode node)
    if (node == null)
        return 0;
    return 1 + Math.Max(Depth(node.Left), Depth(node.Right));

Which is generally harder for inexperienced programmers to understand and reason about; even though once you’ve learned the pattern it can be clearer and more concise.

Once you’ve mastered how to understand what a single method does, you have to understand how methods become composed together. In the OO world, this means understanding classes and interfaces; it means understanding design patterns; you need to understand how code is grouped together into cohesive, loosely coupled units with clear responsibilities.

For example, the visitor pattern has a certain mental structure – it’s about implementing something akin to a virtual method outside of the class hierarchy; in my mind it factors a set of classes into a set of methods.

public interface IAnimal
    void Accept(IAnimalVisitor visitor);

public class Dog : IAnimal { ... }
public class Cat : IAnimal { ... }
public class Fox : IAnimal { ... }

public interface IAnimalVisitor
    void VisitDog(Dog dog);
    void VisitCat(Cat cat);
    void VisitFox(Fox fox);

The first step in reading code is being able to read something like a visitor pattern (assuming you’d never heard of it before) and understand what it does and develop a mental model of what that shape looks like. Then, you can use the term “visitor” in your code and in discussions with colleagues. This shared language is critical when talking about code: it’s not practical to design a system by looking at individual lines of code, we need to be able to draw boxes on a whiteboard and discuss shapes and patterns. This shared mental model is a key part of team design work; we need a shared language that maps to a shared mental model, both of the existing system and of changes we’d like to make.

In large systems this is where a common language is important: if the implementation uses the same terms the domain uses, it becomes simpler to understand how the parts of the system interact. By giving things proper names, the interactions between classes become logical analogues of real-world things – we don’t need to use technical words or made up words that subsequent readers will need to work to understand or learn, someone familiar with the domain will know what the expected interactions are. This makes it easier to build a mental model of the system.

For example, in an online book store, I might have concepts (classes) such as book, customer, shopping basket, postal address. These are all logical real world things with obvious interactions. If I instead named them PurchasableItem, RegisteredUser, OrderItemList and DeliveryIndicationStringList you’d probably struggle to understand how one related to the other. Naming things is hard, but incredibly important – poor naming will make it that much harder to develop a good mental model of how your system works.

Reading code, the necessary precursor to writing code, is all about building a mental model. We’re trying to visualize something that doesn’t exist, by reading lines of text.

Reading code

Writing good code is all about making it fit for human consumption. Any idiot can write code a computer can understand, it takes care to write code another human can understand. But what does it mean to make code easy to understand? Programming is a literate task – writing well requires experience of reading code and in particular reading well written code. But how do we read code? Do we start at the beginning, read line by line until the end? Hardly.

Writing Code

Let me start by asking a different question: how do you write code? You probably write a test, write a small amount of code to make the test pass, then refactor to improve the readability, design etc of your code. Little by little the functionality accumulates; little by little the design emerges from a sequence of decisions and refactoring steps. TDD is fundamentally a design activity. Although you may have an idea of what your design will look like, the actual design will emerge from a sequence of small, interdependent activities.

Reading Code

Now six months later I’m reading this same code. What do I do? Well, I might start by reading the tests – well written tests should help me understand the intent of the code in question and, if they are acceptance tests, tell me what the customer thinks is important. In practice, I find this a painful way of figuring out the important things about a system. If TDD has been followed religiously you’ll have approximately 3.6 billion tests, grouped in a number of different ways making it difficult to keep enough context in my head at once to make any sense of the system.

Instead, I probably have something specific I’m trying to do. I have a change request or a bug to track down. So I probably dive in and make some guesses about where to start. I can’t remember the code so it’s a needle in a haystack. I probably miss and have to wend my way through the code trying to figure out how it fits together. I need to get some kind of big picture – at least for the part of the code I care about, as soon as I work out what that means.

Inferring Design

What I’m trying to figure out is the design of the software. How do these 200 classes relate to each other? What are the bigger patterns that will help me figure out where I need to look. Now, if I had documentation, I could look at that. But 1. it’s probably out of date or missing 2. even if it’s there, I wouldn’t trust it anyway.

So instead I find myself scribbling in a notebook – drawing class diagrams and other doodles with boxes, arrows and lines going all over. But how do I infer the design? I’m trying to understand how some parts of the system interact with each other. I look for references to interesting methods, chasing up the call stack to see what’s interesting. I find interesting classes involved along the way and I click through into interesting method invocations to see if anything fun’s happening. All the time I’m bouncing up and down call hierarchies trying to fit the system together.

As my notes start to coalesce I’ve got some idea about the key classes I care about and their roles in the system – I might find one or two key classes and scan through the whole thing, to see what other responsibilities it has. All the time following interesting method calls and looking for references.

The problem

Reading code is frankly nothing like writing code. But if writing great code means writing code that is easy to read, it’s a damn shame that the task of writing is so fundamentally different from the task of writing. It’s not as if after I’ve written a class I can pretend to have forgotten how it works and try and infer it. The best I can do is make sure that the code itself looks superficially clean. Is the style right? Is it formatted neatly? Insanely superficial stuff that frankly won’t make any difference in 6 months when I’m cursing the idiot that decided 16 levels of method calls with similar names was a sensible design approach (me).

The solution

On a superficial level I wish we could stop dealing with code as text and instead work on the syntax tree, with automated formatting following my preferences – not yours. Then we can all have our own idiosyncratic way of having code presented without having to argue for the billionth time whether spaces are preferable to tabs (of course they are) or whether curlies should be on a new line (of course not, heathen!).

The craftsmanship ethic of writing clean, well factored code is a good step. It doesn’t tackle the fundamental problem that reading code is different from writing it – but reminds us to address some of the symptoms: a comment is a whole new method just dying to be extracted. A 100 hundred line method will be at least twice as easy to understand refactored to two 50 line methods. Make things clear and simple and maybe the code will be easy to understand. Of course, I can still write a clear and simple mess.

It’d be nice if our IDEs were less damned text based. Maybe then we could have IDEs that give us a better, visual language to describe relationships between classes in. But it’s difficult to see how that would work, or how it would avoid degenerating into all the lousy CASE tools that already litter architects desks.

Ideally writing code would be more like reading code. We would be able to describe relationships natively, at a higher level than naming methods and classes. But what does that even mean?! How can I describe a set of classes without scribbling boxes and arrows? And more importantly, how do I make that a part of the implementation language so it never gets out of sync?