What Is Clean Code? A Guide to Principles and Best Practices

In this article:
Subscribe to our blog:

Writing code is like giving a speech. If you use too many big words, you confuse your audience. Define every word, and you end up putting your audience to sleep. 

Similarly, when you write code, you shouldn't just focus on making it work. You should also aim to make it readable, understandable, and maintainable for future readers. To paraphrase software engineer Martin Fowler, "Anybody can write code that a computer can understand. Good programmers write code that humans can understand."

As software developers, understanding how to write clean code that is functional, easy to read, and adheres to best practices helps you create better software consistently.

This article discusses what clean code is and why it's essential and provides principles and best practices for writing clean and maintainable code.

What Is Clean Code?

Clean code is a term used to refer to code that is easy to read, understand, and maintain. It was made popular by Robert Cecil Martin, also known as Uncle Bob, who wrote "Clean Code: A Handbook of Agile Software Craftsmanship" in 2008. In this book, he presented a set of principles and best practices for writing clean code, such as using meaningful names, short functions, clear comments, and consistent formatting.

Ultimately, the goal of clean code is to create software that is not only functional but also readable, maintainable, and efficient throughout its lifecycle. 

Codacy can help you build clean, secure code efficiently and fearlessly.

Why Is Clean Code Important?

When teams adhere to clean code principles, the code base is easier to read and navigate, which makes it faster for developers to get up to speed and start contributing. Here are some reasons why clean code is essential.

  1. Readability and maintenance: Clean code prioritizes clarity, which makes reading, understanding, and modifying code easier. Writing readable code reduces the time required to grasp the code's functionality, leading to faster development times.

  2. Team collaboration: Clear and consistent code facilitates communication and cooperation among team members. By adhering to established coding standards and writing readable code, developers easily understand each other's work and collaborate more effectively.

  3. Debugging and issue resolution: Clean code is designed with clarity and simplicity, making it easier to locate and understand specific sections of the codebase. Clear structure, meaningful variable names, and well-defined functions make it easier to identify and resolve issues.

  4. Improved quality and reliability: Clean code prioritizes following established coding standards and writing well-structured code. This reduces the risk of introducing errors, leading to higher-quality and more reliable software down the line.

Now that we understand why clean code is essential, let's delve into some best practices and principles to help you write clean code.

Principles of Clean Code

Like a beautiful painting needs the right foundation and brushstrokes, well-crafted code requires adherence to specific principles. These principles help developers write code that is clear, concise, and, ultimately, a joy to work with.

Let's dive in.

1. Avoid Hard-Coded Numbers

Use named constants instead of hard-coded values. Write constants with meaningful names that convey their purpose. This improves clarity and makes it easier to modify the code.

Example:

The example below uses the hard-coded number 0.1 to represent a 10% discount. This makes it difficult to understand the meaning of the number (without a comment) and adjust the discount rate if needed in other parts of the function.

Before:

def calculate_discount(price):
discount = price * 0.1 # 10% discount
return price - discount

The improved code replaces the hard-coded number with a named constant TEN_PERCENT_DISCOUNT. The name instantly conveys the meaning of the value, making the code more self-documenting. 

After :

def calculate_discount(price):
  TEN_PERCENT_DISCOUNT = 0.1
  discount = price * TEN_PERCENT_DISCOUNT
  return price - discount

Also, If the discount rate needs to be changed, it only requires modifying the constant declaration, not searching for multiple instances of the hard-coded number.

2. Use Meaningful and Descriptive Names

Choose names for variables, functions, and classes that reflect their purpose and behavior. This makes the code self-documenting and easier to understand without extensive comments. 

As Robert Martin puts it, “A name should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent.”

Example:

If we take the code from the previous example, it uses generic names like "price" and "discount," which leaves their purpose ambiguous. Names like "price" and "discount" could be interpreted differently without context. 

Before:

def calculate_discount(price):
  TEN_PERCENT_DISCOUNT =
0.1
 discount = price * TEN_PERCENT_DISCOUNT
return price - discount

Instead, you can declare the variables to be more descriptive.

After:

def calculate_discount(product_price):
   TEN_PERCENT_DISCOUNT = 0.1
   discount_amount = product_price * TEN_PERCENT_DISCOUNT
   return product_price - discount_amount

This improved code uses specific names like "product_price" and "discount_amount," providing a clearer understanding of what the variables represent and how we use them.

3. Use Comments Sparingly, and When You Do, Make Them Meaningful

You don't need to comment on obvious things. Excessive or unclear comments can clutter the codebase and become outdated, leading to confusion and a messy codebase.

Example:

Before:

def group_users_by_id(user_id):
   # This function groups users by id
   # ... complex logic ...
   # ... more code 

The comment about the function is redundant and adds no value. The function name already states that it groups users by id; there's no need for a comment stating the same.

Instead, use comments to convey the "why" behind specific actions or explain behaviors.

After:

def group_users_by_id(user_id):
   """Groups users by id to a specific category (1-9).

   Warning: Certain characters might not be handled correctly.
   Please refer to the documentation for supported formats.

   Args:
       user_id (str): The user id to be grouped.

   Returns:
       int: The category number (1-9) corresponding to the user id.

   Raises:
       ValueError: If the user id is invalid or unsupported.
   """
   # ... complex logic ...
   # ... more code 

This comment provides meaningful information about the function's behavior and explains unusual behavior and potential pitfalls.

Exploring the State of Software Quality 2024 Report

4. Write Short Functions That Only Do One Thing

Follow the single responsibility principle (SRP), which means that a function should have one purpose and perform it effectively. Functions are more understandable, readable, and maintainable if they only have one job. It also makes testing them very easy. 

