Skip to content

Commit

Permalink
add new revision
Browse files Browse the repository at this point in the history
  • Loading branch information
zakirullin committed Jan 6, 2024
1 parent 7f0b680 commit 1994fef
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 36 deletions.
91 changes: 55 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
[简体中文](https://github.com/zakirullin/cognitive-load/blob/main/README.zh-cn.md)

# Cognitive Load Developer's Handbook

*It is a living document, last update: **January 2024***

## Introduction
We spend far more time reading and understanding code than writing it. Therefore, the amount of cognitive load required to understand code is a crucial metric.
There are so many buzzwords and best practices out there, but let's focus on something more fundamental. What matters is the amount of confusion developers feel going through the code.

Confusion costs time and money. **Confusion is caused by high cognitive load**. It's not a fancy imaginary concept, it can't be wrong - cognitive load is there, and we can feel it.

Since we spend far more time reading and understanding code than writing it, we should constantly ask ourselves whether we are embedding excessive cognitive load in our code.

## Cognitive load
> **Cognitive load is how much a developer needs to think in order to complete a task.**
Expand All @@ -14,7 +18,7 @@ The average person can hold roughly [four unrelated facts](https://github.com/za

*Let's say we have been asked to make some fixes to a completely unfamiliar project. We were told that a really smart developer had contributed to it. Lots of cool architectures, fancy libraries and trendy technologies were used. In other words, **the previous author had created a high cognitive load for us.***

![Cognitive Load](/img/cognitiveloadv3.png)
![Cognitive Load](/img/cognitiveloadv4.png)

The tricky part is that the previous author may not have experienced a high cognitive load due to familiarity with the project.

Expand All @@ -28,22 +32,25 @@ The tricky part is that the previous author may not have experienced a high cogn
<i>Thanks to <a href="https://dannorth.net">Dan North</a> for sharing his ideas.</i>
</details>

Once you onboard new people on your project, try to measure the amount of confusion they have (pair programming may help). If they're confused for more than ~40 minutes in a row - you've got things to improve.

## Types of cognitive load
**Intrinsic** - is the inherent level of difficulty associated with a specific problem we are solving. It can't be reduced, it's at the very heart of software development.
**Intrinsic** - caused by the inherent difficulty of a task. It can't be reduced, it's at the very heart of software development.

**Extraneous** - is generated by the manner in which information is presented, is imposed by factors not directly relevant to the problem we are trying to solve. Can be greatly reduced. We will focus on this type of cognitive load.
**Extraneous** - created by the way the information is presented. Caused by factors not directly relevant to the task, such as smart author's quirks. Can be greatly reduced. We will focus on this type of cognitive load.

![Intrinsic vs Extraneous](/img/smartauthorv2.png)

Let's jump straight to the concrete practical examples of extraneous cognitive load.

*P.S. Contributions are welcome! Feel free to send PRs with your own examples.*
*P.S. contributions are welcome!*

---

> **Note**
> We will refer to the level cognitive load as follows:
> `🧠`: fresh working memory, zero cognitive load
> `🧠++`: two facts in our working memory, cognitive load increased
> `🤯`: working memory overflow, more than 4 facts
We will refer to the level cognitive load as follows:
`🧠`: fresh working memory, zero cognitive load
`🧠++`: two facts in our working memory, cognitive load increased
`🤯`: working memory overflow, more than 4 facts

## Inheritance nightmare
We are asked to change a few things for our admin users: `🧠`
Expand All @@ -59,8 +66,34 @@ Oh, wait, there's `SuperuserController` which extends `AdminController`. By modi

Prefer composition over inheritance. We won't go into detail - there's [plenty of material](https://www.youtube.com/watch?v=hxGOiiR9ZKg) out there.

## Featureful languages
We feel excited when new features got released in our favourite language. We spend some time learning these features, we build code upon them.

If there are lots of features, we may spend half an hour playing with a few lines of code, to use one or another feature. And it's kind of a waste of time. But what's worse, **when you come back later, you would have to recreate that thought process!** `🤯`

**You not only have to understand this complicated program, you have to understand why a programmer decided this was the way to approach a problem from the features that are available.**

These statements are made by none other than Rob Pike.

> **Reduce cognitive load by limiting the number of choices.**
Language features are OK, as long as they are orthogonal to each other.

<details>
<summary>Thoughts from an engineer with 20+ years of C++ experience ⭐️</summary>
<br>
I was looking at my RSS reader the other day and noticed that I have somewhat three hundred unread articles under the "C++" tag. I haven't read a single article about the language since last summer, and I feel great!<br><br>
I've been using C++ for 20 years for now, that's almost two-thirds of my life. Most of my experience lies in dealing with the darkest corners of the language (such as undefined behaviours of all sorts). It's not a reusable experience, and it's kind of creepy to throw it all away now.<br><br>
Like, can you imagine, <code>requires C1&lt;T::type&gt; || C2&lt;T::type&gt;</code> is not the same thing as <code>requires (C1&lt;T::type&gt; || C2&lt;T::type&gt;)</code>.<br><br>
You can't allocate space for a trivial type and just <code>memcpy</code> a set of bytes there without extra effort - that won't start the lifetime of an object. This was the case before C++20. It was fixed in C++20, but the cognitive load of the language has only increased.<br><br>
Cognitive load is constantly growing, even though things got fixed. I should know what was fixed, when it was fixed, and what it was like before. I am a professional after all. Sure, C++ is good at legacy support, which also means that you <b>will face</b> that legacy. For example, last month a colleague of mine asked me about some behaviour in C++03. <code>🤯</code><br><br>
There were 20 ways of initialization. Uniform initialization syntax has been added. Now we have 21 ways of initialization. By the way, does anyone remember the rules for selecting constructors from the initializer list? Something about implicit conversion with the least loss of information, <i>but if</i> the value is known statically, then... <code>🤯</code><br><br>
<b>This increased cognitive load is not caused by a business task at hand. It is not an intrinsic complexity of the domain. It is just there due to historical reasons</b> (<i>extraneous cognitive load</i>).<br><br>
I had to come up with some rules. Like, if that line of code is not as obvious and I have to remember the standard, I better not write it that way. The standard is somewhat 1500 pages long, by the way.<br><br>
<b>By no means I am trying to blame C++. I love the language. It's just that I am tired now.</b>
</details>

## Too many small methods, classes or modules
> **Note**
> Method, class and module are interchangeable in this context
Mantras like "methods should be shorter than 15 lines of code" or "classes should be small" turned out to be somewhat wrong.
Expand All @@ -78,6 +111,8 @@ I have two pet projects, both of them are somewhat 5K lines of code. The first o

Once I came back, I realised that it is enormously difficult to untangle all the interactions between those 80 classes in the first project. I would have to rebuild an enormous amount of cognitive load before I could start coding. On the other hand, I was able to grasp the second project quickly, because it had only a few deep classes with a simple interface.

[Linear code is more readable](https://blog.separateconcerns.com/2023-09-11-linear-code.html).

> **The best components are those that provide powerful functionality yet have simple interface.**
John K. Ousterhout

Expand All @@ -92,34 +127,19 @@ close(fd)

A modern implementation of this interface has **hundreds of thousands of lines of code**. Lots of complexity is hidden under the hood. Yet it is easy to use due to its simple interface.

> **Note**
> This deep module example is taken from the book [A Philosophy of Software Design](https://web.stanford.edu/~ouster/cgi-bin/book.php) by John K. Ousterhout. Not only does this book cover the very essence of complexity in software development, but it also has the greatest interpretation of Parnas' influential paper [On the Criteria To Be Used in Decomposing Systems into Modules](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf). Both are essential reads. Another related reading: [Small Functions considered Harmful](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29).
> **Warning**
> If you think we are rooting for bloated God objects with too many responsibilities, you got it wrong.
## Too many shallow microservices
We can apply the aforementioned scale-agnostic principle to microservices architecture as well. Too many shallow microservices won't do any good - the industry is heading towards somewhat "macroservices", i.e., services that aren't that shallow. One of the worst and hardest to fix phenomena is so-called distributed monolith, which is often the result of this overly granular shallow separation.

I once consulted a startup where a team of three developers introduced 17(!) microservices. They were 10 months behind schedule and appeared nowhere close to the public release. Every new requirement led to changes in 4+ microservices. Both time to market and cognitive load were unacceptably high. `🤯`
I once consulted a startup where a team of three developers introduced 17(!) microservices. They were 10 months behind schedule and appeared nowhere close to the public release. Every new requirement led to changes in 4+ microservices. Diagnostic difficulty in integration space skyrocketed. Both time to market and cognitive load were unacceptably high. `🤯`

Is this the right way to approach the uncertainty of a new system? It's enormously difficult to elicit the right logical boundaries in the beginning, and by introducing too many microservices we make things worse. The team's only justification was: "The FAANG companies proved microservices architecture to be effective".

A well-crafted monolith with truly isolated modules is often much more convenient and flexible than a bunch of microservices. It's only when the need for separate deployments becomes crucial (e.g. development team scaling) that you should consider adding a network layer between the modules (future microservices).

## Featureful languages
We feel excited when new features got released in our favourite language. We spend some time learning these features, we build code upon them.

If there are lots of features, we may spend half an hour playing with a few lines of code, to use one or another feature. And it's kind of a waste of time. But what's worse, **when you come back later, you would have to recreate that thought process!** `🤯`

**You not only have to understand this complicated program, you have to understand why a programmer decided this was the way to approach a problem from the features that are available.**

These statements are made by none other than Rob Pike.

> **Reduce cognitive load by limiting the number of choices.**
Language features are OK, as long as they are orthogonal to each other.

## Business logic and HTTP status codes
On the backend we return:
`401` for expired jwt token
Expand Down Expand Up @@ -149,7 +169,6 @@ Cognitive load on the QA side: `🧠`

The same rule applies to all sorts of numeric statuses (in database or wherever) - prefer self-describing strings. We are not in the era of 640K computers to optimise for storage.

> **Note**
> People spend time arguing between `401` and `403`, making choices based on their level of understanding. But in the end it just doesn't make any sense. We can separate errors into either user-related or server-related, but apart from that, things are kind of blurry. As for following this mystical "RESTful API" and using all sorts of HTTP verbs and statuses, the standard simply doesn't exist. The only valid document on the matter is a paper published by Roy Fielding, dated back in 2000, and it says nothing about verbs and statuses. People get along with just a few basic HTTP statuses and POSTs only, and they are doing just fine.
## Complicated if statements
Expand Down Expand Up @@ -244,11 +263,11 @@ Jumping from call to call to read along and figure out what goes wrong and what

This architecture was something that made intuitive sense at first, but every time we tried applying it to projects it made a lot more harm than good. In the end, we gave it all up in favour of the good old dependency inversion principle. **No port/adapter terms to learn, no unnecessary layers of horizontal abstractions, no extraneous cognitive load.**

> Do not add layers of abstractions for the sake of an architecture. Add them whenever you need an extension point that is justified for practical reasons. **Layers of abstraction aren't free of charge, they are to be held in our working memory**.
> Do not add layers of abstractions for the sake of an architecture. Add them whenever you need an extension point that is justified for practical reasons. **[Layers of abstraction aren't free of charge](https://blog.jooq.org/why-you-should-not-implement-layered-architecture), they are to be held in our working memory**.
Even though these layered architectures have accelerated an important shift from traditional database-centric applications to a somewhat infrastructure-independent approach, where the core business logic is independent of anything external, the idea is by no means novel.

These architectures are not fundamental, they are just subjective, biased consequences of more fundamental principles. Why rely on those subjective interpretations? Follow the fundamentals instead: isolation, DIP, IoC, single source of truth, cohesion, coupling, true invariant, complexity, cognitive load and information hiding.
These architectures are not fundamental, they are just subjective, biased consequences of more fundamental principles. Why rely on those subjective interpretations? Follow the fundamentals instead: isolation, single source of truth, true invariant, complexity, cognitive load and information hiding.

## Learning from the Giants
Take a look at the overarching design principles of one of the biggest tech companies:
Expand All @@ -267,9 +286,8 @@ Does the new fancy buzzword comply with these principles? Or all it does is crea
</details>

## Conclusion
**If you are holding a lot of extraneous stuff in your head, think about future developers, yourself included. They would have to recreate this high cognitive load.**
**If you are holding a lot of extraneous stuff in your head, think about future developers, yourself included. They would have to recreate this thought process.**

> **Warning**
> Be aware that, as an author, you may not experience a high cognitive load because you have developed a mental model of your extraneous stuff over time. Others, however, do not have this mental model and would have to create a high cognitive load in their heads. In other words, extraneous things that are familiar to you are translated into an unnecessary cognitive load for others.
*The intricate and multifaceted nature of cognitive load within the realm of comprehension and problem-solving necessitates a diligent and strategic approach in order to navigate the complexities and optimize mental capacity allocation.* `🤯`
Expand All @@ -283,6 +301,7 @@ We have enough complexity in the work that we do already, why add more on top of
**Do not make the lives of future developers harder.**

---
Connect on [LinkedIn](https://www.linkedin.com/in/zakirullin/) or follow on [Github](https://github.com/zakirullin) for more articles
[I want more! A complete handbook for personal and corporate use](https://inmind.tech/cognitive-load)


*Contributions are welcome!* 🌱
Follow on [Twitter](https://twitter.com/zakirullin) or onnect on [LinkedIn](https://www.linkedin.com/in/zakirullin/)
Binary file added img/cognitiveloadv4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1994fef

Please sign in to comment.