Clean Code Series – Part 3: Clean, Readable Code

Posted by

The first 2 articles in this series were about Object Oriented Programming.
Clean Code Series: OOP Part 1
Clean Code Series: OOP Part 2

Now, on to Part 3, about Readable Code.

 

 

 

 

 

 

  • Introduction
    • What is readable code?
    • Why is it required?
  • Principles of Readable Code
    • Naming
    • Keep It Simple, Stupid
    • Refactoring to keep it clean
    • Well structured
  • Naming of Variables, Methods, Classes, etc.
  • Keep It Simple, Stupid
  • Continuous refactoring of code is necessary to keep the code clean and readable
  • Structuring code is important to not just keeping it clean, but also for making it extensible
  • Summary

Clean, Readable Code

Before we even start discussing or debating what clean or readable code is, I want to first acknowledge the fact that clean or readable code is very subjective, and the definition varies from person to person. There is no one right answer to the question – what is clean code? There are, however, industry standards that are widely preached and accepted by a large majority of engineers. We are going to talk about such practices, with a little bit of ranting sprinkled in between that originates from personal experience.

So, let’s start with trying to understand what clean, readable code is. As I already mentioned, there is no one right answer or opinion to this. My definition of clean code might be different from what you define clean code as. But we can agree on one thing – the code we write should be easy to understand and easy to modify. And when I say the code should be easy to understand, what I really mean is that it should be easy to understand not just for me, but primarily for engineers who come after me to either maintain it or to add new functionality to it. 

But it’s not as easy as it sounds. For example, consider the code snippet below:

int c = a + b;

It’s a pretty simple piece of code, I’m just adding the values of two variables and storing the result in another. Wouldn’t you say this is a piece of clean code? Well, not for me. Even though it is easy to read and understand, I would argue that this isn’t clean or readable. That’s because of the fact that by looking at this code, I don’t understand what the variables a, b, and c are.

There are a few principles that collectively make up clean code. The example I just gave is one such principle, naming your variables correctly. Let’s list out these principles that help in making code clean.

Principles of Clean Code

The following few principles don’t just help in making your code clean, but also make it easier for others and yourself to extend the code in the future.

  • Naming of variables, classes, methods, functions, etc. – Naming your variables, classes, and methods make a huge difference in the ability to read the code.
  • Keep It Simple, Stupid – Popularly known as the KISS principle, it helps in keeping the code as simple as possible so that it becomes more modular and maintainable.
  • Refactoring – Refactoring of code has to be a constant effort. The more often you refactor your growing code base, the cleaner it will be.
  • Well structured – Structuring a code is no easy task. It takes a lot of effort and experience to be able to structure your classes and methods in a way that’s more meaningful and extensible.

These principles look pretty straightforward, similar to how they intend the code to be. But in reality, it’s not as easy to follow these principles. At least till you get used to this practice of writing clean code, until it becomes your second nature, you have to fight the constant battle with yourself of writing easy code vs. writing clean code. But trust me, with time, clean code becomes so easy that it just becomes common sense. All it takes is conscious, deliberate effort in the beginning.

Now that we listed the principles, let’s go through each of them in a bit more detail with examples to understand them better.

Naming Variables, Classes, Methods, and Other Things

I think I already made this point clear with the code snippet in the previous section. Naming your variables right saves a lot of time when you come back to a piece of code to either update it or fix a bug. Also, it’s just easier for anybody else going through your code to understand what you’re trying to do.

I have seen, in my experience, very well named variables, and also some of the worst. If you don’t want to name your variables or methods with some meaningful names, you might as well use memory addresses directly in an assembly language, right? I know that it’s not easy to come up with meaningful names for all variables as and when you write the code. So what I do when I face such situations is, I give the variable a name that’s not too bad, and I’ll create a @ToDo for renaming that variable. With modern IDEs and text editors making it so easy to rename things in your code, this should be an easy workaround if you want to come back to a variable and rename it.

As a thumb rule, make sure your variables, classes, methods, functions, and anything else that can be identified with a name, are named in such a way that they don’t need a comment to explain what they are. At the same time, don’t overdo the names. I remember this one instance when an intern I was mentoring got too excited about naming variables descriptively that she wrote this:

