Clarifying Agile Development “Catch-Phrases”
When I first talk to a team as their agile coach, I often discover that they've read some books or attended conferences, and they've come away from these brief experiences with lingering concerns over certain "agile catch-phrases." Of course, the skepticism is healthy, and the misconceptions are natural. This is all part of a team's transition to true agility.
How is it that misleading agile catch-phrases continue to propagate after a decade? We shouldn't be too surprised. Coaches, authors, and presenters use them to focus attention on a particular topic, and to provide a simple memory-aid (e.g., "YAGNI," which I'll discuss later). I'd bet that these evangelists mostly follow up with some level of clarification. But I know that if I hear something controversial, I will sometimes find myself inside my own head, having a personal dialog about what I think the statement means, and may miss entirely the presenter's follow-up explanation. Distraction is a natural human state. If I don't pay attention to the broader discussion, I may come away from a conversation with some concerns surrounding a little sound-bite.
So allow me to clarify some of these catch-phrases. I'd like to focus today on a few that impact the act of writing software. I'm a big fan of Test-Driven Development (TDD), as a coach and a practitioner, so I'd like to focus even further on that topic. (I'm perhaps a Behavior-Driven Development proponent, but there is no difference between BDD, and TDD done correctly and sustainably…the way we were taught ten years ago.)
"Write the tests first"
There are two important types of TDD in common use: unit-test TDD, and acceptance-test (or story-test) TDD. (We call them UTDD and ATDD.) With ATDD, we do indeed want to have as many acceptance tests written for the iteration's stories as early in the iteration as possible. Using a tool like FIT, ATDD is an extremely viable practice. FIT allows testers to write tests without having to deal with the GUI, and to run those tests without an underlying implementation.
But in UTDD, developers don't write a bunch of unit tests first! We write one lone unit test first, then get it to pass, then "set it aside". It continues to run with all the other prior tests, and that's part of the benefit. Imagine a juggler: He starts with one ball, then adds another and another. In our case, the computer is juggling the thousands of other tests, making sure none of them fail while we concentrate on the single behavior right in front of us.
When we try to write more than one, we're trying to capture too much analytical detail up front. On the other hand, the great thing about writing one test is that it exposes the opportunity for other tests (e.g., "Oh! Since I'm getting it working when the string is lowercase, my next test should be when the string is all uppercase. Oh, and what about an empty string? What about a null reference…?") Where do we record this onslaught of analytical fruition?
My suggestion is to write each idea down on a to-do list. I usually have a 3x5 card sitting next to me, and I write down ideas (often expressed as "test <some scenario>"). I draw a little checkbox by each, and check it off when I'm done, or X it out if I decide it doesn't move the behavior forward, or if it's an impossible scenario.
You can usually add these to-do items to the code using a special comment, and the IDE will keep track of them for you. (This is particularly useful if you find yourself in the unfortunate circumstance of having to write code alone.) Your fingers never have to leave the keyboard.
Those to-do comments are fine, but please never ever check in a to-do comment! Either do it, or delete it. Otherwise you or others will be asking "Does that really need to be done? By whom? When?" No one ever seems to do those to-do items until a tester (or a customer) discovers that something is broken. At the same time, the list of checked-in to-dos grows and grows, becoming just more noise that no one listens to (like compiler warnings).
"Do the simplest thing that could possibly work"
We're not encouraging developers to lazily hack out a solution. It's not about the simplest activity for the programmer! It's the simplest design. I suspect that when Ron Jeffries and Kent Beck were initially using this phrase, they also knew that their Smalltalk team was well-versed in good OO design techniques and design patterns. They're invoking the old KISS principle (Uh…"Keep It Succinct and Simple" of course) in order to avoid undue complexity.
There is nothing magical about OO design. Good OO design is design that is understandable and maintainable by the development team. The design should allow behavior to change in known and possibly as-yet-unknown ways, and the tests should allow the design to change as we discover new opportunities for more flexibility. All this is done using the OO features of the language in well-known and appropriate ways.
One long, monolithic main() method may seem like the simplest design to one programmer. It probably isn't.
Also, this current design (as expressed by the entire product code base) needs to exist as-is only until you prove that it's not sufficient via another tiny unit test. Then it adapts and improves, incrementally. How much time between starting a new test and having a good design behind the new, functional behavior? Two minutes, tops!
Two minutes? I'm being extreme for a reason. But I want to push a little, so people do not think that they'll be spending ten minutes per unit test. The very first unit test may take that long, if you're not familiar with the techniques. But in 30 minutes you should have about a dozen important little unit tests written.
If it takes more than two minutes, you may be over-thinking (or, more importantly, under-testing). I see this happen a lot on teams adopting TDD: People start with a big test (say, a dozen lines of test code) and can't get it to work. Frustrated, one (of the pair) suggests something really tiny and "silly," but they get it working in no time. They proceed this way, stealthily creeping up on the full solution. Had they been trying "baby steps" from the start, they would have spent the same amount of time (or less). And the result is always more, discrete tests. Start by breaking down the behavior into smaller and smaller scenarios.
TDD is a game you play with your own brain: First "prove" you need it (write the "test" that describes the behavior), then build it. Look for opportunities to simplify and clarify (based on what you have, or based on the next behavior you want to add). Then repeat. Each test, and each bit of production code, is a quick baby-step towards the completion of your task. When all the tiny items on your task to-do list are done, you're ready to check-in and integrate a small bit of clean, working, production-ready code. This is how I've seen stunningly successful agile developers work.
In my experience, teams find TDD to be faster. That's because TDD pins down the behavior, while leaving the design fluid and flexible. So, I can alter the design without breaking existing behavior, and I can freely alter the design to accommodate new behavior. No more "two steps forward, one step back" as it were.
I also find that I spend my intellectual cycles (and time) on the test-writing (i.e., the analysis), and the subsequent design changes show up almost effortlessly. Sure, I keep good OO design qualities like non-duplication, strong cohesion and intentional coupling in mind. No one ever suggested that we discard good design qualities. (They may have simply neglected to remind us.)
"You Aren't Gonna Need It" (aka YAGNI)
There is this old story about the XP team who needed to store something, so they wrote it into a flat file until they proved (with tests) that they needed a relational database. Of course, you know what you know, and if you know that you're building an application that is going to have a rich transactional life, then by all means go ahead and store it in the database installed on the development box. Certainly the early XPers weren't trying to work us into an architectural pickle. They were asking people not to overdesign, or to try to predict where the software requirements are going to take us next.
YAGNI is true, until and unless you prove you need "it" with a test. Again, you know what you know, and you are playing the TDD game to prove it to yourself and to leave discrete documentation of that analysis activity (a unit test). If, in the prior two minutes, you didn't build a hierarchy you know you need (note, I said know), you can either write the next unit test for the factory of the varying abstraction, or refactor so you have an abstraction and a single concrete class, then test and build the "other" of that abstraction, then test the factory. This is known as "refactoring to the open-closed (principle)" and it is how efficient agile teams have been adding features to their software, whether or not they recognized it consciously.
YAGNI makes you think consciously about what you really need, and what you may have only assumed you need. We make the needs explicit in the tests. This includes many software architectural issues.
So is there any other benefit to playing the YAGNI game? Indeed, there is.
It's not just a great way to reduce unneeded complexity and unused code (which is extremely dangerous and burdensome code). It also fosters creative, simple solutions. When we step back and ask, honestly, "What is it I really need to accomplish here?" the answer may be simpler and more flexible than otherwise.
Someone recently pointed out that the data bandwidth of burning and shipping a set of DVDs overnight often exceeds typical cable-modem bandwidth for the same large set of files. If that set of files is valuable enough to the recipient, then which is the better solution?
I've seen numerous teams choose simple, testable techniques and architectures over the hottest technology du jour, and they never regretted their choice. Real (albeit well-aged) examples include Velocity over JSP pages, a simple custom hidden field over inappropriate use of the ASP.NET page's View State, simple key-value pairs over XML , XML over a relational database, XSLT transformations over a full rewrite of the XML-emitting Java code, and buying a handful of powerful new servers over a years-long re-architecting of a clean design into a multi-threaded morass. To use another Ron Jeffries quote, "Your Mileage May Vary" (YMMV).
This interpretation of YAGNI encourages us to figure out the right what-ifs to ask, based on root-cause analysis of the problems we're facing. If we solve root issues in an architecture, chances are very good that we will not need to go back and re-architect.
Trust Your Instincts
Using agile and TDD techniques, we still have to think about the software we're writing. The subject-matter and the timing of our critical thinking may be changed by the adoption of TDD, but software-craftsmanship remains an intellectual profession. It still requires realistic training and well-managed experience.
It is also a changing profession, so if you hear something that seems like a hard-and-fast rule that contradicts your experience, be sure to question it. But be open to trying it out, once people have clarified the intent.
Leave me your comments on the agile catch-phrase that causes you or your team consternation, and we'll work through it. Perhaps next time I'll focus on catch-phrases regarding agile project management, such as "done done," "releasable code," and "cross-functional team."
Comment on this blog or post at our Lean Programming Yahoo Group:
http://tech.groups.yahoo.com/group/leanprogramming/
Thanks,
Rob Myers
- Rob Myers's blog
- Login or register to post comments


