An Introduction to Clean Code
Learn about the meaning and importance of clean code, what technical debt is, and when code quality shouldn't be the focus.
We'll cover the following
Clean code is code that is clear, understandable and maintainable. In this chapter, we'll explore what that means and why it's important.
The meaning of clean code
There is no sole or strict definition of clean code. Moreover, there is no way of formally measuring clean code, so we can't run a tool on a repository that will tell us how good, bad, or maintainable that code is. Sure, we can run tools such as checkers, linters, static analyzers, and so on, and those tools are very helpful. But while they are necessary, they are not sufficient. Clean code is not something a machine or script can recognize (so far) but rather something that we, as professionals, must make decisions about.
For decades of using the term programming languages, we thought that they were meant to communicate our ideas to machines so they can run our programs. We were wrong. That's not the whole truth, but rather part of the truth. The real meaning of the "language" part of "programming languages" is to communicate our ideas to other developers.
Here is where the true nature of clean code lies. It depends on other engineers to be able to read and maintain the code. Therefore, we, as professionals, are the only ones who can judge what clean code actually is. Think about it; as developers, we spend much more time reading code than actually writing it. Every time we want to make a change or add a new feature, we first have to read everything surrounding that code. The language (Python) is what we use to communicate among ourselves.
The importance of having clean code
There are a huge number of reasons why clean code is important. Most of them revolve around the ideas of maintainability, reducing technical debt, working effectively with agile development, and managing a successful project.
The first idea we'll explore is in regard to agile development and continuous delivery. If we want our project to successfully deliver features constantly at a steady and predictable pace, then having a good and maintainable code base is a must.
Imagine we are driving a car on a road toward a destination we want to reach at a certain point in time. We have to estimate our arrival time so that we can tell the person who is waiting for us. If the car works fine, and the road is flat and perfect, then we shouldn't miss our estimated arrival time by a large margin. However, if the road is in poor condition and we have to step out to move rocks out of the way, avoid cracks, or stop to check the engine every few kilometers, then it is very unlikely that we will know for sure when we are going to arrive (or if we will arrive).
We think the analogy is clear: The road is the code. If we want to move at a steady, constant, and predictable pace, the code needs to be maintainable and readable. If it is not, every time product management asks for a new feature, we'll have to stop to refactor and fix the technical debt.
Technical debt
Technical debt refers to the concept of problems in the software that occur as a result of a compromise or a bad decision being made. It's possible to think about technical debt in two ways. From the present to the past: What if the problems we are currently facing are the result of previously written bad code? And, from the present to the future: If we decide to take a shortcut now, instead of investing time in a proper solution, what problems are we creating for ourselves further down the line?
The word debt is a good choice. It's debt because the code will be harder to change in the future than it would be to change it now. That incurred cost is the interest of the debt. Incurring technical debt means that tomorrow, the code will be harder and more expensive to change (it would even be possible to measure this) than it is today, and even more expensive the day after, and so on.
Every time the team cannot deliver something on time and has to stop to fix and refactor the code, it is paying the price of technical debt.
One could even argue that a team that owns a code base with technical debt is not doing agile software development. Because what's the opposite of agile? Rigid. If the code is riddled with code smells, then it can't be easily changed, so there's no way the team can quickly react to changes in the requirements and deliver continuously.
The worst thing about technical debt is that it represents a long-term and underlying problem. It is not something that raises an alarm. Instead, it is a silent problem, scattered across all parts of the project, that at some point will wake up and become a showstopper.
In some more alarming cases, "technical debt" can even be an understatement, because the problem is much worse. Previously, we referred to scenarios in which technical debt makes things harder for the team in the future, but what if the reality is much more dangerous? Imagine taking a shortcut that leaves the code in a fragile position (one simple example could be a mutable default argument in a function that causes a memory leak). We might deploy our code and it works fine for quite some time (as long as that defect doesn't manifest). But it's actually a crash waiting to happen. One day, when it's least expected, a certain condition in the code will be met that causes a runtime problem with the application, like a time bomb inside the code that goes off at a random time.
Clearly, we'd like to avoid scenarios like that one. Not everything can be caught by automated tools, but whenever it's possible, it's a good investment. The rest relies on good, thorough code reviews, and good, automated testing.
Software is only useful to the degree to which it can be easily changed. Think about it. We create software to respond to needs, whether that's purchasing a plane ticket, shopping online, or listening to music, just to name a few examples. These requirements are rarely frozen, meaning the software will have to be updated as soon as something changes in the context that led to that software being written in the first place.
If the code can’t be changed (and we know the needs leading to that code do change), then it’s useless. Having a clean code base is an absolute requirement for it to be modified, hence the importance of clean code.
Some exceptions
We've explored the critical role a clean code base plays in the success of a software project. That being said, remember that this is a course for practitioners, so a pragmatic reader might rightfully point out that this begs the question: "Are there legitimate exceptions to this?"
Indeed, there are some cases in which we might want to think about relaxing some of the constraints around having a pristine code base. Here is a list of situations that might justify skipping some of the quality checks:
Hackathons.
Writing a simple script for a one-off task.
Code competitions.
Developing a proof of concept.
Developing a prototype.
Working with a legacy project that will be deprecated, and it's only in maintenance mode for a fixed, short-lived period of time.
In these cases, common sense applies. For example, if we just started on a project that will only be live for a few months until it gets decommissioned, then it's probably not worth going through all the trouble of fixing all of its inherited technical debt and waiting for it to be archived might be a better option.
Notice how these examples all assume that the code that can afford not being written under good quality standards is also code we'll never have to look at again. This is in-line with what we've previously discussed and can be thought of as the counter-proposal of our original premise: We write clean code because we want to achieve high maintainability. If there's no need to maintain that code, then we can skip the effort of maintaining high-quality standards on it.
Remember that we write clean code so we can maintain a project. That means that we have to be able to modify that code ourselves in the future, or, if we're transitioning the ownership of that code to another team in the company, we want to make that transition (and the lives of future maintainers) easier. That also means that if a project is in maintenance mode only, but it's not going to be deprecated, then it might still be a good investment to pay off its technical debt. This is because at some point, there will be a bug that will have to be fixed, and it will be beneficial for the code to be as readable as possible.