The Clean Code Guide

The Clean Code Guide

Introduction

Writing clean code is a journey. Writing clean code will never be perfect at first; there will continuously be refactoring during the development of our project, no matter what.

In the long-term clean code makes a huge difference in how we add new features, testing, maintainability, etc.

Here are some reasons (or excuses) people write destructive code:

  • Don't have enough time
  • Stress, product team set tight boundaries
  • Rush to get the job done at the time
  • Don't care about clean code. Unless it works, I'm okay with it :(
  • I will clean that later (later equals never, like our TODOs)

The only way to go fast is to keep things clean.

Your house is messy; you are trying to find the keys to your home/car, I bet it happened to you at least once; how much time did you spend finding the keys? one minute? Two? Five? or even a week later?

That's the same with software; if you keep your code clean (or house), you will never need to spend too much time on useless things like trying to understand poorly written code, five minutes here, 10 minutes there; in the end of the day, it adds up in the long term. You could have spent that time on other productive stuff.

The relationship between writing and reading code is different; sometimes, we read much more code than writing, meaning that we should invest more in cleaning our mess to be efficient while reading our/others' code.

"Always leave the campground cleaner than you found it."

Or in other words, always leave the codebase cleaner than you got it.

Functions

Functions should either do or answer something, they must do one thing.

  • Arguments, Arguments, and Arguments!

    A function should have a small number of arguments. I wouldn't necessarily say no arguments are the best, but a function with more than three arguments is questionable and should be avoided.

    We should strive for as few arguments as possible, and no arguments are not ideal.

    As Uncle Bob say: "three is the maximum arguments acceptable."

  • Unused functions (aka "dead functions" ):

    Keeping unused, dead code is useless. Don't be lazy. Clean your code and delete new functions & classes. Don't confuse the person after you.

  • Boolean arguments: When accepting boolean arguments in a function, SRP could easily be violated because the function will probably do more than one thing.

  • Have no "Side Effects" in functions: Side effects in functions are used to describe a situation when you call a function that supposed to do x, but instead it does x + y. let's take for example this simple function:

def check_password(user_id: int, user_password: str): -> bool
  user = users.objects.get(user_id=user_id)
  if not user:
    password_valid = user.check_password(password):
    if password_valid:
      initilaze_session()
      return True

  return False

The side effect here is the call to initilaze_session The check_password, by its name, says it checks the password. The function name does not imply that it initializes the session. The one who will use this function believes that the function only checks for a password, but what he doesn't know about is the session initiation, and if you paid attention, we have also violated SRP. Side effects will continually introduce new bugs.

Comments

It would help if you were careful about what to comment on when writing good comments.

The problem with comments is that they usually tend not to get updated; they can be old, and every time we change our code, we might need to change our "not updated" comment, and that's time-consuming.

Comments usually get irrelevant and incorrect over time as long as we are not changing our code. So instead, people use words to explain the mess they've made.

Writing comments is useful when the code needs further explanation; words should say things that the code can't know for itself or, for example, warn other programmers about a specific function.

Comments should be reserved for technical notes like the code and design; we should choose words carefully.

Make your comment both worth reading & writing.

  • Redundant Comments:

    Redundant comments are the worse; the code tells a story; why should we retell the story again? Why should we rewrite the code in plain English?

 def sum_of_two_numbers(first_number, second_number):
        """
        This function calculates the sum of two numbers.

        Params:
          a: (int) -> the first number
          b: (int) -> the second number
        """
        return first_number + second_number


# Incrementing by one the number of loops:
loops = loops + 1

# returning sum
return sum
  • "Metadata comments":

    You must avoid writing metadata comments; wasting time about writing metadata isn't a great use of our time because a version control system like GitHub is already doing that job for you.

    Metadata comments can be:

    1. authors
    2. last-modified-date
    3. SPR number
    4. or any other information you think is unrelated to the code.

    There's an excellent VScode extension out there called "GitLens." This extension is handy; it lets you see code authorship, last modified, etc.

  • Commented-out code:

    Another type of comment is commented-out code.

    The issue with that comment is that no one deletes it because everyone assumes someone else is using it.

    You should avoid commenting out code at all costs and might consider deleting it; it gets dusty over time.

    Don't worry; GitHub remembers everything in case someone from your team needs it.

  • Noisy comments Functions that don't provide any new information.
# The days in a year
days_in_a_year = 365

# mike's age
mike_age = 32

Classes

Classes need to be small. The class naming convention should always be nouns and not verbs.

The side effect concept is also relevant here and tends to be abused even more with classes.

When dealing with a class, we need to think about what can be broken down into smaller classes & functions to reduce the possibility that one function will break another - isolatIon.

  • Use abstract classes

    A good class will depend on abstractions, not on concrete details. Here's a good article about abstract classes here.

  • OCP (Open Close Principle)

    One of the principles in SOLID, classes should allow adding new functionality by extending the system without modifying the existing code. So, for example, if you want to add a new method/feature, you would add the required code without changing any existing functionality.

  • Cohesion

    Classes should achieve high cohesion by adding methods that affect on many instance variables as possible.

    When methods aren't using many variables, consider creating a new class for them to achieve more cohesion.