Technorati Tags:
tag with del.icio.us
Comments
More about refactoring to Open-Closed
I glossed over the example of refactoring to OCP (the Open-Closed Principle). Scott Bain produced a "streamzine" on this topic, which nicely covers this in much more detail:
http://www.netobjectives.com/resources/webinars/refactoring-to-open-closed
I tie this to TDD by suggesting that if you're disciplined about your TDD practice, your design is always very appropriate for the behavior you've test-driven into the product; with no more complexity than it needs. Each time you examine the code in preparation for adding new behavior, take a look at what is varying, use refactoring to encapsulate the variation, then add the new variation using TDD.
I have a number of first-person experiences where this technique has paid off immeasurably, as we discovered some new use for the software that was never predicted. (I include the business folk in the team "we", by the way.)
The requested change, in each example, was something that would usually be considered "architectural" in nature, and in fact there was one incident where the business told us up-front that we could have an extra three months to rewrite, because the change was that important.
In each of these first-person events, the team added this radical new feature in less than a week.
Diligent refactoring pays.
More clarifying
Two others that I see confusion on:
Minimum Marketable Feature - It seems our stakeholders hear "minimum" but not "marketable". They don't see this as a competitive advantage, but seem to picture a boring hum-drum solution that gets smoked by the competitor.
Last Responsible Moment - Translated as "Procrastination, No planning, no design, flying by the seat of your pants and hoping for the best". Again, it is not seen as a competitive advantage whereby we avoid waste. Stakeholders don't understand we are asking them not to invest in things that might still get dropped.
re MMF and Last Responsible Moment
I hit this all the time myself. It's why I make a point to emphasize that it must include any jazz necessary. I actually point to the iPhone as an example. They could have released it earlier, but would wait until the marketing stuff is available. That's why it is call Minimum _Marketable_ Feature and not the Minimum _Releasable_ Feature.Instead of saying Last Responsible Moment - you might try - not too early.Alan Shalloway, CEO Net Objectives