I happen to be particularly enamored of simplicity in art: the uncluttered style of oriental graphic art, the enigma of a Koan, the spare beauty of Haiku and the purity of a cappella voices.
Carrying that aesthetic to our chosen profession, we find an increasing focus on simplicity in software engineering. One of the core principles that is part of the Agile Manifesto reads “Simplicity – the art of maximizing the amount of work not done – is essential”. The lean movement puts additional emphasis on this. Finally, the admonition YAGNI (“You Aren’t Going to Need It”) is at its core an appeal for simplicity.So, why do we need all of this attention on a simple principle? Because Simplicity is HARD and doesn’t come naturally to most western minds.
- As software engineers, we’re paid to write code and we’re good at it, so our instincts (and/or our habits) are to write more of what we’re good at.
- Our product owners have very long lists of features and requirements that will make our products richer and more competitive.
But the cost of complexity is insidious (and I’m focused on both aspects of the word: both evil and not easily noticed):
- More code paths (one of the measures of complexity) represent more unit tests, more test cases, and more combinations and permutations.
- More code paths result in higher entropy: that conditional execution that is so minor and obvious today will not be so obvious and will be forgotten 2 years from now when you’re doing maintenance. The additional code paths greatly increase the chance that the bug fix or enhancement 2 years hence will break something
- When complexity finds its way into the user interface, it increases the power and flexibility of the application, but at the expense of usability. At best, the users grumble a bit, at worst, they stop using the product.
So, let me offer a few recommendations:
At the user interface, find a way to collapse a large number of high-level choices into a smaller number. For example, many text processing applications will have menu items for Search, Global Search, Find in Files, Replace, Replace Globally. In the end, those can all be handled by one Find function, keeping the top-level set of choices much smaller and more approachable.
In Axceler’s ControlPoint, we implemented Site-level permissions reports sorted by Site, Site-level permissions reports sorted by User, Comprehensive (detailed) permissions reports sorted by Site, and Comprehensive (detailed) permissions reports sorted by User. With the clarity of hindsight, these would have benefitted from collapsing these into a single high-level Permissions Report, with a sort option, and a level of detail option. (Now, a reality check on this: at some point, pushing too many choices down into detailed options on a function makes that function suffer from the complexity bug, so don’t carry it too far! However, at the more detailed level, you have more choices to manage complexity through techniques such as “Advanced” options so you only see the complexity when you need or want it).
When considering boundary conditions, error conditions, and special cases (these have a way of surfacing in bug reports, so the discussion tends to happen in that context), it is tempting to create special validation or special handling for each unique situation. Now, in some cases, that is appropriate, but you should resist the temptation: often the long term compounding effect of lots of little bits of complexity is greater than the incremental benefit of a uniquely tailored message or handler. My last blog post actually provides a good example of this. Apple chose to handle both “too hot” and “too cold” conditions in the same boundary condition handler. I believe that this was an excellent choice that kept the logic simpler. The only shortcoming (and a minor one at that) was that the message verbiage only acknowledged the most likely of the two possible conditions!
In closing, I’d like to quote a man who bridged the gap between art and technology (namely Leonardo daVinci) “Simplicity is the ultimate sophistication”.