The best code is pseudocode

19 Feb 2024

When I find myself giving code review, the kind of review that aims to make code not just good but also great, I keep coming back to the same principle, that the best code is pseudocode.

Pseudocode and top-down programming

There are two great ways to decompose a difficult problem: you can go bottom up, building capabilities until you are able to express a solution, or you can go top down, sketching the high level solution and then filling in the details.

Pseudocode is the top-down approach, the approach where you try to say in a few lines how you want the problem to be solve and the control flow to go.

Perhaps you do all this just as comments, or using hypothetical functions that don't exist yet. Then you flesh them out as you go.

More than the exact solution, pseudocode is the clean story and intent behind what you're doing, and this makes it the perfect entry point for others into your code.

Transitioning to working code

Of course, you need our code to actually work, so you begin a process of turning the pseudocode into real, functioning code by writing what you need bit by bit. Now you're forced to make a lot of decisions, and recognise a ton of details you weren't aware of before.

The naive way of doing this is to let it all expand into the function or method that you're writing. Before long, it can expand into a page or two, and for you who's writing it that can be OK, but it becomes very unwieldy for others coming after, and for those who come to it with less context.

The appropriate top-down approach is to continuously refactor the code as you go, pushing details down into helpers and making sure that the high level story is still clean and clear, and can be understood at a glance.

A particular challenge can be the number of distinct arguments that you need to pass around between functions, and your ability to come up with meaningful abstractions that fit those arguments well. You may need to create new types or structures to hold them in order to retain a pseudocode-like feel.

If you manage to do this well, you'll be rewarded by code that both works great but is also easy to understand, maintain and navigate.

Can it be done bottom-up?

Programming bottom up is also a great way to work. When you do this, you find you have identified good fragments of a solution, and your process is to integrate them together again and again until you have a complete solution.

As you integrate, you will notice the conceptual mismatches between parts of your solution, which causes you to refactor and adjust the fragments until they fit together cleanly.

💡 If you code in a bottom-up style, you should at the end still take the effort to ensure that the highest level methods tell a very clear story.

This is the same as the principle behind writing commit messages, pull-requests, or academic papers: to always tell the most engaging, clear and accurate story you can about the work, rather than about the path you took to get there.

Can it be done in all languages?

When I review code, I often ask myself, "What would the pseudocode for this look like? Is there a good reason this code is different from the pseudocode?"

In the high level languages that I work most in, like Python and Typescript, the answer is almost always no -- the author could have written the code in a way that was much more pseudocode-like, but they did not have the time or perspective to tell their story well.

It's possible that other languages are less suited to this kind of approach. Low-level languages definitely still support it. But in my mind it's entwined with a kind of imperative control flow, so declarative and functional languages might be slightly less suited to this particular style.

What skills are required?

One reason this doesn't happen enough is because it takes some skill and practice to decompose a program so clearly.

To tell a clear story in a limited number of phrases, you need to be able to find natural seams in the control flow, in other words great abstractions. To make these phrases clear and engaging, you need the skill of naming things well.1

When a function body spans multiple levels of abstraction, contains complex control flow and is full of details, it's not clear to me that the author really gave themselves the chance to reflect on and fully understand the overall arc of the code or its effects.

Do coding assistants change our style?

Today, many of us use GitHub Copilot or a similar coding assistant. They can be incredibly useful at helping us to fill in the details of an idea, to write boilerplate code, and to get started with APIs that we don't know well.

I have the impression that coding assistants are better at helping us to write code bottom-up than top-down, just as they are better at writing new code than refactoring. Bottom up they help us build capabilities that we can sketch into control flow, and they are anchored by the pieces we've already built or by the APIs that we need to use.

That really means that the job of continuously refactoring and re-integrating the pieces of our code together, that still sits with us. Likewise, the job of telling a clear story about our code, as close to pseudocode as possible, that still lives with us too.

Footnotes

  1. I've written before about naming as a core programming skill, and I'm forever reminded of the world of Earthsea, where knowing the true name of something gives you power over it. ↩