When programming, I often have a hard time following the rules of the trade. Instead of doing test-first TDD, I only start testing after creating some bits of code. Instead of pair-programming, I rather prefer sitting down with the code in solitude.
Why is that? Why do I have such a hard time doing as the gurus tell us to do? Why, however, are there situations, where I am happy pair-programming and doing test-first TDD?
The Cynefin Framework of Sensemaking
Let me introduce the framework with a few examples from my field of work, web development.
Let’s say I have to change some copy on a static website. Everything is clear, the solution is self-evident. I apply best practices and finish the task. In terms of the Cynefin framework, this task can be considered simple and the way to deal with it is sense, categorize, respond.
More often than this, I have to change some functionality on the website. The task is small enough, but I still have to figure out how to go about the change. How exactly does the old code work? Where are the few lines of code I need to change? In other words the solution requires expert knowledge and is not self-evident. There might even be several proper solutions. Cynefin calls this kind of problem complicated and we have to sense, analyze, respond. We still apply good practice, which means we adapt to the situation at hand.
If the change to our website is bigger, the solution space opens up even more and we enter the complex domain. An important effect in this domain are unkown unkowns, we no longer understand which questions we have to ask. We cannot anticipate which parts of the application will interact with our new feature and in what way exactly. In this case, solutions emerge from code spikes (or save-to-fail experiments in Cynefin terms). We can only increase our knwoledge by interacting with the system, that is we probe, sense, respond.
The last of the four basic domains of Cynefin is the chaotic domain. This is a transitory domain which we find ourselfs in, when, for example, an emergency production bug shows up. We don’t have time to analyze the problem, but have to act immediately and get the site back up. Cynefin calls this approach act, sense, respond. However, by acting successfully, we move the problem to the complex domain and buy us the time needed to solve it properly.
Application to Agile Software Development
What does all this have to do with agile practices? Well, I would argue, that different practices serve different needs in the picture I painted above.
Let’s start with pair programming. While the driver is working on the code at hand, the navigator is thinking strategically and reviewing the driver’s code. For simple problems, there is no need to think strategically as problems are just too small. We might still want some code review, but that can easily be done after the fact. I consider pair programming wasteful for the navigator in this situation.
When facing complicated problems, the navigator is helpful in routing us through the solution space. As we write non-trivial production code, direct code review is helpful. Solutions might get big enough, to overwhelm reviewers otherwise.
If we look at complex problems, there is a need to interact with the system in order for solutions to emerge. We could do this in pairs, however, I wonder if developer time is really spent best pairing on a solution or if it weren’t better to explore different approaches in parallel. Parallel exploration would also be more in line with the idea of multiple safe-to-fail experiments.
Finally, for chaotic situations, the goal is to get them under control as soon as possible. If talking helps you solve problems, pair. Otherwise, don’t.
In a nutshell, I consider pair programming a technique that is mainly useful in complicated situations. It might have some application in complex or chaotic situations, but I certainly wouldn’t prescribe it.
Let’s have a look at another technique, test-first TDD. TDD’s value lies in design feedback and ensurance of proper functionality of the code.
I have a hard time thinking of any simple programming task. Simple problems in my work almost always deal with changes I don’t test automatically.
Complicated tasks usually involve changing or extending some existing functionality. After analyzing existing code, it is usually pretty clear how to proceed and thus we apply test-first TDD.
Complex tasks usually come in the form of adding new functionality to an application. We might have different ideas about how to approach the task and Cynefin suggests spiking them in order to interact with the system let a sensible solution emerge. While spiking a solution, we hardly care about code design. Instead we explore different options in order to see how to they work in our application. This code also needn’t be correct, seeing interactions is usually good enough. Therefor I argue that TDD doesn’t give much value in this context. Instead we should focus on exploring the solution space as quickly as possible.
There is another interesting aspect to tests. Dave Snowden talks about a constraint based definition of Cynefin’s domains. In this view, problems range from highly constraint (simple) to unconstraint (chaotic). Adding tests to code could be considered increasing the constraints and thus moving a problem from one domain to another. I see this idea realized in a technique called spike and stabilize by Dan North. Spiking is a natural way of working with complex problems, however, to exploit the solution productively, we need to move the solution into the complicated domain at some point. This is what he calls stabilize or adding tests.
To sum it up, TDD is a practice that is also mainly helpful in complicated situations to write production grade code. Tests can be used to move exploratory code (complex context) to production grade code (complicated context).
A similar progression can be observed on operational tasks. With non-trivial software, we often start in the complex domain, poking around to make it work. Once we get it working, we document what we did. This way we constrain the next guy’s interaction with the software, so he just has a complicated problem. However, some expert knowledge is still required as documentation is seldomly perfect. When we interact more often with the software, we should at some point automate the interaction. The task then becomes simple as automation means maximum constraint (for example, think fully automated server deployments)
Coming back to my problems of applying TDD and pair programming in my daily work. I see myself confronted with complex problems almost daily. When I begin work on a new feature, I hardly have any idea about the solution. For me, it works best to sit down alone with the code and form a rough picture of a solution. Only then am I able to apply TDD and pair programming in a helpful manner, shifting the solution to the complicated domain.
However, I still wonder if I am missing something, as the gurus of our trade seem to be able to apply these techniques almost always. What do you think?