The subtle art of Clean Code

Clean Code

There are two ways to write an application – write logic using code or write logic using clean code. Coding is what every fresh graduate is doing. But if you want to become a better programmer then it’s all about writing clean code.

Writing clean code should be any developer’s fundamental goal. But writing clean code is more of an art than anything else.

Characteristics of Clean Code

  1. The code must have some elegance to it. The look and feel of the code should be clean. The code must not look rushed. 
  2. The code must be concise and focused. Every line of code (LOC) should be single-minded and undistracted. Every module, class and function should have a reason for its existence.
  3. High readability of the code does not mean that it’s understandable. The code must be self-documenting in nature. This results in a better understandability of the code. As a result, the next developer will have an easier time working with it and making changes.
  4. The code and its test cases go hand in hand. That’s why any further change must not break the test cases’ assertions. This is known as test-driven development.
  5. The code must follow a predefined set of rules or standards for consistency. Even if many developers are working on the same codebase, the code must remain consistent. This results in a uniform and clean looking code.
  6. The code is like a baby. You need to invest time and care when writing the code. Its hard work.

Code Structure

The first thing that’s come to mind when trying to write elegant and clean code is the code structure. So, The code structure will be the first problem we’ll try to solve when starting with a new project.

First and foremost task before starting to write even a single line of code is to think about your application. It’s components, modules, entities must be clearly defined. And, have a clear understanding of what each component’s role and responsibilities are? Keep in mind that the more you plan before you write the easier it will be to keep writing the code without refactoring. The general rule of thumb here is to “Think twice write once“.

Now, once we figure out what is it that we are trying to solve and what are all the components in the system. The next step is to add packages or modules. These packages will encapsulate the components in a nice little wrapper. Only add the classes that belong in that particular module. There must be coherence between all the classes in a single module.

Composition vs Inheritance

It is usually a good practice to favour composition over inheritance. But this is not always true and one should not blindly follow the composition-first approach.

The questions you should be asking is “Is typeB a typeA?” or “Does typeB has a typeA?”. Now, If the answer to the above questions is “typeB is a typeA”. Or in other words, typeB is a superset of typeA. Then go ahead and use inheritance. But if the answer is “typeB has a typeA” then the solution is to use composition.

Let’s elaborate on the above with an example. Say if we are trying to build an application with dogs then a class called labradoodle should extend base class Dog. In this case, the composition will not solve the problem correctly. But adding Flyable feature via composition route to a bird’s descendent class can be a good practice. After all, not all birds can fly.

Naming Conventions

Everyone thinks that the names they have assigned to identifiers are obvious. And everyone knows about the names. But it’s rarely true.

While naming any identifier, think about their intent. The names must be clear and must tell the story or intent behind it. Let’s say you want to give a name to an identifier that will hold the number of days the process took.

int daysConsumed;

Above is much better when defining the intent behind the identifier then below:

int days;

On the same grounds, if you have a function or method that handles click of a button then a good name for the function is:

void handleButtonClick() {
    ...
}

Which is a much better choice then,

void handle() {
    ...
}

I have seen many experienced developers using Hungarian notation. But I would suggest staying away from it. And, on the same ground don’t add “m” prefix on all the instance variables.

long lLargeNumber;

or

long mLargeNumber;

should give way to,

long largeNumber;

Giving proper names to identifiers is the first step towards making self-documenting code. Similarly, You can extract methods for better code readability and understandability. Say you have below code.

// check if the person is a teenager.
if (person.getAge() >= 13 && person.getAge() <= 19) {
    ...
}

It’s better and easier to refactor this code into something like below.

if (person.isTeenAger()) {
    ...
}

There are three benefits attached to the suggested change:

  1. It’ll make the code self-documenting. You can just go ahead and remove the comment.
  2. The code is much more understandable. A quick glance can tell the exact intent behind the condition.
  3. Lastly, It decouples the logic and the condition.

Comments

From the previous section, it’s clear that self-documenting code is the way to go. But you cannot always skip adding comments. Comments are an integral part of the code.

Well commented code is very well glued together. It also saves developers from known gotchas and pitfalls. Like the structure and naming convention, we must adhere with commenting principles.

  1. Always add comments to tricky methods or any non-standard implementation. Even if you think that complex code is self-explanatory, add comments.
  2. I have seen many developers love to add random numbers in their statements if (persons.size() == 10) {...} . It’s good to add a comment on the magic number if you don’t want to extract constants.
  3. Don’t add obvious comments.  if (person.equals(nextPerson)) // checking for equality . This is an obvious comment. Other than ruining the flow of code and adding not needed lines, the comment does not serve any purpose.
  4. Comments are always preferred and welcomed when there are warnings or known gotchas. 

Code Duplicacy

One of the basic principles of writing clean code is to avoid duplicate code. If you see any code that’s repeating again, extract that method out. Most of the modern IDEs give at least a way to refactor or extract methods very easily.

Sometimes the repeating code is spread across multiple classes. In that case, either the functionality can be passed as composition or it should be extracted out to an abstract base class. And if the code does not directly depend on the class, the functionality should be extracted out into a utility class.

Keeping the classes clean and without the duplicated code does wonders and increase the maintainability by many folds.

Hardcoded Values

The best suggestion I can give you for hard-coded values is “Don’t do it”. It’s very easy to add hard-coded values in the code. But it almost always results in hard to maintain code and can lead to unexpected bugs.

But if it’s still required to hard-code some values, the best approach could be to extract the values in externalised config files. This way you can update the values without code changes. And, all hard-coded values are available at one place so debugging will be easier.

Logging

Good logging is the key to better debugging. So we must add logs where we expect bugs creeping in. Generally, it’s a good practice to add logs as you go.

But if you don’t have good logging in place, I suggest adding logs during initial dev and QA testing. When you encounter a bug, add logs at the required places. It may not cover all the scenarios but it’ll be a good start for better logging.

Just remember not to leak any user data in the log streams as it can result in all kinds of business/legal risks. It is also a very common mistake a lot of fresh developers do.

More miscellaneous points

  1. Use dependency injection where-ever possible. It will increase the testability of the code by many folds.
  2. Don’t break the indentation. It will increase the code readability.
  3. Don’t write huge methods. A good rule of thumb is to keep the method’s LOC to 10 or less.
  4. Keep multi-thread and sequential code separate.
  5. Try to minimise the entities in the system.
  6. While maintaining the code, always find the root cause of the problem and don’t go for the quick and dirty fix.
  7. You can improve poorly written code by following a simple rule – “Always leave the campground cleaner than you found it“.

Before ending this very long post, I would like to add that these are the few very important rules to keep the code clean. These rules are in no way forms the complete set but give a good start to keep the code clean. Clean code is about your resolve to follow the principles and your hard work in keeping the code clean. For further reading, I suggest the famous book on the same topic by Robert Cecil Martin (“uncle bob”).

Leave a Reply

Your email address will not be published. Required fields are marked *