When I start a new feature I need to learn what the customer wants, what the code currently does, and how to write new code to resolve those two tensions. The more mature a system gets, the more time I spend reading and understanding abstractions than writing them. We want to explore techniques to decrease the time it takes to build a mental model of the code’s purpose.
Daniel Kahneman did a lot of research on the subject of thinking speeds in his book Thinking, Fast and Slow. He talks about System 1 and System 2 thinking. System 1 allows us to have quick access to ideas for real time processing. He describes it as fast, automatic, frequent, emotional, stereotypic, subconscious. If you closed your eyes and quickly opened them looking at a picture of someone's face, you could tell if the person was happy or sad. You wouldn't have to look at the picture and reason about it, you would just know it using intuition. System 2 thinking is what we use to think through a problem. Dr. Kahneman describes it as slow, effortful, infrequent, logical, calculating, conscious.
When writing code we can lean on structure to see if we can invoke the System 1 thinking in certain areas.
Take this for example:
def process_video(data) create_embedded_video(data) if data[:embed_url].present? create_uploaded_video(data) if data[:vendor_video_id].present? end
This code is read like a sentence. I read it from right to left, build up an context, and understand exactly what the code is doing. This code is using my System 2 thinking.
Consider the following roughly equivalent code:
def process_video(data) if data[:embed_url].present? create_embedded_video(data) elsif data[:vendor_video_id].present? create_uploaded_video(data) end end
Try to use Daniel Kahneman's facial emotions trick. Close your eyes, then open them and see if you can quickly understand what is going on. I see a multipart conditional switched on the symbols :embedurl and :vendorvideo_id. If statements in many languages are structured the same way. I can quickly scan the conditions and gloss over the body of the branch.
This example also illustrates the importance of thinking about the structures not only of the language but of the paradigm. A pure rubyist may use System 1 with both of these examples. However, like many programmers, I have no 'native tongue'. Different project and situations have made me write in many languages over the years. I would guess the second example invokes System 1 in many more people operating in an object-oriented paradigm.
Often times a programing language will give you different syntax for a given reason. In this example, I think ruby is giving us the choice. Use the code in the first example if you want the reader to pay attention to the full statement. Choose the second if you want it to be a split decision where to dispatch the reader to next.
Consider the following Fibonacci solution in Erlang. It uses pattern matching where the program chooses the code to execute by trying to ‘match’ the parameter’s . For instance, if the parameter was the value 1, the fibonacci(1) body would be executed. If it was 45, it would be go all the way to the third body fibonacci(N).
fibonacci(0) -> 0; fibonacci(1) -> 1; fibonacci(N) when N > 0 -> fibonacci(N - 1) + fibonacci(N - 2).
The structure of this code conveys information. Having the base cases on top communicates it is using recursion. With the hard coded 1 and 0 cases it conveys it is using pattern matching. Those things are screaming at me through System 1 thinking.
The third line is a little more confusing to read. This is okay because understanding this line is understanding the fibonacci sequence. We quickly conveyed the type of algorithm (recursion) and the flow of conditions (pattern matching) using System 1 thinking, but now to understand the meaning of the algorithm we need to switch to System 2.
When writing code, I try to think about the intent of the reader. Do I want them to navigate through this area as quickly as possible using System 1? Do I want them to take note of the algorithm using System 2?