If you’re a developer, whether you realize it or not, you’ve probably experienced ‘decision fatigue’ at one point in your career. Decision fatigue occurs when the quality of the decisions we make deteriorates due to the mental fatigue of having already made a lot of decisions. While some people may want to believe that they have infinite willpower, our mental energy and focus has a limit no different than our physical bodies do. You can train long, or you can train hard - either way your body will tire eventually.
But just as we can train our bodies to endure additional stress, we can also train our minds to increase our focus. Or ideally - simplify our work to allow us to save our energy for the mental challenges that really make a difference. For example, a person with a clear idea of how they would like to manage their diet will be able to make easier decisions when grocery shopping, which can be helpful when different options are particularly abundant.
While this example refers to a relatively simple, personal decision, it’s even more important to alleviate the burden of choice for more complex decisions which involve a greater number of stakeholders. These are the types of decisions software developers will make with each line of code written, putting them at a high-risk of experiencing decision fatigue. Here we look at some of the ways in which decision fatigue and other frustrations that can manifest in software development, and the tools and methods that can be employed to combat them.
If there is a task that must be performed many times, or takes a long time to do manually, or is complicated and error-prone, developers should consider using automation. Continuous integration and continuous delivery (CI/CD), the practice of automating testing and deployment of your application, is one example of this. When you automate a process, you don’t just make it easier to execute, save time, and ensure consistency - you also make it discoverable.
This means that if a new member joins the team and has never worked with CI/CD before, reviewing the script that manages this automated process will help them to easily understand how the application is deployed.
This can apply to source-code as well. If you’ve ever worked on a backend API that is consumed by others, you know how important it is for your API to be easily understood in order for users to easily consume it. This same level of understanding can be applied to your internal APIs that you build for yourself, for future you, and for other members of your team.
As software engineers, we are all authors. As such, we need to consider the audience who is consuming the code that we are writing.
When working independently, it can be easy to find yourself lost down a "rabbit hole” - deep into trying to solve a problem and unable to remember where you had started or why you went down this route in the first place. Developers working in isolation are particularly prone to finding themselves in a rabbit hole, which is a sure-fire sign that decision fatigue is settling in.
Collaborative development processes, like pair programming, help developers avoid rabbit holes through creating a culture of fast feedback and knowledge-sharing. When there’s only one person who understands how a specific feature was implemented, this carries a risk for the team if that person is out on vacation, happens to get sick, or is out for any number of reasons, then the team can get stuck.
One recommended practice for pair programming is to rotate who pairs with who daily to ensure that several different team members have knowledge of how each part of the system works. This is a great way to share knowledge throughout the team as well as to ensure that the team can always continue to make forward progress, even if a team member is unavailable.
Pair programming is built upon equality and mutual respect, a by-product of which is the establishment and/or sustenance of a culture of knowledge-sharing. Approaches to collaboration based on equal parts observation and communication are always more effective in keeping people engaged. After all, too much of one or not enough of the other can make it difficult to concentrate, which is especially detrimental when trying to absorb or share knowledge. For example, I recently learned from a pair programming session about how test-driven deployment (TDD) could be applied to configuring docker containers using scripting purely vicariously.
Unsure how to implement a new feature or enhance an existing feature? If there’s a similar feature in the application, it could be a good reference so you can just follow the existing code patterns and tack on the new functionality, right? Sure, this might work for the first or second time - but by the third or fourth time you’ve repeated this pattern, it could start to get unwieldy. The easy way may be to follow the same pattern, and this can be good to maintain consistency in the codebase. However, to ensure that the software can continue to evolve, a different abstraction might be helpful.
Developers often see an existing pattern and simply copy and paste or tack on more of the same. While the pattern may have been easy to understand when it was initially implemented, repeated use can eventually create a ‘domino’ effect - whereby that same pattern, at scale, may no longer serve the code after it has evolved.
While there are some developers who are strictly against code duplication, it can sometimes actually be helpful. There’s a delicate balance between code duplication and creating a premature abstraction. Sometimes duplication is, in fact, necessary if an abstraction or cleaner way of writing can’t yet be discerned. It can be good to just let the code “simmer” for a bit longer and see how it will evolve.
Developers should therefore be aware of the code being copied or the patterns continually being promoted in the code. The next time you find an existing code pattern and your initial instinct is to simply copy it, consider this: the next time you or someone else encounters this code, would it make sense to continue using this pattern, or is it time to consider refactoring or creating an abstraction?
A good example is when a class becomes so long that it is difficult to manage (also known as ‘long class code smell’ amongst developers). The easy action is to continue to add to it and make it longer. When you detect this “smell”, consider if it would become easier to manage if it was broken down into more than one class. Are there multiple responsibilities within the same class that could be separated?
Culture of growing and sharing knowledge
This is by no means an exhaustive list of all practices for building code which works for everyone. Consistency in the practices deployed (i.e. code style, formatting, automation) is another significant area where many of the major barriers can be overcome. But the first step toward implementing any of the practices described here involves a more widespread, cultural change within the organization itself.
Developers work best when organizational culture promotes growing and sharing knowledge within the team. This should be approached comprehensively rather than on the basis of incentives: “lunch and learn” sessions are great, but free food will only get you so far when you don’t really get to take a break from work.
Instead, development teams should tie core values like communication, courage and respect to every aspect of the work they do. Doing this will help alleviate common barriers faced by developers to writing great software, while also contributing to an increase in productivity, quality of work, and developer well-being.
Derek Lee Boire, Senior member of Technical Staff, VMware Pivotal Labs