DEV Community

Jaap Groeneveld
Jaap Groeneveld

Posted on

Writing code like writing books

Writing readable code is important for a few reasons. First, readable code is easier to understand, which means that it is easier to modify and maintain. This can save time and effort, especially for large projects that may be worked on by multiple people. Second, readable code is more likely to be correct, because it is easier to spot mistakes when the code is easy to understand. Finally, writing readable code can improve your own understanding of the code, because the process of writing clearly and concisely can help you to think more deeply about the problem you are trying to solve.

It is difficult to say exactly how much time developers spend reading and understanding code compared to writing it, as this can vary depending on many factors. However, it is generally accepted that a significant amount of time is spent reading and understanding code, often more than is spent writing it. This is because writing code is just one part of the development process, and it is often necessary to read and understand existing code in order to modify it or add new features. Additionally, many developers find that reading and understanding code can be a challenging and time-consuming task, especially for large and complex projects.

Writing code like books

A good way to make code more readable is to get influenced by how people structure books and articles, especially in technical writing. Usually, the main goal of them is to make a complicated matter understandable by the reader. As this is also one of our goals, lets talk about this.

One way is to use comments and white space to break your code into logical sections, similar to how a book is divided into chapters and paragraphs. You can also use indentation to show the hierarchy and structure of your code, and to make it easier to see how different parts of your code relate to each other. Finally, you can use documentation to provide a high-level overview of your code, similar to how a book might have a table of contents and an introduction.

We want to read code as a set of "paragraphs" each describing the current level of abstraction and referencing down. This can make it easier to understand the logic and flow of the program.

  • Place functions in the downward direction.
  • Declare variables close to their usage.
  • Keep lines short.
  • Use white space to associate related things and disassociate weakly related.
  • Think about cohesion

Placing functions in the downward direction can help to show the order in which they are called, and declaring variables close to their usage can make it easier to see how they are used in the code. Keeping lines short can also make the code easier to read and understand, as long as the code is still clear and easy to follow. Long lines are fine if it is obvious what is going on and it is not hiding relevant calls.

Using white space to associate related things and disassociate weakly related items is also a good idea, as it can help to clarify the structure and organization of the code. This can be achieved by using indentation, blank lines, and other techniques to visually separate different parts of the code.

Cohesion is another important concept in writing readable code. This refers to the degree to which the different parts of a module or function work together to achieve a single, well-defined purpose. A high degree of cohesion can make your code easier to understand and maintain, because it helps to ensure that each module or function has a clear and focused role.

In this article I will explain the Step-Down-Rule and Line-of-Sight. To principles that will help you to structure code and make it more readable.

The Step-Down-Rule

The step-down rule is a software engineering principle that suggests that functions should be organized in a hierarchical manner, with higher-level functions calling lower-level functions. This means that higher-level functions should do the overall work of the program, while lower-level functions should perform more specific tasks that support the work of the higher-level functions.

The step-down rule is based on the idea that it is easier to understand and maintain code when it is organized in a clear and logical manner. By organizing your code in a hierarchical fashion, you can make it easier to see the relationship between different parts of the code, and to understand how they work together to achieve the desired outcome. Additionally, following the step-down rule can help to ensure that your code is modular and reusable, which can make it easier to modify and extend over time.

package api

func CreatePost(w http.ResponseWriter, r *http.Request) {
    post, err := getPostFromRequest(r)
    if err != nil {...}

    err = validatePost(post)
    if err != nil { ... }

    err = savePost(post)
    if err != nil { ... }

    http.WriteHeader(201)
}

func getPostFromRequest(r *http.Request) (Post, error) { ... }
func validatePost(post Post) error { ... }
func savePost(post Post) error { ... }
Enter fullscreen mode Exit fullscreen mode

In this code example we have the CreatePost function that outlines the process and deals with the interface to the outside world. It delegates sub-tasks to other functions and only deals with results and errors. This allows you to quickly understand the overall flow without getting lost in the details on how a post is validated or saved. If you want to know how that works, you can dig deeper. This is also called separation of concern and does not only make the code more readable, it also makes it easier to test.

Separation of Concern

Separation of concern refers to the idea of dividing a program into distinct parts, each of which addresses a specific concern or responsibility. This can make code easier to test because it allows you to focus on testing specific parts of the code independently, rather than having to test the entire program at once.

For example, if you have a program that performs several different tasks, you can divide the code into separate functions or modules, each of which is responsible for a specific task. This allows you to write individual tests for each function or module, and to verify that they are working correctly on their own. This can make it easier to find and fix bugs, because you can isolate the problem to a specific part of the code, rather than having to search through the entire program to find the source of the error.

Additionally, separation of concern can make it easier to test your code because it can help to ensure that your code is modular and reusable. This means that you can use the same functions or modules in multiple parts of your program, and you only need to test them once. This can save time and effort, and it can also help to reduce the amount of code that you need to write and maintain.

Line of Sight

Line of sight is a software engineering principle that suggests that the flow of control through a program should be easy to follow and understand. This means that the structure of the code should be clear and logical, and that the relationships between different parts of the code should be easy to see.

One way to achieve line of sight in your code is to use indentation and white space to visually separate different parts of the code and to show the hierarchy and structure of the program. You can also use clear and descriptive variable and function names to make it easier to understand what each part of the code is doing.

One important idea with the line of sight is to prevent nesting as much as possible.

What is nesting and why do we want to prevent it?

Nesting refers to the practice of placing one control structure inside another, such as putting an if statement inside of a for loop. While this can sometimes be necessary to achieve the desired behavior, excessive nesting can make the code difficult to read and understand.

One reason to avoid excessive nesting is that it can make the code hard to follow. When you have many levels of nested control structures, it can be difficult to keep track of where you are in the code, and to understand how different parts of the code are related to each other. This can make it hard to find and fix bugs, and it can also make it difficult to modify or extend the code in the future.

Another reason to avoid excessive nesting is that it can make the code less modular and reusable. When you have many levels of nested control structures, it can be difficult to extract a specific part of the code and use it in a different part of the program. This can make it harder to write clean and concise code, and it can also make it harder to maintain and update the code over time.

Overall, while some nesting may be necessary in some cases, it is generally best to avoid excessive nesting in your code in order to make it easier to read and understand.

Here is a example of the above code writting with excessive nesting.

func CreatePost(w http.ResponseWriter, r *http.Request) {
    post, err := getPostFromRequest(r)
    if err == nil {
        err = validatePost(post)
        if err == nil {
            err = savePost(post)
            if err == nil {
                http.WriteHeader(201)
            } else {
                handleError("error saving post", err)
            }
        } else {
            handleError("error validating post", err)
        }
    } else {
        handleError("error getting post", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

How do we prevent nesting?

The golden rule is to align the happy path to the left; you should quickly be able to scan down one column to see the expected execution flow. If everything is fine, continue down, if there is an error, go right, handle it and return.

  • Use early returns
  • Extract functions to keep bodies small and readable
  • Avoid else returns; consider flipping the if statement
  • Put the happy return statement as the very last line

To prevent excessive nesting in your code, you can follow a few best practices. One way to avoid nesting is to use early returns, which means that you return from a function or method as soon as you have completed the necessary work. This can help to reduce the amount of nesting in your code, because it allows you to exit a function or method without having to place the rest of the code inside of an if statement or other control structure.

Another way to avoid nesting is to use helper functions or methods to break up complex code into smaller, more manageable pieces. This can make it easier to write clean and concise code, and it can also make it easier to test and debug your code. By dividing your code into smaller, focused functions or methods, you can reduce the amount of nesting and make your code easier to read and understand.

Top comments (0)