What is Clean Code?

What is Clean Code?

Tips from Uncle Bob

·

8 min read

Robert C. Martin, often called Uncle Bob, is an American software engineer well known for his books on Clean Code and Clean Architecture.

This article is the first of a series that I am writing as an introduction to Clean Code. It’s made of notes that I wrote while watching a series of video lessons that Robert gave about clean code. This one will focus on the first episode, which is an introduction to Clean Code through a few concepts that are important to know as a software engineer.

You can find the 6 episodes' Youtube playlist here.

Quotes from his friends

Here are some quotes about Clean Code that Robert’s friends gave to him.

Bjarne Stroustrup (C++):

  • I like my code to be elegant and efficient”.

  • Clean code does one thing well

Grady Booch (Object Oriented Software Engineering, UML, …):

  • Clean code is simple and direct. Clean code reads like well-written prose…

Uncle Bob (Clean Code, Agile Software Development, …):

  • You must write code that other people that maintain. If you hand me code that works perfectly but I can’t understand it, as soon as the requirements changes, that code is useless. But if you give me code that doesn’t work but I can understand it, I can make it work.

  • It's much more important to write code that your peers can understand, than that the computer can understand

Ward Cunningham (Wiki concept inventor):

  • You know you are working on clean code when each routine you read turns out to be pretty much what you expected...

Why is Clean Code important?

Society works with software.

In our society today, it’s impossible to go more than 60 seconds without interacting with a software system: you can’t use a phone, watch TV, wash your clothes, you can’t buy or sell anything, …

Even your car can’t work without software: GPS, entertainment system, engine, and brakes. When you press on the breaks, there is code.

And who writes that code, that’s going to decide whether or not to stop the car when you push on the break? We write that code.

Why are programmers so slow?

Software teams want to go fast:

  • They write a lot of features fast at a beginning of a new project, so they make a mess. And as the mess grows they get slower.

  • So they need to hire more people to keep going fast. The old code teaches the new people, which continues to make a mess.

  • We are slow because we make a mess.

→ To work fast, work well. You are not done when the code works, you are done when it’s clean! The only way to go fast is to go well.

Function names

You don’t want nouns as function names. Function names should be verbs because functions do things.

Functions names should explain clearly what it’s doing.

→ Read more on my article on methods and variable naming.

Polite code: don’t be rude!

You have to care about developers that will read your code. So you have to make your code easy to read and understand. This is polite.

The code should read like an article

When reading the first methods names of a class (for instance the public methods) and their few lines of code, you should be able to understand in a few seconds what this code is about.

Then if you’re interested and want to go deeper into the implementation, you can continue with the next methods (for instance the private ones). You should not have to read all the code to understand what a class is doing.

→ Start with short high-level code. Code’s level is lower as you dive deeper into it.

It allows the reader to exit early.

The good size for a function

The good size for a function is when you can’t extract any other function from it.
This can be a bit extreme but the principle is still the same: a function should read easily.

So if when reading you alternate between high-level code and low-level code (like doing complex manipulations on a string and then calling a global method with this string), there is probably code that you should extract.

// high level code: publishing an article
func publishArticle(rawText: String) {
    let markdown = self.convertToMarkdown(rawText: rawText)
    self.uploadMarkdownText(markdown: markdown)
}

private func convertToMarkdown(rawText: String) -> MarkDownObject {
    // low level code: text conversion
}

private func uploadMarkdownText(markdown: markdown) {
    // low level code: network call
}

→ Every line of code in a method should be on the same level of abstraction, and that level should start right below the function name.

Explanatory variables

This is the same principle about code readability but in a deeper scope. Some code can contain complex statements that should be extracted into variables.

Reading those variable names may help the reader to understand the code faster.

// complexe if statemment
if user.isAdmin || (user.name != "" && user.address.country == "france" && user.job == "developer") {
    // ...
}

// explanatory variable
let isAdminOrFrenchDev = user.isAdmin || (user.name != "" && user.address.country == "france" && user.job == "developer")
if isAdminOrFrenchDev {
    // ...
}

