In Pursuit of Good Code: Defining Programming Principles
There’s no shortage of “good code” advice on the internet already, but many sources are entirely or mostly composed of the how, rather than focusing on basic principles. In a quest to create a list of principles that can be followed regardless of language or project, I’ve consolidated and organized the rules and advice I’ve accumulated over the years, combined with tips gleaned from other sources and internal mentors.
This subject came up for me around the area of mobile development as I was evaluating a number of languages, frameworks, and so on. All the different languages, design patterns, and other coding things rattling around in my brain triggered this particular vision quest. I've been programming for a long time and I've built up a method of working, but I've come to understand how disjointed and unorganized my internal rules and guidelines were.
Is it possible, I wondered, to create an organized list of the principles of “good” code? Naturally, the first thing I did was to do a search to see if someone had already done the work. The resources I found, however, either mostly or entirely emphasized practice and not principles.
Between my experience, my research, and input from my exceptional colleagues, I've put together a workable list of coding principles that can be incorporated into training opportunities, code reviews, and more.
The Principle Thing
There is a value in going back to first principles. It provides clarity and allows you to break down complex problems. Moreover, they provide a guiding star that can be useful when things get complicated. As we learned from the Cheshire Cat, it’s hard to know where you’re going to end up if you don’t know where you want to get to.
Another valuable attribute is that foundational principles tend to be a lot more compact than their implementation. The constitution of the United States, plus all 27 amendments (our country’s founding principles) contain just 7,591 words. The federal laws for the U.S. require an entire library! This makes a set of principles easier to remember and to reason about.
In our specific case, the guiding principles can be independent of language, frameworks, and design patterns which, hopefully, makes them applicable across a broader spectrum of developers.
The Principles of Good Code
Understanding The Principles
Now that we have a list of guiding principles and have discussed what they’re good for, let’s discuss what they mean.
Good Code is Correct
If your code is good then it should do everything it’s supposed to do. This one should be pretty self-evident. If the code doesn’t do what it’s supposed to do, then it can’t possibly be correct.
A well-written code does nothing it’s not supposed to do (like leaking memory, for instance)! This one is less obvious. The code can’t be good if it’s misbehaving. This might be a performance issue or unintended effects on other parts of your program.
With the above tenets in mind, good code should do both of those things even when given bad/weird input. It’s not overly difficult to write code that works properly with expected inputs and successful communications. It gets a lot more complicated when those things go in odd directions. Writing code that handles those challenges properly takes a lot of effort to get working right. It takes even more work to make such code readable and obvious.
When your code just can’t be correct (low memory, communication errors, etc.) it should fail safe and inform the user of the failure. This is the more catastrophic version of “does both of those things even when given bad/weird input.” What do you do when you just can’t go on? A lot of languages have good exception handling facilities these days. However you code it, you need to back out as gracefully as you can and let the user know that you just can’t proceed. Remember, all that code needs to be easily readable.
Good Code is Secure
Your code should be as secure as necessary. The amount of time and effort you need to put into making your code secure depends entirely on the needs of your application/system. At the very least your effort needs to be sufficient to protect your user’s data.
Good Code is Efficient
Good code needs to be as efficient as necessary, this means it should be fast enough to satisfy the user. Optimization can cause trade-offs with readability. So, optimize only when necessary. Keep in mind that you still need to make the code readable!
The points above are tenets that can be checked when the code is first written. Unit tests and a strong QA process can ensure that the code does what is intended, handles bad input and communications, etc. as soon as the code is finished. The next set, however, can only be judged by your future self or one of your colleagues after time has passed. Only then will you know whether your efforts have been sufficient to make maintaining and enhancing your code as easy as possible.
Think about what you need to do when it’s time to modify the existing code. You need to find the code that needs to be modified and understand the current code (what it’s doing and why). You can then determine how to change the code appropriately; if you misunderstand what the code is doing, you can easily introduce new bugs.
These tasks are made much easier if whoever wrote the code has followed the remainder of our principles.
Good Code is Discoverable
Your code should be understandable and discoverable at all levels of abstraction — statement, function/method, module, and subsystem for example. You will find plenty of advice on the internet telling you what you need to do to make your code readable and you should follow any you think will help your code down the road.
Finding the code you need to fix or enhance may seem easy if your codebase is small. When your code runs to hundreds of thousands of lines, it’s a non-trivial task. Even at the class/module or subsystem level, one should be able to make an educated guess as to what its purpose is – even if that means maintaining class, module, and system comments.
To make matters even more complicated, some modern languages and design patterns encourage creating many smaller source files. This practice encourages making code structurally clear. However, this makes it even more difficult to know where to begin looking for the code of interest if the source files are not logically named and organized (and documented or commented if necessary).
It’s important to ensure your code contains no duplicated code. Code that gets copied and pasted can mean that you need to fix the same code in many places. Some of which will try to hide. Or at least that’s the way it seems. I should mention, however, that this issue can be difficult to avoid. If your codebase is large, it is easy to inadvertently duplicate code that already exists, because you don’t know about it.
Next, make sure your code contains no unused code or out-of-date comments. Out-of-date comments can be misleading or sometimes outright lies – with the comment in direct disagreement with the actual code. Additionally, unused code and out-of-date comments are distractions that make it more difficult to find the code you are looking for.
Good Code is Easy to Fix and Enhance
When you have good code, it’s clear not only what the code is doing but why it’s being done (code intent). You can’t fix or enhance a piece of code if you don’t understand what it’s doing and why it’s doing it. You might end up guessing about the intention and introduce new bugs as a result. There are a lot of suggestions out there about how to achieve readability and most of them seem valid. I recommend, the acid test, which is handing your code to the most junior programmer on your team and standing back. If they can figure out, from just the source, what it’s doing and why, you’ve done well.
Your code needs to be structurally clear, meaning the code is grouped and organized reasonably and thematically. I’ve seen code where each individual statement was relatively clear, but data access and manipulation statements were intertwined with display code and other things. If every statement in a method/function doesn’t have a single intent and if every method in a class or function in a module doesn’t support a single intent, then you still have an unmaintainable mess. It’s still going to be hard and time-consuming to figure out what needs to be changed for your fix and/or enhancement.
Another sign of good code is it’s easy to factor and reorganize. Similar to being structurally clear but with a different need. If methods, classes, and modules are not organized according to their intent, then maintainers will have to work three times as hard to add enhancements. They’ll have to get the code to a factorable state, then refactor it, before they can actually begin coding.
Last but not least, good code should ensure write access to variables is properly protected. I realize that this tenet seems oddly specific. But this one is an absolutely essential principle and it doesn’t seem to fit within any of the others. I can’t imagine how many programming hours have been lost to tracking down a bug to a variable that contained the wrong value at the wrong time – and then not being able to figure out how it got that way. If the data is not encapsulated in one way or another, you may have to search the entire codebase to find the offending statement(s). This used to be a bigger problem when global variables were common, though it can still occur when object variables are not protected. When variables are properly protected, you can assume the problem lies somewhere in the vicinity.
Putting It Into Practice
There is great value in these tenets. If you practice these in your development project the future-you who is adding a feature or fixing a bug will thank present-you who crafted the initial code so carefully.
I found this to be a fascinating exercise and hope it has been useful for you as well. In addition to providing a resource for others to follow, it has given me a sense of clarity and completeness for this topic. At the very least, you should be able to measure your department’s style and programming guide to see what might be missing. On the other hand, if you think there are other general principles that should be added to this list, please send me a note at David.Rogers@bounteous.com, I'd love to hear about it.