Clean code – 15 steps to create clean code
Every programmer understands how much trouble and frustration can be brought by poorly written code. Yet, not everyone knows how to write clean code and what distinguishes it. This article is compulsory reading for everyone who wants to learn the techniques of thorough and effective coding.
Probably every programmer has - at some point - stumbled upon sloppy code, wasting a lot of time trying to get through incomprehensible variable names, twisted program structure, and hidden method behaviors. Such a mess slows down your work, creates frustration, and, in a way, makes you afraid to introduce any changes. All this significantly increases the cost of software production. Each new functionality or system modification requires the programmer in charge to navigate through these tangled and impenetrable vines of someone else’s code, consuming a lot of their precious time. Trying to change one part of bad code will likely lead to errors in some other parts. That usually ends up in re-analysis and eventually bug fixing. A situation like this is a classic example of regression: something that used to work suddenly stops working. Usually, a mess creates an even bigger mess, which greatly reduces the efficiency of the team and becomes extremely hard to "clean up" over time. As a consequence, sloppy code leads to project stagnation and may bring major problems to the company.
What’s behind sloppy code?
Reasons for someone to write messy code can be many: not having enough knowledge, not realizing the potential consequences of bad code, or not being experienced enough to detect and correct your own messy code. Think of it as painting a picture. Most people can tell if a painting is pretty or ugly, but that doesn't mean they know how to paint. Rush, deadlines, and pressure from management are probably the next most common causes of code clutter. Unfortunately, this is a trap that programmers quite often fall into - you must never sacrifice the quality of your code only to meet a deadline! Professional programmers know perfectly well that code clutter will immediately slow them down, and make it even harder to meet a deadline. Clean code is the best path to success, it makes it easier to meet deadlines, keep high-quality software, and, above all, develop applications quickly. Programmers should fight for their code with great passion just like their superiors fight for keeping to the schedule.
How to detect sloppy code?
The most common method showing whether someone’s code is clean or sloppy is the number of WTFs other programmers shout out when dealing with it. The more WTFs the worse the code. Additionally, you might want to use a special tool, such as Sonarque, which, based on automatic static code analysis and defined rules, can clearly show defects and impurities in the code. However, these are only machines that should, by all means, be used for preliminary feedback, but the best evaluation of the code is the assessment of other experienced programmers who can "feel the code", are familiar with the rules of clean code know how to apply them and have seen lots and lots of applications in their professional life.
What does “clean coding” mean?
Even though specific definitions might vary from one programmer to another, the fundamentals of clean code should be always the same. Clean code should be:
- clear, self-commenting, orderly. A common reference to good code is a good article. There is an introduction that gives a general idea of what the article is about. There are suggestive, understandable headlines with a few paragraphs that order thoughts and threads in such a way that the article is enjoyable and easy to read. Trying to translate these ideas into code, headings would be classes, paragraphs would be methods, and sentences would be instructions,
- easy to implement, short, without any mysterious tricks that do not add any value,
- pass all tests without problems
- reliable and intuitive, meeting all of the SOLID principles.
How to write clean code?
First of all, one must realize that no code is perfect. Your code can, at best, be the cleanest and best-written at a given moment. Writing such a code requires skills that you gain through hard work and practice, based on knowledge in the field of programming, along with knowing the rules, patterns, and clean code heuristics.
Here are 15 basic rules for writing clean code:
1. Descriptive names
The names of variables and classes should be descriptive. They should explain the intentions, inform the purpose they exist for, the role they play, and the way they should be used.
Below is an example of unreadable code, packed with magic numbers (0, 1) and meaningless names (l, PTLights, m1). Additionally, the name of the variable is misleading in the sense that the lowercase L looks very similar to the number 1.
Turning those magic 0, 1 numbers into constants and introducing appropriate class, field, and method names significantly improves readability. Here’s a sample solution:
2. Names at the appropriate level of abstraction
Even though you remember to use descriptive names, they can say too little or too much at a particular level of abstraction. For example:
The csvPath variable makes it clear that this is the path to a CSV file. Yet, someone may ask: ”Can only CSV files be compressed”? “Can files only be compressed into ZIP format?” Names like that restrict the use of this method to compressing CSV files in ZIP format only. Although this limitation is only at the context level, it means that the method may be used less often. Additionally, very similar methods to compress other files in other formats might be created unnecessarily. In this case, a better method name would be compress and the name of the variable: filePath.
3. Names should describe side effects
The name you choose should describe what a given method, variable or class does. For example, if a function is created to search for an object and - in the case of not finding it - create a new one, it is better to use the name findOrCreate instead of just find.
4. There should be no need to write comments
Typically, having to restore to writing comments in your code is caused by the fact that names of certain variables, methods, and classes are illegible or incorrect. Instead of writing comments, it's better to spend time choosing the right names. If writing a comment really serves a purpose, make sure to write it well. It should be concise, grammatically correct and use carefully selected words.
5. Commented-out code should be removed
Commented-out code raises a lot of questions from the reader, it pollutes the entire code and interferes with reading. The longer it exists, the more it breaks down and loses its relevance. To make matters worse, it may happen that no further reader will dare remove it because they don’t know if it is important or not, whether or not someone needs it or has plans for it. We can’t highlight it enough: commented-out code should always be removed.
6. Code formatting according to the rules established within the team
When you work in a team, you can’t freely pick formatting rules, just because you like them the most. It is up to the whole team to establish a single formatting style and every member should stick to it. Your code must look consistent and reliable to the reader, giving him/her certainty that formatting in one source file will mean exactly the same in another source file.
7. Functions should be small and perform one operation only
The size of a function can be measured by the number of rows it is made of. The more lines, the harder to read the function becomes and the more difficult it is to understand its operation. Small functions are those that are composed of a maximum of 20 lines and only ever do one thing. If there are more rows, it is quite probable that your code function has multiple returns.
The code above is not too long, but it clearly has multiple returns: it collects relations, deletes every relation, logs information about deleted relations, and deletes the car. This can be corrected in the following way:
In this case, it is also very clear that the function performs 3 actions, but it should be stressed that all the 3 steps are placed one abstraction level below the declared function name, which means that the whole function has in fact only one return.
8. The fewer function arguments, the better
Function arguments are troublesome and make it harder to understand the function. It is best to build zero-argument functions, then one-argument and two-argument functions. Try to avoid functions with 3 arguments, and functions with multiple arguments (3+) should not be used at all. For example, writeText(text) function is way easier to understand than writeText(outputStream, text). You can always get rid of the outputStream argument by defining it as a class field. When a given function requires too many arguments, some of them might probably be placed in separate classes. For instance:
can be replaced with:
9. Code functions should never return NULL references
Returning NULL references creates unnecessary extra work and causes problems in caller functions. By not checking for null values, you lose control of your application. NullPointerExceptions start appearing, and they might interrupt the functioning of your program in the least expected time. In the example below:
instead of returning null in the getCars() method, it is better to return an empty list…
10. Separating commands from queries
A function should either execute a command or respond to a query. Yet, it is important to remember that the same function should not perform both operations at the same time, as it often leads to mistakes. Have a look at the function below:
As you can see, it sets a value on the given attribute and returns true on success and false if no such attribute exists. This results in strange instructions such as:
You might ask yourself: “what does this function actually do?” “Does it verify if the “title” attribute was previously set to "Clean Code"? Did you manage to set the "Clean Code" value on the "title" attribute? In this case, the word "set" can be either a verb or an adjective, which makes it difficult to infer the meaning by calling this method.
The solution to this puzzle lays in separating the command from the query, which helps to avoid any confusion.
11. DRY - the “Don’t Repeat Yourself” coding principle
According to the DRY principle, repetitions during software development should be avoided. Good coding is not about pasting or writing the same or very similar pieces of code in different places. Such code duplication not only makes it unnecessarily long, but also requires you to introduce the same modifications in several places in case of any changes to the algorithm. This, in turn, increases the risk of mistakes.
The easiest solution for duplicate code is moving it to a separate method. Resorting to polymorphism or design patterns (method template or strategy) can also be helpful in certain cases.
12. Errors should be handled by a single operation
Error handling is just one operation and according to the single responsibility principle the error handling function should not do anything else. That means that your function should only contain the keyword “try” as its first element, as well as the words “catch”and “finally”.
13. Code classes should have a single responsibility
The single-responsibility principle (SRP) is a programming principle that states that every module, class or function in a computer program should have responsibility over a single part of that program's functionality, and it should encapsulate that part. If a class is responsible for more than one area in your application, it can lead to problems in the future, e.i. when introducing changes in one area, you may mess something in another, seemingly unrelated one.
In the example above, the FileCompressor class has more than one responsibility. Not only does it compress the file, but it also creates a file catalogue. If you want the FileCompressor to follow the SRP rules, you should transfer it to a separate class using the createDirectory method, e.g. DirectoryCreator.
14. Hermetization– variables and utility functions should remain private
In most cases, variables and utility functions in classes should be private. Such access hiding results in safer refactoring and fully controllable object status changes. Sometimes it is necessary to change the scope of variables or functions to “protected” or “package-private”. That happens mainly when a given function needs to be called or referred. However, your primary goal should be to always look for a way to maintain privacy.
15. Class consistency should be high
A class where every instance variable is used in every method is maximally consistent. Such perfect results are not always possible, but you should always try to achieve a high level of class consistency. Thanks to it, class methods and variables become interdependent and when using small functions with a small number of parameters, you will often find that some instance variables are only used by a subset of the methods. Unfortunately, that means that there is another class within a class and your job is to release it. By doing that you will improve your class consistency. For example:
The Engine class has a “gear” field which is used only in two methods: shiftGearUp and shiftGearDown.Thanks to this observation, you can create a new Gearbox class, which will lead to a high consistency of the Engine and Gearbox classes. Additional benefit is that your classes will obtain single-responsibility status.
Clean code is the foundation of a good application that is easy to maintain and extend with new functionalities. Writing clean code requires knowledge, practice, and assertiveness. The principles presented in this article are just a drop in the ocean of clean code rules. However, using them is the first step to increasing the quality and readability of our code:
- Choosing descriptive names for classes, functions, and variables
- Names at an adequate level of abstraction
- Names should describe side effects as well
- No comments should be necessary. Your code should be self-describing.
- Code formatting should be done according to the rules your team has established
- Functions should be small and perform only one action
- Functions should take no more than 3 arguments The fewer the better.
- Functions should not bring back null
- Functions should either perform a command or answer a question
- The DRY principle should be applied
- Errors should be handled by a single operation
- Classes should have one responsibility
- Variables and functions should be private
- Classes should be highly consistent
- Code should be unit tested
- Object-oriented programming should meet SOLID principles