The Good, the Bad, and the Ugly - a thing about writing clean code

What is Clean Code and why should you care? 

Initially, we should explain what this clean code is about. It’s not an easy task, because this term is very subjective. Generally speaking, this means that the code is written in such a way that it allows producing readable, reusable, and refactorable software, which is the dream of every developer, project manager, and CTO. The whole concept is best illustrated by this popular image:
 

Clean code

The advantages of clean code

There are many benefits of writing clean code. Let’s start with bugs, which are inevitable in software development, regardless of programming skills and type of app we’re working on. Writing clean code results in making fewer mistakes. How is that achievable? It’s safe to say that the clean code is always less complicated. For this reason, it’s easier to perform tests that allow to eliminate potential errors on an ongoing basis. Additionally, code is clearer to read during code review which allows catching more errors.

Writing code is easy, but reading it is hard - this saying seems to be true in terms of messy and ambiguous code. When this happens, developers often choose to rewrite some parts of the app instead of interacting with existing functionality. That should be avoided at all costs as it’s a very inefficient strategy. When developers, for some reason, produce a spaghetti code (BTW, another great reference to our western metaphor!), the ugly one, and not change a single line because it’s working, then we can be sure that sooner or later it will come back with its ugly consequences. And it’s usually sooner than later as maintaining and developing apps require continuous programming work, especially when they turn out to be successful and require effective scaling up.

How to scale digital products in terms of dynamic growth? Read our guide

So, we return to the project and find the neglects made and left to save some time and we waste much more time understanding previous work, trying to connect all the dots. It usually ends with giving up on the existing version and writing exactly the same functionality from scratch. Now, a seemingly simple change that was supposed to take two hours consumed two days instead. If we followed the clean code rules, it would probably needed some time to understand the previous work, but I bet it would be a matter of minutes, not hours. Summing up - clean code facilitates making changes in the digital products, refactoring and adding new features. 

Another advantage of writing clean code is a simpler onboarding of new team members. When you hire a new developer to work on the project or outsource it to the external team, you definitely don’t want to spend every minute to explain the code line by line. Of course, at the beginning little help will be unavoidable, but what do you think will take more time - getting along with messy or the clean code? 

The obstacles in writing clean code

Writing clean code is without a doubt a challenging task, even though it’s definitely worth effort. It’s just hard and requires more time and work. But keep in mind that overall paying more attention at the beginning is more efficient and less annoying than correcting a sloppy code.

Another difficulty is related to a very common issue of having too much work and not enough time at once. It especially concerns startups that need to quickly release at least the MVP of their apps to validate their idea with first users and gain more funds for further development of the product. Many different features to implement, pressure, deadlines - all this can lead us to think about releasing low-quality code which can be written faster. Don't be deceived - it’s a vicious circle.

Last but not least obstacle is the We’ll refactor it later problem. I hope you’ve never thought about it, but I know the reality and how hard it can be. A lot of people leave refactoring until later when there’s more time or there are no other important topics.

Check if your codebase requires refactoring!

Avoid that way of thinking. Remember that coming back to previous work and refactoring, is an uphill battle and in most cases, it simply doesn’t work. There will always be some bigger or more interesting tasks. Care about the code right from the first line.

How to write clean code? 

Now that we know what is clean code and why you should care about it, let’s have some practice. Here’re some of the principles of creating it with the examples comparing the ugly and the good code. 

Using meaningful names

It’s probably one of the most important issues. The names should provide information for what they are used for. They also should be searchable in order to not waste time while working with the code.

// The Ugly - what is d???
const d = new Date();
// The Good - self-explanatory variable name
const currentDate = new Date();// The Ugly - what is 5? May I change it?
if (numOfDays < 5)
// The Good
const NUMBER_OF_WORKING_DAYS = 5;
if (numOfDays < NUMBER_OF_WORKING_DAYS)

Explicit is better than implicit

This sentence from The Zen of Python should be always kept in mind regardless of the programming language. The unambiguous instructions inside the code force to stop working and guess what was supposed to happen at the current line.

// The Ugly
const movieTitles = ['Les Miserables', 'The Good, The Bad and The Ugly', 'West Side Story']
movieTitles.map(t => {
firstFunction();
secondFunction();
// …
// …
myFunction(t); // wait, what's t? Let me check...
});// The Good
const movieTitles = ['Les Miserables', 'The Good, The Bad and The Ugly', 'West Side Story']
movieTitles.map(title => {
firstFunction();
secondFunction();
// …
// …
myFunction(title); // Ah okay, I'm using title here
});

Cutting the number of comments

Comments can definitely be a blessing. As well as a curse. The problem lies in their amount and the reason why they were made. The good practice is to add comments only when it’s absolutely necessary e.g. when they concern business logic complexity. So it’s crucial to avoid redundant clarifications. Clean code should be self-documenting.
 