→ Use explanatory variables. Their only purpose is to explain what its content is. It extracts code complexity to a “well-written prose”.

Number of arguments in a function

Reading a long list of coma-separated arguments is rude!
You probably never need to pass more than 2 or 3 things as arguments.

If you need more, like 6 things:

  • Those arguments are probably so tightly linked to each other that they should be an object.

  • Or probably some code can be extracted into 2 separate methods that only need 3 arguments each.

→ Keep the number of arguments down to 2 or 3. If you need more use other strategies: create objects, extract code, …

Don’t pass booleans as arguments

A function that takes a boolean as an argument will probably contain an if statement, that ends up with two branches in that function, for the if and the else.

Why not just separate them into 2 different functions? Call the one in the true case, and call the other in the else case.

→ Boolean arguments are often useless and can be replaced by directly calling specific methods.

Avoid switch statements

Let’s say we have a switch statement that switches on a Shape enum that declares Circles, Squares, etc.

  • How many switch statements do we have in our application? As many as we want to draw a shape, rotate a shape, move a shape, …

  • Now what happens when you create a new type of Shape? You have to go through the all code to find those switch statements to add a new case each time. This is fragile!

What we want is polymorphism.

  • We create a base class Shape (or an interface).

  • Then we create all the subclasses we need (Circle, Square, …) and put those draw(), rotate() and move methods in it.

  • Now what changes when we add a new Shape? We just have to create a new subclass. Nothing else change in our system.
    Note: This is the “open-closed principle” (the “O” of the “SOLID” principles), which means you can add new functionalities to the system, without changing the existing code.

→ If you write the same switch statement multiple times, you should probably use polymorphism.

Side effects

What is a side effect? It’s a change in the state of the system. If you call a function that causes the system to change state, that function has a side effect.

For instance, some functions come in pairs:

  • File management with open and close.

  • Memory management with allocation and free.

  • Semaphores with lock and release.

Those functions cause side effects. If we forget to call the second function the state of the system stay changed.

A solution to prevent that can be to wrap those pairs of functions in a new one that doesn't have side effects.

Here is an example of a custom high-level open method in Swift that prevent side effects.

func open(_ fileName: String, lambda: (File) -> Void) {
    let file = open(fileName) // low level open method from the system
    lambda(file)
    close(file) // low level close method from the system
}

// calling this function doesn't cause any side effect
open("file.txt") { /* use the file... */ }

→ Write code that has as few side effects as possible.

Command and query separation

Commands are methods that change the state of the system. Therefore they return Void.
And by convention, a method that returns a value doesn’t have side effects.

Then when you see a function that returns a value, you know that is safe to call, it will leave the system in the same state.

If you need to return an error, instead use exceptions.

→ Follow this convention to write more consistent code.

DRY: Don’t Repeat Yourself

We don’t like duplicated code. And we don’t like code that is kind of duplicated code.

If you repeat yourself, prefer placing that code into functions. And if the repeated code changes a bit sometimes, you may want to use some sort of argument in your function to make it more flexible.

But what happens if it’s not the code that is repeated but the code structure, like loops?

For instance, let's say you have some sort of complex configuration data structure, and to work with it you have a big nested bunch of loops and if statements, then you finally get to the end node and you’ve got some processing code.

And then you see that same loop repeating inside different parts of the system.

Here again, you can use lambdas. You can put that looping structure into a function that takes a lambda as argument and then you pass the processing code into the lambda when calling that function. You can take inspiration from the way functions like map, flatMap pr compactMap works.

→ Don’t repeat yourself.
Use functions to centralise repeated code.
Use lambdas to centralise repeated code structures like complex loops.

Wrap up

Here were my few notes on this first lesson on Clean Code.

You can find my other notes on Uncle Bob's lessons here:

I hope this article has been helpful to you. If you have any questions or feedback about this article, don’t hesitate to contact me on Twitter!