String currentDateInYYYYMMDDFormat;

I appreciate the effort, but this is taking it too far. Just currentDate is perfect. In cases where you have to declare variables in various formats, use descriptive variable names so that the reader understands what format you’re using. For example, the date format in the US is YYYY-MM-DD, and in India we use the format DD-MM-YYYY. If you need to use both of these in your code, you can declare your variables something similar to the following snippet:

String usDateFormat = "YYYY-MM-DD";
String indiaDateFormat = "DD-MM-YYYY";
String currentDateInUsFormat;
String currentDateInIndianFormat;

This would immediately make it obvious what the variable holds and also what the formats of the dates are.

Also, if you’re using constants in your code, don’t hard code them, but instead declare constant variables, with proper names of course. For example:

if (numberOfDays < 7) {
            return 'Something';
}

Here, the value 7 is hardcoded in the if() condition. This might not sound dangerous, but it will become dangerous if left unchecked. The best way to handle this is of course to create a constant variable and use that instead, like this:

if (numberOfDays < NUMBER_OF_DAYS_IN_A_WEEK) {
           return 'Something';
}

I hope this gives a good understanding of how to get started with naming your variables better. Now let’s look at how you can keep your code stupidly simple.

Keep It Simple, Stupid

If you have read any book on design principles, you’d know by now that the phrase “keep it simple, stupid” was first coined by the late Kelly Johnson when he was the lead engineer at Lockheed Skunk Works. The story behind this is that he told his team of engineers at Lockheed that their designs should be so simple that any person in a combat situation should be able to repair it with basic mechanics training and some basic tools. 

Albert Einstein once said, “if you can’t explain it, you don’t understand it well enough.” And for you to understand anything, it has to be simple. Once you understand something, you can write it very simply. Similar to having meaningful names for your variables, writing simple code takes a lot of practice. The sooner you start giving attention to the code you write, the sooner you’ll become good at it.

When you are deep in the weeds trying to fix a bug or trying to implement a new feature, the last thing you want to worry about is understanding horribly written code. If the code is clean and simple, you can get in, finish your business, and get out quickly. Isn’t that a dream? 

Wherever possible, break down your code into multiple methods, even if it means you need a bunch of methods to do a simple insert into a database, for example. And make sure each method is no more than 5-8 lines long. And if 5-8 lines is too restrictive for your method of your programming language of choice, another popular method to make sure your methods are short is to fit them in your viewport. This means you don’t have to scroll to see one complete method on your screen. 

You can already get an idea of how difficult writing such code is. We are naturally good at writing non-modular code. We can very quickly write a function with 500 lines in it and ship the feature. But if you come back to that function only a month back to fix a bug (which will obviously be there), you will be scared to change even one line, because the function is pretty complex.

If you break that function down to say 50 methods with 10 lines each which are logically structured, imagine how easy it would be to quickly figure out where the bug is and fix it. Also, it gives the added benefit of keeping your code modular and reusable.

Keeping your code simple and stupid is easier said than done, and it is definitely more than just breaking larger functions into smaller ones. But that is a good place to get started. 

Refactoring

I’m sure of one thing, we all have refactored our code at least once, for whatever reason. In the beginning of my career, I never liked going back to a code to just refactor it, without adding any new features. I never understood why I needed to do it. But my seniors and mentors didn’t care that I wasn’t happy refactoring code.

A few years later, when I started peer reviewing code and then became a mentor myself, I quickly understood why refactoring is so important. When we write a piece of code during the implementation of a new feature or while fixing a bug, we are usually in a race against the clock. It shouldn’t be this way at all, but that’s the case more often than not. 

It goes without saying that in such situations we don’t usually write the best code. So it’s a good practice to come back to our code whenever we have time and refactor it. I picked up this habit early on in my career, and I still do it today. It has kind of become an OCD now. And yes, I don’t advise anybody to develop this as an OCD. For me, it gives joy to refactor my code endlessly, because there is just no end to how simple you can make your code.

