Doing the simplest thing that can possibly work considered harmful
I’m not sure if there’s a blogger out there that hasn’t written a “Considered Harmful” blog. If you go back to the original article by Dijkstra, he never comes out and completely condemns GOTO’s but rather advocates capturing popular GOTO patterns and using those in “Structured Programming”. I.e., loops, ifs and switches. We still have reasons to pepper our code with GOTO’s in some cases, although following the rule that the use of a GOTO is either a) bad form and there’s a better way or b) bad form and there ought to be a better way, has granted us some of the more rich control structures in our high level languages. Exception handling is probably the more recent advancement. Anywho, enough with history. I just thought it’s particularly humorous to use the phrase “considered harmful” with “simplest thing that can work” that’s all.
TDD advocates a method that you test first, then code up the “simplest thing that can possibly work” until the test passes, refactor and repeat. TDD also happens to be a pretty damned effective method of producing code quickly and easily, as well as a huge pile of unit tests that make refactoring a breeze and aid in ensuring the quality of your code. As I said in Unit Tests as a Negative, unit tests and the tests that TDD pushes you to create also serve as an excellent specification and documentation of your code – each and every one of them are contracts, enforced each test cycle, that explain “When my code gets a, it returns b. Always.”
I can see the attraction to the “simplest thing that can possibly work” mentality. Indeed, many of the problems we arrive at in software come from overengineering a solution. But blindly following the simplest thing, including the KISS principle, can lead to tragically comical underengineering. I’m reminded of a post from months ago where a TDD advocate attempted to develop a Sodoku solver using test driven methods. It was pretty painful to watch.
The problem he ran into is that the main problem in software design is and always has been: How do we define the problem? The problem is the problem. When we can specify the problem well, and understand it, generally speaking there are always algorithms and data structures to solve it completely and elegantly. It’s when we cannot specify the problem that we run into boundary errors and extensibility issues. TDD is a very effective way to explore a problem domain one step at a time, when it’s unfamiliar. Unfortunately, the problem of solving a Sodoku has an already well defined, elegant algorithmic solution. There’s no reason to plod away step by step using TDD to explore it. Moreover, TDD is one of many methods of problem domain exploration. It gets points for probably being one of the most general, and applicable to any sub domain, but certainly gets points taken away in this case as it’s particularly bad at solving a mathematical problem.
In our Calculus courses, we didn’t spend all day making sure an equation worked when x = 1, then x = 2, then x = 3. No, we instead explored a few examples, then attempted to algebraically generalize the solution in a proof. Likewise, TDD can help you explore a domain, but too much emphasis might be given on the KISS regimen and too little on the refactor. Just like our normal generalization ‘instinct’, if we’ve done something three times, it’s time to generalize it. Likewise, three tests for any problem with a fundamentally algorithmic solution should give us enough ‘exploration’ to allow us to put our computer scientist hats on and provide a robust algorithm + data structure solution.
Not everything turns into an algorithm, obviously, we have message forwarding code, simple switches and our object structures. TDD works here too, but you can’t always generalize. However, the hardest things to test to prove an absence of defects also tend to be the ones easiest to abstract into a mathematical problem. RedBlack trees have certain characteristics about them that have been proved logically. Once we identify a problem as one that a RedBlack tree solves, the only testing we need to do is to ensure we didn’t screw up the implementation of the RedBlack tree. We don’t need to keep testing for new inputs and outputs once we’ve taken an established solution to a problem, and frequently, TDD helps us find those solutions.
So while the first cycle of testing, using the KISS approach might work, frequently we need to always keep our trusty old computer scientist hats at the ready and, upon having a few examples/use cases/tests on how something should work, design the proper algorithm not just to satisfy the use cases. In other words, abandoning the “simplest thing that can possibly work” for another methodologist cliche, “overgeneralization is the root of all evil” (apparently there are two roots) might be a more effective way to utilize TDD. Generalizing a solution over it’s variable inputs is one thing, but generalizing a solution over it’s types, functors, uses, policies, etc… is quite another. One provides for more robust code as there is usually a ‘known’ proper, elegant solution. The other makes code reuse much easier – however, it makes no sense when you only use your code once and can frequently send us down rabbit holes.
The elegant solution to a problem usually is a mathematical one, but it may not be the simplest. Don’t confuse mathematical generalization with software reuse generalization as it might make you spend about 100x more manhours on that Sodoku solver than you needed to.
No comments yet.

Archives
 May 2017 (1)
 February 2017 (1)
 January 2017 (1)
 December 2016 (2)
 November 2016 (2)
 October 2016 (2)
 September 2016 (6)
 August 2016 (2)
 July 2016 (1)
 December 2015 (1)
 November 2015 (2)
 August 2015 (1)

Categories

RSS
Entries RSS
Comments RSS
Leave a Reply