Anton Lindstrom (about, @twitter, @github)

Go func: Reducing cognitive load

Published:

I have been maintaining several fairly large Go projects for a while. Some of these projects have been for web services (APIs as well as backend workers) and some have been command line tools. A few of these projects have been new projects (green field) where I led the design and implementation, some have been taking over maintenance and some have been projects where I contributed.

One major thing that I have learned across these projects is that even though the code format (go fmt) is set, the layout and design have been quite different. This is perfectly fine where the requirements differ. However I want to cover some things that I have noticed that makes it easier for developers to pick up and read code.


This post will cover a part that makes it hard for me to understand where configuration comes from. Reading the code path is essential for me to understand how a program functions and how to debug it.

Good code for me is code that is easy to read, that is the number one property. Writing abstractions to make it easier to write code fast and efficiently becomes unimportant when comparing to readability.

To reduce cognitive load, I want to be able to read a function/method without having to think about where some variable comes from. The input and output should be well defined (essentially a pure function). Let me give an example:

package example

import "config"

func hasSomeFile() bool {
    if _, err := os.Stat(config.MyFile) {
        return os.IsNotExist(err)
    }

    return false
}

The most common pattern I have seen is that a config package is defined and is imported in many different packages, changed by either flags or somewhere else (or in the worst of cases, both).

When I read the code above, my first question is: What is config.MyFile?

From this example, I can probably check the value and type from the editor. However, this adds extra cognitive load. Is it set manually from a flag or file?

One example of getting rid of these questions is to define the signature to get rid of the config package and call the function with the filename as an argument. This way we know that we have to provide the value ourselves:

package example

func hasSomeFile(file string) bool {
    if _, err := os.Stat(file) {
        return os.IsNotExist(err)
    }

    return false
}

The documentation will be more clear and we know what to call the function with.

Conclusion

In order to reduce cognitive load and make it easier for new developers to understand and read your code, be explicit of what you use in your signatures. Code readability should be your top priority.

Edit: This post has been updated to provide better examples.

See also