Even though this has to be a constant and deliberate effort, there are many advantages of doing this. To begin with, if you keep refactoring your code often, no matter when a new engineer comes in to extend your code, they’ll be happy and thankful for keeping the code so clean and easily understandable. Not just others, but even if you come back in a few months to extend your own code, trust me, you’ll bless your past self for being kind to your present self. 

But you might have a question here. If you keep refactoring your code regularly, don’t you have to keep testing your application regularly too? That sounds unnecessarily tedious. Well, the simple answer to this is writing unit tests and integration tests. That’s another indicator of a well written code, that it comes with unit test cases. So even when you are refactoring your code, if all the tests still result in a green, you are good to go. I don’t want to get into how important writing test cases is, but you get the point – they are important, so write them!

Well-Structured Code

This one should be an easy one. Structured code is just easier to work with and extend in the future. But similar to other principles mentioned here, structuring your code well requires patience, constant and deliberate effort. There are two popular approaches to writing structured code. One is where you write all the code you have to write in one single function, and then start giving it a structure. The other approach is where you first write all the stubs or empty methods you’d need for the feature you are going to code, and then start filling in those empty methods.

I started with the first approach, mostly because it was easy for me to begin the practice with. I used to write all code in one function, test it out manually, and then break it down into smaller chunks. This is a time consuming approach, because you have to spend a lot of time testing for each piece of code that you extract into a method. You will end up testing the complete feature repeatedly.

Ever since I switched to the second approach, the time I take to develop a feature has come down drastically. This is because when I’m thinking about how to code a feature, at a high level, I’ll get an idea of the smaller functional chunks that I’ll have to write. These small functional chunks will translate to methods. So what I do is I write the main method first, then call all these smaller chunks, and finally start coding. For example, when I want to authenticate a user, the sequence of events that I’ll have to follow (depending on the auth process, of course) are:

  1. Compare the username and password provided with what I have in the DB.
  2. Create a token if the auth is successful. This will most probably be a JWT token.
  3. Create a session object for the login – session is not the traditional session you’re thinking of. This is a session associated with the token for analytical and debugging purposes. This is not a common practice.
  4. Return a response to the client.

Now that I know I’ll be performing four different operations for this authentication, I’ll first write the following code:

This, again, is not perfect. But you can understand where I’m going with this.Once I have this flow and the structure setup, and once I’m satisfied with this, only then I’ll start writing the actual logic for these methods.

And actually, if you follow test driven development (which you should!), you’ll be first writing tests for these methods even before you define these methods. Your unit tests should drive the design and the structure of your code.

The benefits of structuring your code are obvious at this point. To name a couple of them, you’ll thank yourself when you have to debug the code to find out where a bug is. Second, you can imagine how easy it is to extend this code. If you want to add another step to the authentication process from the example above, you just slot in another method in the flow and you write only that method. You don’t have to worry about any other part of the code or wonder how any of it works. 

Clean Your Code Right Now

If you don’t have the practice of writing clean code, I suggest you start right away. There are numerous books on this subject, I also suggest you read at least a couple of them to get more ideas on how you can clean your code.

There’s never a better time than the present to refactor your code to clean it. But if you are starting with the practice now, start doing it on your personal projects where nobody is affected even if you mess it up. Once you are confident, you can adopt the practice for your production code as well. 

And of course, this is by no means the end of it. This post is a collection of only four ways in which you can write clean code. There’s a lot more, and I’m sure you’ll discover them all in your journey to clean, readable code. And to reiterate, this post is just a reflection of my thoughts on clean code. Your opinion on this subject could be entirely different, or might be an extension of the thoughts I presented here. No matter what the case may be, be sure to voice it out in the comments section.

References

  1. Clean Code: A Handbook of Agile Software Craftsmanship, by Robert C. Martin
  2. Code Complete, by Steve McConnell
  3. The Pragmatic Programmer: From Journeyman to Master, by Andrew Hunt and David Thomas

Liked this article and want to test or practice your clean code skills? Geektrust’s coding challenges are designed to test your clean code skills. You can pick any one challenge, solve it and submit to get evaluated, improve or find jobs at the best companies. More resources can be found in our blog or the coding help docs here.

Leave a Reply

Your email address will not be published.