When Writing Code Meets The Marshmallow Test
JoniSar May 29, 2017
One of my favorite videos around the web is this video of kids trying to resist eating a marshmallow after being promised a greater reward if they can hold on for a short while:
This isn't only a funny video. It's also a fascinating experiment in human psychology that demonstrates one of our most important cognitive biasses: time preference.
What is time preference
Time preference basically means we value short-term rewards more than we value long-term ones. Practically speaking, it means we would rather have a single marshmallow right now than a whole bunch of them later. The longer the time difference, the worse it gets.
As time preference is affected by time (shockingly), studies suggest that people who lived longer may suffer less from its manipulations. To help battle this bias without waiting to become wise elders, here are a few tips as to how to identify and overcome this bias today.
Time preference and writing code
Like many other tasks, writing code often means choosing between short-term and long-term values. Time preference might tempt us to choose the immediate satisfaction over long-term values. Enforcing better practices upon ourselves often require the activation of precious and limited mental resources such as willpower.
Here are a few suggestions for how and when we can be aware of this bias when writing code, and how we can try to overcome it.
Test-driven development
We all know about TDD. We all know how important unit test are. There is little doubt about how they help make sure nothing breaks when we change stuff, and how they affect the overall quality and maintainability of our applications.
But, "Interrupting" our development flow to write tests in short cycles isn't always psychologically simple. Sometimes we just want to get the job done and worry about long-term stuff later. We can adopt different methodologies, but the curve for adoption often comes with a bit of a struggle.
There are a few ways we can look at TDD to increase short-term value and satisfaction, balancing the equation a little more in our favor.
A good example is the fact that tests help to decide when something is good enough. They define the behavioral scope of different components, helping us to better understand what each of them is supposed to do and when it's good enough to be considered "working". This can actually help us save time and stop optimizing things which already hit home.
Also, don't be ashamed to take pride in work ethic and quality of practice. Seeing green indicators flash over our code and knowing we put in the effort to create something we're proud of isn't something to be taken lightly. Practice makes perfect, and good practice should make us proud.
Design for modularity and reusability
Building modular software out of smaller atomic functionalities offers many advantages. Designing our applications with these principles in mind makes for better and more maintainable software. Still, much like TDD, it might also require some additional thinking and effort right now.
To help make life easier, we can try and generate short-term values and satisfaction from this practice. Designing with modularity in mind helps to better understand how our application is built and how every component fits in the bigger picture. Such clear structure can help avoid writing stuff we don't need and helps get things ready for production quicker. Thick twice, write once.
Also, we can and should aim to make our modular components truly reusable as we work, not only by design.
Some add components to a general "util" library they drag across projects. Others keep a "waiting for export" directory ready for exporting a reusable component every time we create it. Publishing to package managers such as NPM every single component might consume much of our day (boiler-plating etc.), thus creating more immediate negative value in the equation. To lower the barrier we can also use projects such as Bit and take advantage of the simplicity of export to create a growing arsenal of reusbale components to be shared with the open source community.
Building an arsenal of open source work is fun and generates a clear visual feedback for our effort. It's also a great way to collaborate with the others while getting feedbacks and improvement suggestions for our work.
Giving to others is something to be proud of, and social metrics or downloads makes us (biologically) feel the rush of what we did.
In the long run, your code will also be easier to maintain and understand.
Short documentation cycle
We often think of documentation if the context of explaining our code to the next person who will have to look at it. We know how important that is, and we've all tried to dive into someone else's code wishing they'd taken the extra time to write useful documentation.
Taking the time to document every component isn't always simple. Future us or future others aren't always our first priority, and we get tempted to overlook it and put our mental resources into getting our code to work.
There are a few ways to make this process more practical and satisfying in the short term. First, we can create a clear format for documenting different components and modules. Deciding what will be the exact format for the documentation can help grasp what we're going to do and how long it will take us, lowering the mental barriers of starting to write it. For example, when documenting a Javascript core functionality we can work with a checklist of (a) short description (b) signature (arguments, returns) (c) 1-3 examples and so on. This makes it easier to repeat the process.
We can also make sure our documentation builds a logical story. If we know that functionality A leads to B which together work with C, we can "read" the story of our code and make sure everything makes sense and that we didn't add redundant chapters. Of course, modularity means every component will be independently documented. Still, chapters in a story should build upon one another in a logical way as much as possible. If they don't, the docs is a good way to find out.
Good docs also help when publishing our work to our team or the open source, playing well with modularity and reusability.
Unit tests can also work as part of the component's documentation. For example, if we have a simple array-first Javascript function, the tests can tell us that (a) when the array is empty it will return null
(B) the first value of the array is returned when given X. This way the tests can also function as usage examples that help better understand the different use cases our code handles. This way we can hit two marshmallows with one bite.
At the end of the day, these are only suggestions. It's really up to us to know when and how ever we can gain improve immediate satisfaction from practices that also create long-term value. Over time, we develop our own understanding of "what works for us" and there isn't one rule that applies to everyone. In many ways, that's part of the beauty of it all.
"I can resist everything except temptation"
- Oscar Wilde