Programming is difficult, I know. Doing it well is even more so.
You need abstract thinking and practice to know how to program. But to do it well, you also need dedication and discipline. Instead of considering these as two different aspects of one task, what if we think of them as one thing?
This is the aim of clean coding, a concept coined by Robert C. Martin in his book Clean Code.
What is a Clean Code?
A code is considered to be clean when it’s easy to read and understand. If it helps to solve problems without adding unnecessary complexity, and enables easy maintenance and adaptation caused by changes in requirements, then we have a clean code before our eyes.
In order to write a clean code, you have to know and apply a set of development principles or techniques that help to avoid code smells, that is, those signs indicating there’s a deeper problem to a program.
We will explain a few of those techniques, so as to appreciate the difference between legible code (easy to be read by any programmer) and obfuscated code (that can be read only by a computer).
In our examples we will use Kotlin, a statically typed language that runs in Java Virtual Machine. We will see that its features allow to write a significantly cleaner code.
1. Small Functions and Meaningful Names
One golden rule is that functions have to be as small as possible and be in charge of one task only, which makes them more understandable and less prone to changes.
The names given to variables and functions have to be clear and descriptive, so that the code can be read fluently and understood quickly.
As an example, we will show a function displaying a list of the best youngest players in an online game. Data classes will be Winner, Player, and Score, and Level will be the enum class, all part of the game’s rules.
Some issues could be observed related to the function’s too many responsibilities and big size, names that were confusing, and deep nesting of conditional blocks, which prevents it from being easy to understand.
The first step is to refactor the function and show the final result. Then we will explain the criteria used to improve the code and we will show partial results.
Refactoring, Step by Step
In order to improve the code, first we split the filter function into two smaller functions, each one with its own logic.
Filter functions receive lambdas, which are very similar to anonymous functions. When a lambda expression has only one parameter, Kotlin implicitly names it it. This helps to reduce the code’s size, but it can be confusing sometimes to determine the context it belongs to, for example, in the case of nested lambdas.
That isn’t the case here, but, for the sake of readability, the lambda variables are implicitly named Player.
In order to improve the code’s legibility, the lambda expressions were transferred to functions with meaningful names, like isYoung function, which will be part of the Player class, and the scoreExceedsMinimumValue function.
Since these functions have only one expression, Kotlin can easily infer their return value, we can use shorter syntax and omit keys in order to obtain functions that are simpler to read.
Similarly, the lambda variable responsible for organizing results is explicitly declared.
Lastly, the lambda expression that’s in charge of converting a Player class into a Winner class is refactored:
The expression when is used to remove the nested if-else blocks. The operator in is used to validate the score ranges, resulting in a simpler and more concise code.
For explanatory purposes, the step of replacing numbers for constants has been omitted, but this should always be completed.
2. Immutable Data
We recommend you use immutable objects because they make the code more predictable and reduce side effects. This guarantees that objects won’t get modified in unexpected places and that they’re safer to use in concurrent applications.
Kotlin supports its implementation through data classes, which represent classes that only contain status. This proves to be very advantageous. First, the properties declared in the constructor help to avoid the whole getters and setters boilerplate.
Properties declared with val are immutable, so the Player instance can’t be modified once it’s created. In order to create a new instance from an existing one, we can use the copy function.
It’s worth mentioning that the copy function doesn’t create a deep copy, so if some of the variables aren’t primitive data types, immutability won’t be guaranteed unless they’re used consistently.
After copying, the Player reference will be the same in both Game instances. However, since the Game variables are immutable, immutability is guaranteed.
In this article we have discussed two of the most important good practices for writing a clean code with Kotlin. In sum:
- Small functions or classes make the code easier to understand and maintain.
- The code modules must have one responsibility only. This stems from the SOLID principles of object-oriented programming.
- When it comes to the names of variables, methods, classes and functions, they have to be meaningful in order to enable a fluent reading.
In a next article, we will talk about how to avoid setting variables with null values and how to handle errors to get Clean code. And you, what other clean code practices do you use?