If a function becomes too long or complex, consider breaking it into smaller, more manageable functions.

Example:

Before:

def process_data(data):
   # ... validate users...
   # ... calculate values ...
   # ... format output 

This function performs three tasks: validating users, calculating values, and formatting output. If any of these steps fail, the entire function fails, making debugging a complex issue. If we also need to change the logic of one of the tasks, we risk introducing unintended side effects in another task.

Instead, try to assign each task a function that does just one thing. 

After:

def validate_user(data):
   # ... data validation logic ...
def calculate_values(data):
   # ... calculation logic based on validated data ...
def format_output(data):
   # ... format results for display 

The improved code separates the tasks into distinct functions. This results in more readable, maintainable, and testable code. Also, If a change needs to be made, it will be easier to identify and modify the specific function responsible for the desired functionality. 

5. Follow the DRY (Don't Repeat Yourself) Principle and Avoid Duplicating Code or Logic

Avoid writing the same code more than once. Instead, reuse your code using functions, classes, modules, libraries, or other abstractions. This makes your code more efficient, consistent, and maintainable. It also reduces the risk of errors and bugs as you only need to modify your code in one place if you need to change or update it.

Example:

Before:

def calculate_book_price(quantity, price):
  return quantity * price
def calculate_laptop_price(quantity, price):
  return quantity * price

In the above example, both functions calculate the total price using the same formula. This violates the DRY principle.

We can fix this by defining a single calculate_product_price function that we use for books and laptops. This reduces code duplication and helps improve the maintenance of the codebase. 

After:

def calculate_product_price(product_quantity, product_price):
 return product_quantity * product_price

6. Follow Established Code-Writing Standards

Know your programming language's conventions in terms of spacing, comments, and naming. Most programming languages have community-accepted coding standards and style guides, for example, PEP 8 for Python and Google JavaScript Style Guide for JavaScript. 

Here are some specific examples:

  • Java:
    • Use camelCase for variable, function, and class names.
    • Indent code with four spaces.
    • Put opening braces on the same line.
  • Python:
    • Use snake_case for variable, function, and class names.
    • Use spaces over tabs for indentation.
    • Put opening braces on the same line as the function or class declaration.
  • JavaScript:
    • Use camelCase for variable and function names.
    • Use snake_case for object properties.
    • Indent code with two spaces.
    • Put opening braces on the same line as the function or class declaration.

Also, consider extending some of these standards by creating internal coding rules for your organization. This can contain information on creating and naming folders or describing function names within your organization.

7. Encapsulate Nested Conditionals into Functions

One way to improve the readability and clarity of functions is to encapsulate nested if/else statements into other functions. Encapsulating such logic into a function with a descriptive name clarifies its purpose and simplifies code comprehension. In some cases, it also makes it easier to reuse, modify, and test the logic without affecting the rest of the function.

In the code sample below, the discount logic is nested within the calculate_product_discount function, making it difficult to understand at a glance.

Example:

Before:

def calculate_product_discount(product_price):
 discount_amount0
 if product_price > 100:
   discount_amount = product_price * 0.1
 elif price > 50:
   discount_amount = product_price * 0.05
 else:
   discount_amount0
 final_product_price = product_price - discount_amount
 return final_product_price

We can clean this code up by separating the nested if/else condition that calculates discount logic into another function called get_discount_rate and then calling the get_discount_rate in the calculate_product_discount function. This makes it easier to read at a glance. 

The get_discount_rate is now isolated and can be reused by other functions in the codebase. It’s also easier to change, test, and debug it without affecting the calculate_discount function.

After:

def calculate_discount(product_price):
  discount_rate = get_discount_rate(product_price)
  discount_amount = product_price * discount_rate

  final_product_price = product_price - discount_amount
   return final_product_price

def get_discount_rate(product_price):
 if product_price > 100:
   return 0.1
 elif product_price > 50:
   return 0.05
 else:
   return 0

8. Refactor Continuously

Regularly review and refactor your code to improve its structure, readability, and maintainability. Consider the readability of your code for the next person who will work on it, and always leave the codebase cleaner than you found it.

9. Use Version Control

Version control systems meticulously track every change made to your codebase, enabling you to understand the evolution of your code and revert to previous versions if needed. This creates a safety net for code refactoring and prevents accidental deletions or overwrites.

Use version control systems like GitHub, GitLab, and Bitbucket to track changes to your codebase and collaborate effectively with others.

Write Clean Code with Confidence Using Codacy

Writing clean code is not just about following a set of rules but also a mindset and a commitment to writing high-quality software that is easy to read and maintain. If you're looking for a tool that automates keeping your code clean and secure as you write it, check out Codacy.

Codacy goes beyond simply enforcing coding style guidelines. It provides a comprehensive suite of features that help you write better code, including:

  • Static code analysis to detect potential bugs, security vulnerabilities, and code smells early in development

  • Duplication detection to identify and eliminate duplicated code, making your codebase cleaner and more maintainable

  • Code metrics to provide quantitative data about your code, helping you understand the quality and maintainability of your codebase

Finally, with support for GitHub, GitLab, and Bitbucket, Codacy can integrate seamlessly with your existing development workflow. Try it out today.

RELATED
BLOG POSTS

A Deep Dive Into Clean Code Principles
Consistent coding practices become crucial as software projects grow in complexity, involving multiple contributors and moving parts. Clean code...
How To Write Clean Code
When you write code, you shouldn't just focus on making it work. You should also aim to make it readable, understandable, and maintainable for future...
Code Quality Explained
Ask 10 developers what code quality means, and you’ll get 15 different answers.

Automate code
reviews on your commits and pull request

Group 13