// The Ugly
let initCount = 0; // initial counter value
initCount = initCount + 1; // increment initial counter// The Good - no comments needed!
let counter = 0;
counter = incrementByOne(counter);

Functions should do one thing

It’s not difficult to make a function unreadable - allowing it to do more than one thing is enough to achieve that. If the function needs to complete task A, then task B and later use the previous result to do task C, then you can simply extract three different functions from it! Having small and simple functions is completely fine and ensures that the code is maintainable and refactorable.

// The Ugly
function notifyAdults(users) {
users.forEach(user => {
if (user.age > 18) {
notify(user);
}
}
}// The Good
const AGE_THRESHOLD = 18;

function notifyAdultsUsers(users) {
users.filter(isAdultUser).forEach(notify);
}

function isAdultUser(user) {
return user.age > AGE_THRESHOLD;
}

Removing duplicated code

Most of the duplications make code harder to maintain and work with. Imagine that there’s a need for changing some logic in the software and the code is repeated in three different files. It simply means that the functionality has to be changed in three different places. As you can guess, this isn’t the most efficient way. If it’s possible, it’s better to create an abstraction to solve the problem. Look at the examples below. In the first one, we can see that changing the key role to position entails the necessity of modifying every function. However, it’s different in the second case. The change is made only once. I’m sure you can feel the difference.

// The Ugly
const employees = [{ name: 'John', role: 'Developer'}, ...];

function findDeveloper() {
employees.forEach(person => {
if (person.role === 'Developer') {
....
}
}
}

function findDesigner() {
employees.forEach(person => {
if (person.role === Designer) {
....
}
}
}// The Good
const employees = [{ name: 'John', role: 'Developer'}, ...];

function findEmployee(employeeType) {
employees.forEach(person => {
if (person.role === employeeType) {
....
}
}
}

findEmployee('Developer');
findEmployee('Designer');

On the other hand, there can be situations, when duplication is an acceptable solution because it’s going to be easier to maintain. You or your developers have to decide, what will be a better solution in the project - duplicating one line of code or instantiating a new class/creating a new component.

Avoiding multiple function arguments

The fewer arguments the function has, the better. Why? Because then it’s easier to test. Most of the time, if the function has more than two arguments, then probably it does too much. But if there’s a need for using more arguments, providing a higher-level object as an argument will solve the problem.

// The Ugly
function createBook(title, author, genre, price, publishDate) {
....
}

createBook('Pride and Prejudice', 'Jane Austen', 'Romance novel', 29.99, '28.01.1813');// The Good
function createBook({ title, author, genre, price, publishDate }) {
....
}

createBook({
title: 'Pride and prejudice',
author: 'Jane Austen',
genre: 'Romance novel',
price: '29.99',
publishDate: '28.01.1813',
});

Avoiding unneeded context

Extra information can be confusing and misleading. So it’s better to avoid adding additional context if it’s not needed and not repeating the names of objects or classes. 

// The Ugly
const User = {
userName: "Clint",
userAge: 89,
isUserActive: true,
}

function deactivateUser(user) {
user.isUserActive = false
}// The Good
const User = {
name: "Clint",
age: 89,
isActive: true,
}

function deactivateUser(user) {
user.isActive = false
}

Avoiding negative conditionals

Generally speaking, negative conditionals are harder to read. It’s just an unnecessary layer of abstraction. In most cases, it’s possible to omit the negation.

// The Ugly - double negative
if (!isDisabled) // hmm so it means that it's enabled or what?// The Good
if (isEnabled) // what a clear statement!

Mind the errors

This may sound trivial and obvious but is often forgotten. Caught errors should be handled properly. And I don’t think only about omitting them but also about logging them to the console. It doesn’t give the opportunity to fix them or change the behavior of the application. It’s worth trying wrapping fragments of code which we’re not certain in a try/catch statement. This will also mean that it’s a piece of code that should be given more attention.

// The Ugly
try {
dangerousFunctionThatMightProduceAnError();
} catch (error) {
console.log(error);
}// The Good
try {
dangerousFunctionThatMightProduceAnError();
} catch (error) {
console.error(error);
handleErrorProperly();
callThePolice();
}

Let your code be the good one 

Writing clean code is not an easy task. It’s time-consuming and requires increased effort from developers. But at the end of the day, it pays off. I hope that after reading this article you think the same way, or at least start thinking about what you can improve to elevate the quality of code. This will result in making the app work smoothly, preparing it for scaling up, and handing over a project to another team if it will be needed. Feel free to dive into the concepts of clean code. I’ve just picked a few of them, the list is much longer. If you’re looking for a valuable source, you should definitely check a fantastic book Clean Code by a well-known Robert C. Martin. 

Looking for experienced developers who can provide you with clean and quality code? Check how they help you in creating or developing digital products!
 

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .