Yesterday, I was discussing the use (and abuse) of design documents. From personal experience (not necessarily the best statistical indicator, I’ll admit), I know that design documents are flawed in a couple of main ways: first, the design, although detailed, may just not work when implemented (read: too slow, too much of a memory hog, too brittle) or may be too difficult (read: too costly) to complete, and second, the design may leave bits out, things that were just not thought of at the time and that came up as part of the implementation.
For the “doesn’t work” case, sometimes the developer may work out a different design through experimentation that does function correctly and satisfies the ultimate requirement. What happens about the design document then? Generally, it’s just left as is.
For the “bits left out” scenario, the developer may just implement the missing functionality or classes or methods as he’s writing the code. The design document is again adrift from its implementation.
The agile way is to use the requirements as the basis for the design/implementation, and to implement the requirements one by one. No design document is written, per se. Of necessity, this does mean that later requirements will cause earlier requirement implementations to be “wrong” or "not broad or deep enough" and to force them to be refactored. This is the sticking point for most developers: why waste time writing a whole bunch of code to satisfy requirement A when requirement X dictates that A should have been implemented in another way? Surely, the argument goes, you need to understand everything about the requirements space beforehand?
Well, no, you don’t. You just have to have confidence that you are able to refactor your code. You have to have confidence in your unit tests and that they will save you from refactoring mistakes. You have to relinquish the proprietary feeling you have towards your older code and thereby be confident enough to change it at will. This applies, by the way, not only to code but also to the database schema, to XML formats, and so on.
If you do have this confidence, you can design as you go along. You will be able to develop the application feature by feature.
This will avoid another issue as well: you never really understand the problem space until you try and write code to solve the requirement. Design documents have the tendency to gloss over the hard bits (I know this from my time at MS: my first design for the method parameter change refactoring was way cool, but also way too hard to implement). If you start writing code for experimental reasons (can this feature be implemented like this?) and then write a design document to reflect the results, I’d have to say why stop with the experiment? Continuing to implement and completing the code would be a better strategy, no?
The one thing that does make sense is to write up a description of where you got to in implementing a requirement and why you took the path you did. Perhaps this will help someone else understand the ultimate design decisions you made and why you made them. After all, unless you go spelunking through your source code control system, all the code shows you is the successful result; sometimes the failed experiments on the way there are more interesting.