The cognitive code strain on our brain

Something I noticed, unfortunately, to be deprioritized in the benefit of following some of Uncle Bob's rules blindly is the cognitive complexity we put on ourselves and our colleagues while writing code.

This brings unnecessary complexity to our code. Even though on the first hint it tricks us that it’s more descriptive and readable, it requires more effort by an another engineer to understand the code. Probably ourselves as well, just in a few months when we come back.

It’s also quite related to the code complexity learning curve meme of a Junior Engineer to Senior Engineer. Whereas more seasoned engineers like to keep it simple. There’s another picture that reminds me of it:

What is the cognitive strain of our brain? Basically putting a cognitive strain on the brain means that we are tiring it. The amount of cognitive complexity of our code is directly proportional to how tired someone is going to be and how much effort and time someone has to invest to go through and understand the code.

One extreme is being completely oblivious to the code we are writing, like naming all of our variables a, b, c, d, i, k, l . I’m trying to tackle the other side, overengineering and overcomplicating it.

Let me give you an example.

I write my examples in Go because I’m focusing on it now and working with it, but this is applicable to any environment. With the note that all environments have their own conventions on how-to.

func GetPrices(currency string) (http.Response, error) {
   //... Setup code - includes creating client and httpRequest

   resp, err := client.Do(httpRequest)
   if isResponseSuccessful(resp.StatusCode) {
    // Do something
   } else {
    // Do something else
   }

   return *resp, nil
}

func isResponseSuccessful(statusCode int) bool {
   return statusCode == 200
}

Here we see a function that will make an HTTP request to get a price of a currency. I’m not focusing on some details to not overload you (you see what I did there ;) ) with Go as a language and basic setting up of an HTTP client.

What I encountered numerous times is functions and methods getting extracted endlessly but totally unnecessary. Here to achieve a more nice descriptive naming we extracted a response check to a separate function that does a really basic job. But to me who is going through the code the first time, I have to ctrl/cmd + click it to see what the function is doing. Because, what is a successful response? HTTP 200? HTTP 201? HTTP 2xx? Who says that the function name is telling the truth? In reality, it will be slightly more complicated than statusCode == 200 . Either way, we will have to jump down, jump back up, and try to remember where we were the moment before. All of that could be avoided by “making it a bit more ugly” (even though I don’t agree with that):

resp, err := client.Do(httpRequest)
if resp.statusCode == 200 {
  // Do something
} else {
  // Do something else
}

Here, we immediately know what is happening and what is the goal. Let’s face it, all engineers know that status code 200 is a successful response.

The issue arises because cognitive complexity is incremental. This small function is no big deal. But over time, function by function, method by method. We gain hundreds of methods that are not used anywhere else just to stamp them with a nice name.

Another example commonly found is:

func Something(listOfItems []string) {
 item := findItem(listOfItems, "item")

 // do something with item
}

func findItem(listOfItems []string, item string) string {
 for _, listItem := range listOfItems {
  if listItem == item {
   return listItem
  }
 }
 return ""
}

This looks like some for manual job that you usually don’t encounter, but in Go, it’s quite normal. We don’t have fancy .find() methods. :)

Here we find ourselves yet again in a situation, where we would have to go level down to the function, understand what it’s doing and come back up. All for a nice name for the most basic for loop you can see. When this gets incremented over time and amplified it’s really hard and tiring to jump through the matrix up and down to understand simple code.

Let’s try to write simple code, maintainable code and code that we understand easily without firing up our brains to some levels.

By the way, Go is a phenomenal language that is addressing this issue. One of the main purposes of Go is Keep it simple. I think anyone who appreciates that will appreciate Go as a language and could try it! I encourage you!

Keep in mind, this is not related to designing your codebase, your architecture and others. That’s a whole other topic.

I’m obliged to give enormous credit to two people and a book.

My first mentor from my first company was one really talented and fun engineer. Unfortunately, I think I understood many of the lessons later on, but I understood them better thanks to him. Thank you, Victor!

The second is my former boss from Zühlke. He quite directly addressed this bug with me. He was discussing it with me and went into more detail with also recommending the book. Thank you, Igor!

The book is A Philosophy of Software Design — by John Ousterhout. A huge eye-opener and a good challenge to some coding standards. It’s quite small and short but full of beautiful details.