Developers Don’t Solve Big Problems
Hear me out: developers cannot and should not solve big problems. Every time they try to mastermind some brilliant solution to a complex quandary, the endeavor collapses, crushing the sanity of those poor souls who must interpret and maintain the code.
It’s like watching a toddler try to shove too much sandwich into their little mouths: most of it ends up on their face or on the floor and it’s left to the responsible adults to do the messy cleanup.
The thing that developers need to do very well is divide and conquer. They must be adept at breaking up big problems into smaller problems, and then they solve the little problems. The trick always lies in how the problem is framed: the difference between elegant code and a spaghetti mess is how a complex problem is digested into small and sensible bits. Beware those funky chunks of code that try to make a horse sit on both fish and grapefruit (and if trying to make sense of that last phrase gives you an aneurism, then you might need to read my previous article).
For many years I believed that learning design patterns would help in my quest to de-structure complex problems. However, I was never a fan of the Gang of Four’s “Design Patterns” book and I am finally able to articulate why: the examples were painfully inaccessible and they rarely translated into meaningful takeaways for the specific problems I was working on, or into the language I was working with, or both. Some of the secondary takes on those lessons in later publications yielded useful suggestions, but I always found the study of design patterns to be fraught with frustration. Along with many developers, I have been “pattern shamed” into learning these esoteric bits of knowledge, as if spending more time getting brutalized by the savagery of tough-love scholarly study was the antidote to all misunderstanding. Never mind that this sacred canon was often a jumble of incomplete summaries of overgeneralized solutions that may not have satisfactorily solved the original problem of overly-complex code and its inevitable rot.
When I discovered functional programming using Elixir, I started to realize that these design patterns were not universal: they were simply baggage that had stowed away inside the trojan horse of the object-oriented approach (what is it with me and horses recently?). The functional approach — where there are no global variables and everything is an assignment — made design patterns take a backseat to more fundamental building blocks, especially map reduce functions and the considerations of concurrency and message passing. Unlike much of what I tried to remember from my study of design patterns, these functional tools were immediately relevant and highly re-usable. They changed how I thought about code.
I started to have a deeper understanding of what Joe Armstrong, one of the creators of Erlang, famously said of object-oriented code:
You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
If you have referentially transparent code, if you have pure functions — all the data comes in its input arguments and everything goes out and leave no state behind — it’s incredibly reusable.
When you think about it, when you dig far enough below the facade, all code is functional, so wouldn’t we be better off not trying to obfuscate that? (I guess the same argument could be made for writing everything in C or Rust).
To wrap up this thread, I’ll share what have been the most useful bits of guidance when it comes to simplifying complex problems into elegant code:
- Long chunks of code are usually a red flag: each operation should be accomplishable in a handful of lines within an aptly named function or subroutine.
- Improve your “vocabulary” of available functions, especially map and reduce functions! So much of data manipulation boils down to those, and if you don’t know what functions are available and the corresponding “grammar” of how to use them, then you no talk so good!
- Be consistent in your approach. So many errors I witnessed in the OO world came from code that would sometimes rely on inheritance or OO classes, and sometimes rely on functional approaches. Where there is confusion, there are bugs!
- Read source code. Stand on the shoulders of giants: watch how others have solved problems
- Pay close attention to how specific problems are solved in different use cases. You may find similar strategies in vastly different verticals like eCommerce and lead-gen.
In the end, remember to keep it simple.