What makes code good?
My brother recently asked me what makes code good. There are so many different factors that go into making a piece of code good. I am also going to talk a little about beautiful code. I think beautiful code has some overlap with good code, but not completely. I will distinguish between goodness and beauty (which is a subset of goodness). Here’s how Google defines ‘beautiful’ and ‘good’:
beautiful
/ˈbjuːtɪfʊl,-f(ə)l/
- pleasing the senses or mind aesthetically
good
/gʊd/
- to be desired or approved of.
Many of these virtues feed into each other, sometimes one helping the other, sometimes hindering it. Many of the less important virtues are there to support the more important virtues by making it easier for the programmer to manifest them, which in turn are there to support the most important virtues (which will, of course, always depend on what your goals for the project are, though, usually the same ones appear near the top of the list).
So, in some vague half-order, here are some of the different factors that go into making good code.
Function
The most important question is, does it work? As a programmer, this may be the most boring requirement of good code on the list, but I believe it’s the most important. Functional code is good code.
Even if you were only looking at a piece of code to appreciate any of the other qualities and didn’t care at all about the resulting program, if you know it doesn’t work, all the other qualities stand for nought (one exception: examples highlighting use cases for feature proposals to a language). It’s easy to create code that adheres to strict standards of goodness if there’s no requirement that they work.
For the users of the code, this is the most important quality, as it gives meaning to the program. For the (insular) programmer who doesn’t care about the program, this is the most important quality, as it gives meaning to the code and the challenge faced in producing it.
Correctness
Correct code is good code. Isn’t this just the same as above? Well, no, not exactly. There’s a quote attributed to Kent Beck that goes a little something like this:
Make it work, make it right, […]
That “make it right” means different things to different people, but one possible interpretation is to say that, “make it work” means to make the program work in the ‘happy path’ and “make it right” means to cover edge cases.
So, a functional program (not to be confused with functional programming) is a program that does its job in the happy path. It’s useful, even a little bit, and serves some loosely defined purpose. By contrast, a correct program is one that adheres to some more precise specification that covers what the program should do in non-happy cases.
This is a much more difficult (and currently impractical) endeavour, even if we could do it, and is a whole subfield of computer science.
Maintainability
Maintainable code is good code.
If the program works, it may become very successful (i.e., it achieves its goals very well). If it becomes successful, it will probably have a long life serving the needs of its users, and those users, now more successful with whatever their goals are with the help of the program, have new needs due to their newfound success. They may want to increase the scope of the program. This is not a bad thing, but you should be careful about scope creep.
A maintainable program is one that’s easy to change to fit new requirements that are likely to come up. Not only that, but it’s possible to change without sacrificing maintainability. By that, I mean that if the program is easy to change to fit new requirements, but changing it decreases maintanability, then it wasn’t really that maintainable to begin with.
Readability
Readable code is beautiful code. It’s code that can be read… and easily understood. It’s best if a non-programmer can look at the code and at least sort of have an idea of what’s going on. Readable code is more maintainable code (usually).
However, it’s important not to target non-programmers when deciding between something that is more readable to a non-programmer vs. something that’s more readable to a programmer who takes the time to understand abstract concepts (you should know your reader and strike a balance). Software development is all about abstraction. They make our lives easier, as programmers and non-programmers.
Expressivity
Expressive code is beautiful code that clearly expresses the author’s intent to the reader. Expressive code is readable code is maintainable code and expressive code is also clear code is maintainable code.
For example, when fixing bugs, oftentimes, you may be reading a code fragment and it looks like the author made a particular decision for the code to perform its task a certain way. If it looks like the author was very sure in their intent to do it the ‘buggy’ way, you may wonder whether fixing the bug may introduce other errors that the original author was trying to avoid. Incidental trivia should look incidental, and purposeful decisions should look purposeful.
Unfortunately, this particular example and others like it are quite tricky to avoid, but it’s part of our job as programmers.
Concision
Concise code is beautiful code. If I had to choose between the two pieces of code below:
numbers = [1, 2, 3, 4, 5].map { |i| i + 1 }
numbers = [1, 2, 3, 4, 5]
numbers.each_with_index do |number, index|
numbers[index] = number + 1
end
I’d choose the first one in a heartbeat. Although, after learning some Haskell, that ugly block syntax for such a simple lambda makes me queasy. Haskell does this much more beautifully: numbers = map (+ 1) [1, 2, 3, 4, 5]
. Mmm…
Despite the fact that the second one is more readable to someone who has no idea what map
is about, to someone who does, the first is much more readable, as the abstraction of map
is ingrained in them, and they instinctively understand what you’re doing without having to read such boilerplate that could subtly change.
Avdi talks about making same things look the same, and different things look different. Abstractions like map
(applying a function to all elements of a list, returning a new list with the elements replaced by the result of the function application) help us do that.
Being too concise hurts readability and expressivity. At a certain point, the code just becomes inscrutable. You don’t want write-only code. There isn’t a hard line to draw, just know your reader, nearby code, language/community idioms, and other contextual factors.
Simplicity
Simple code is beautiful code. Simplicity is about the right level of abstraction. Abstraction is a useful tool, but not all abstractions are equivalent. There is such a thing as too much abstraction (indirection/overengineering) or the wrong abstraction or doing something at the wrong level of abstraction or too little abstraction.
Done well, abstractions should encapsulate something complicated into something simple (but not something complex into something simplistic). Abstractions can build on each other, but be careful you don’t add unnecessary layers or complicate the whole system in your pursuit to simplify the parts. Those parts will need to work together, and if they’re broken up into unnecessarily small pieces, the – possibly repetitive – glue code could end up being what complicates things.
Speaking of repetition…
DRYness
Good code should be DRY. DRY stands for Don’t Repeat Yourself and DRY code is nonrepetitive code, and is a huge part of the reason we program in the first place. The first virtue of a great programmer is laziness, “the quality that makes you go to great effort to reduce overall energy expenditure”. One of the great strengths of programming is the ability to delegate tedious, repetitive tasks to the machine.
Well, while automating, we often encounter repetitive tasks that we need to do to support the architecture and building of the automation, so what do we do? Why, automate them of course. There exist many abstractions to reduce duplication in code, and many tools to reduce duplication in programming effort.
Performance
Performant code is good code. Most of the time, performance isn’t a huge deal, because whatever you write will probably be faster than whatever manual process it takes over, or not significantly slower than whatever automatic process it takes over (from the human’s perspective, if you do it right). That said, users do expect a lot from a machine nowadays, and a web page that takes 2 seconds to load, for example, is probably at least 1.5 seconds too long. Spending too much time on this and on the wrong parts of the codebase is considered premature optimisation (which, as a concept, could also be applied to other qualities of good code).
In some contexts, like automated trading systems, performance is critical, and really is part of the functionality, as there are minimum performance characteristics required for the program to be successful.
Most of the time, when it comes to performance, [you should only care about the low-hanging fruit.
Cleverness
Clever code has a certain beauty to it for software developers, this is like candy to us. Unfortunately, cleverness is often at odds with reliability, as it may rely (foolishly) on private APIs, low-level tricks, and other unspecified or unguaranteed behaviours.
Reliability
Good code is reliable, rock solid, and doesn’t break. This is not just about adhering solidly to a spec, like correctness, but rather about the spec itself being solid and anticipating as many practical edge cases as reasonably possible.
I guess this is not so much about code itself, but good code should be an embodiment of its spec (correct), and if the spec leaves a lot undefined, good code should account for that, anyway, while still adhering to the spec where it is defined. So, the code itself should be reliable, even if the spec doesn’t force it. This means being liberal in what you accept, and conservative in what you send.
Form
Aesthetically and ergonomically pleasing code is beautiful code. This is probably one of the easiest ones for non-programmers to appreciate. It’s easy to navigate with your editor and easy to scan with your eyes, or whatever you use to consume code.
Ergonomically pleasing code is more maintainable, as it’s easy to navigate, and aesthetically pleasing code is more readable.
Consistency
Beautiful code is predictable, in form, as well as function. Consistent code is also more maintainable. Combine this with good form, and you have a winner. However, consistency is more important than form itself. I can’t think of a single rule of good form that is worth being inconsistent for. Code that adheres to best practices followed by a language or framework’s community is said to be idiomatic.
The reason consistent (and aesthetically pleasing) code is more maintainable is that it’s easy to search through, and easy to record editor macros for. Yep, more automation. Adhering to more rigid standards helps the reader scan the code easier, and helps when searching with the computer too, as you don’t need to search for such complex patterns (yep, simplicity rearing its beautiful head again).