The most important goal in designing software is understandability


When you're designing a piece of software, the single most important thing to design for is understandability. Security, performance, and correctness are all important, but they come after understandability.

Don't get me wrong, all of those are important. Software that isn't correct leads to expensive errors and frustrating experiences. Slow software can be unusable and frustrating. And insecure software, well, we have a moral and an economic imperative to ensure our software is secure. But understandability supersedes these.

It's most important, above these, because you cannot ensure any of these other design goals without understandability. It has to come first.

Misunderstood software produces defects

If software is misunderstood by its implementers and maintainers, then it will end up with defects. Major defects. These will come in many forms.

The most obvious one is with correctness. If you can't understand a given piece of code, you won't be able to read it and understand that it's doing and what it should be doing. Tests are not your salvation here, because (1) they can cover only limited surface area, and (2) they suffer the same problem: if you don't understand the software you likely don't understand it enough to test it well.

This then gets tangled up with security and performance requirements, too. If you don't understand the system, how are you going to make it secure? You can't understand your way into perfect security—it's a process and it's not something that's done. But if you start from not understanding your software, any hope of security is entirely lost. You'll miss some base requirements and introduce grievous simple security problems, not the kind that come from complex and subtle interactions between components.

And when you don't understand the software, then any change you make for performance gains is likely to break critical functionality or secure behavior in fundamental ways. Caching can leak information or mess up your business logic. Improving queries to solve a performance problem can produce major defects, or even end up causing regressions in performance.

So if you don't understand the code, then it's a losing proposition to try to do anything with it: add a new feature, fix a bug, work on security.

"It's not me, it's you"

It's easy to feel shame or anxiety about not understanding the code. I carried that for a long time. There was a codebase I worked on in a previous role where I had no idea what it was doing. The backend was tough for me to understand, but I got it eventually. The frontend, no hope, I never made heads nor tails of it.

I assumed that I was just not a good enough engineer to understand our frontend code, and that there was something wrong with me.

Look, reader, I'm a principal engineer with over a decade of experience. I'm pretty good at my job: our tech leads and most senior engineers come to me for their hard problems, and I consistently debug things anywhere in our stack, including the frontend. If I felt I couldn't understand it, there were definitely others who also could not. And the fact that I blamed myself, with so much evidence that I was good at what I do... Turns out, the problem wasn't me, it was the code.

If you've felt similarly, know that you're not alone. And that it's not you. It's the code, the system around it. Tell that codebase "It's not me, it's you." Sometimes things are not understandable because you don't have expertise, but if you're generally experienced in the area that that code is in, it's quite probable that the problem is the codebase you're trying to work in.

How do we make it understandable?

So that just leaves the issue of how to make things understandable. There are a couple of general approaches. You can make the code itself inherently understandable, or you can give supporting documentation to aid in understanding it. Both are needed, and both have limits.

Make the code understandable

This is something we do routinely in software engineering, although it's easy to lose sight of it. There are a few key considerations I use when I do this:

Add supporting documentation

Sometimes, the code will just be hard to understand. This is usually when there's a tension between requirements. Performance improvement will often result in less clear code, for example.

It's also hard (impossible?) to understand the full context of a codebase from the code by itself. As much as we talk about self-documenting code, the codebase doesn't contain the entire system.

So we need some supporting documentation. Here are some things that are very helpful for understanding a codebase.

Those are just a few of the ways you can can add supporting documentation to help with understandability!

Gradual improvement works

Understandability is a fuzzy thing that's subjective. And it's not something that you can, or should, aim for perfection on. If you're working in a codebase today and it's hard to understand, the temptation can be to throw it away and start over. Sometimes that's merited, but often gradual improvement can be a good solution.

Each time you struggle to understand something, or you gain a better understanding through a task you work on, that's a good time to add documentation or improve the code to make it more understandable! Each small improvement will help you in the future and help your teammates. And each time you improve it, you lead by example and show people that this can and should be done.