Strategies to Alleviate Programmers' Cognitive Overload
Written on
Chapter 1: Understanding Cognitive Overload
Managing cognitive overload in programming is crucial for minimizing bugs and speeding up development processes.
One podcast I often tune into is "No Stupid Questions." Recently, I listened to an episode titled "How Simple Is Too Simple?" In this discussion, they challenged the Occam's Razor principle, revealing how humans tend to favor simpler explanations over more complex ones. While simplicity often prevails in fields like physics, this isn’t always the case in disciplines such as economics or psychology. For instance, the fall of the Roman Empire or the decline in crime rates can't be attributed to a single cause; rather, these phenomena stem from multiple factors. The hosts highlighted how people frequently prefer a singular explanation, despite overwhelming evidence to the contrary. This resonated with me.
About a month ago, I shared my insights on ways to reduce cognitive overload for programmers. I framed it as "a method" rather than "the method." This narrative stemmed from a recent realization I wanted to convey. While it was generally well-received, some individuals reacted defensively, insisting on an alternative method that they deemed the only solution. Interestingly, all dissenters referenced the same approach. While they were correct about its validity, they overlooked the existence of other viable methods. Currently, I've identified three primary strategies, along with several additional techniques that can be beneficial.
Cognitive load is indeed a multifaceted issue with various implications. The three main strategies I’ve identified to combat cognitive overload among programmers are: Split, Simplify, and Externalize.
Section 1.1: Split
This is the foremost tool for alleviating cognitive load. Rather than allowing programmers to confront everything simultaneously, we can divide the domain. A large software artifact often encompasses hundreds or thousands of concepts. To prevent teams from grappling with too many ideas at once, division is essential.
There are two main approaches to splitting: vertical and horizontal. The vertical split is the more recognized method. It segments according to business rules and requirements, making it easier to implement since it often aligns with departmental organization. This is where Domain-Driven Design techniques become relevant, as they facilitate the formation of isolated domains with defined boundaries for each team, thus minimizing the need to juggle additional concepts.
On the other hand, horizontal splitting involves dividing teams based on tasks and technologies rather than business concepts. This method gives rise to specialized teams, such as those focused on infrastructure and productivity. Although this division is commonly practiced, it often neglects cognitive load considerations. By reducing the steps required for complex tasks, these teams can ease cognitive strain.
Generally, it's advisable to split teams as they grow and begin to develop diverse specializations. The rationale behind this technique was recently articulated by the authors of the book "Team Topologies," who present a compelling argument for structuring teams around cognitive impact. For further insights, check out the video below:
Section 1.2: Simplify
This approach is often the first that comes to mind when discussing cognitive overload reduction. The fundamental concept is that a well-designed architecture and clear code structure can lessen the impact of changes in one area of the code, allowing programmers to focus on the current problem in isolation.
This idea closely aligns with the notion of coupling. In its simplest terms, two components, A and B, are considered coupled if a change in A necessitates a change in B. A sound architectural design aims for low coupling, enabling programmers to concentrate on only the necessary parts of the system, thus reducing cognitive load.
Conversely, cohesion refers to keeping related code close together. High cohesion ensures that all relevant functionalities are located in one area, minimizing the need for programmers to remember where changes are needed, thereby also alleviating cognitive burden.
It’s crucial to note that this concept extends beyond code structure. For instance, how folders are organized can be significant. Projects often classify folders by type of logic (controllers, models, etc.) instead of by functionality (users, posts, etc.), which can create low cohesion and increase cognitive load.
Additionally, the principle of least astonishment plays a role here. Good architecture is not enough; the code should be consistent throughout the project. This principle dictates that code should meet programmer expectations, minimizing surprises. This means adhering to established patterns and reusing existing designs, even if they are merely adequate. Such practices further reduce cognitive load.
Ultimately, the goal is to create a codebase simple enough that programmers can make changes without needing to think about other parts of the system.
Section 1.3: Externalize
This concept, which I elaborated on in my previous article, was a recent discovery for me, but it has been known for over two decades. The core idea is to alleviate the cognitive load on programmers by transferring it into the code itself.
There exists a baseline level of cognitive load that programmers must manage. Even with domain splitting and optimal architecture, numerous concepts still demand attention simultaneously. This load is not solely about the code; it also involves understanding how new functionalities will alter the program and how they will impact existing features, alongside identifying potential gaps that could lead to bugs. Programmers must coordinate these concepts and devise solutions that integrate them all.
Interestingly, we can often reduce cognitive load beyond what we initially believed was the minimum. One technique that significantly helps is Test-Driven Development (TDD). While I won’t delve into TDD in detail here, it involves automating tests to ensure the program functions correctly. Programmers start by implementing a test, make it pass by writing the corresponding code, and then move on to the next test. This iterative process resembles assembling IKEA furniture: you follow the instructions step by step instead of trying to visualize the entire assembly at once. This method reduces cognitive strain since programmers only need to focus on the immediate next step and receive instant feedback on whether their changes were successful or if something went awry.
Kent Beck, the pioneer of TDD, noted that after he first employed this approach, he felt as if he had cheated: “that heroic moment, as a programmer, when things are about to get out of control, and then, through the sheer force of your intellect and your will, you pull the order out of the chaos, that moment was gone.”
Chapter 2: Expanding the Toolbox
The initial discussion began with a reference to "No Stupid Questions," where it was emphasized that, particularly when dealing with humans, there is rarely a single solution. Similarly, there isn't just one or even three solutions to reducing cognitive overload.
There are additional strategies worth considering. While they may not be as impactful as the primary ones previously discussed, they still hold value. For instance, in the episode titled "If Everyone Hates Meetings, Why Do We Have So Many of Them?" the hosts discuss two distinct types of schedules: the manager's schedule and the maker's schedule. They highlight that managers tend to divide their days into one-hour increments, whereas programmers and creators often prefer to work in longer stretches. This suggests that minimizing interruptions from meetings can help programmers maintain focus without layering additional cognitive burdens from meeting discussions onto their current tasks.
Another commonly cited technique related to cognitive load is pair programming or mob programming. Surprisingly, coding is inherently a social activity. Rather than working in isolation and pondering how others will interpret your code, engaging in dialogue with teammates can alleviate that burden. Moreover, when interruptions occur, one person can manage the immediate tasks while the other retains the context. Through a fascinating human mechanism, the interrupted individual can seamlessly re-engage with the project upon returning.
Cognitive load presents one of the greatest challenges programmers face. It’s crucial to minimize it as much as possible, employing all available strategies. While the problem is complex, there are indeed multiple solutions at our disposal.