The Art of Tradeoffs
There seems to be a common misunderstanding among netizens who create software for a living. There are those who proclaim loudly that one must model the whole world before committing to code—and that those who jump into code early are merely “code-like-hell” hackers.
Then there’s the crowd that says, Hey, wait a minute. You can’t learn enough from a simple model up front. You have to explore with code of some type. Otherwise, you could very easily miss important details, or even create a model that can’t be built!
The One True Answer
So which camp is correct? Those who prefer hacking around in code, or the those who insist on fully modeling their perception of the world before even thinking about code?
Well, in a way, they are both right. At least, they are trying to solve the same problem—gaining sufficient knowledge to correctly implement a system.
See, a software project is unlike projects in other engineering disciplines. Software projects are inherently projects of discovery. You and your team will learn more as time goes by. Your knowledge of the customer, the application, the environment, the sponsor—you will come to know much more about each of these topics as you go along.
And you must be prepared to act on that new-found knowledge. The error of the classic waterfall approach is that there isn’t any feedback. Discoveries made at the lower levels of coding have no way to affect requirements or architecture in a waterfall model. Yet these lower level details can often have profound effects on your understanding of the higher levels!
Mitigation of risk is at the heart of traditional engineering. When you build a bridge, you don’t build it as a perfect structure that will never collapse. Instead you build it to withstand 500 year winds, 200 year floods, 300% expected maximum load, etc. If you didn’t make these design trade-offs, every bridge would be solid concrete from the deck to the ground, and would be something like 500 feet wide. Engineering is all about making these compromises, and so is software engineering.
The difference is that with software engineering, much of the risk is in the process of building it, as well as in the finished structure, due to the nature of discovery.
Knowing that you will continue to make discoveries as you proceed, it comes down to the fact that you want to minimize the risk of discovering something that invalidates too much of your existing work.
Those who jump right into hacking out production code are already in trouble before they begin: large changes in direction (brought about by increased knowledge) are hardest to change here, and are the most expensive.
Those who propose modeling fully before doing any implementation run the same risk of making important discoveries while creating production code. The model, being an abstract version of reality, must forfeit some details. And the devil may well be lurking in those details.
Then there are those who try to balance these two extremes, by modeling a little, and coding a little, or by modeling with a disposable prototype or tracer-bullet code. Many of the Usenet discussions of this topic seem to bogged down in the details here of breadth-first versus depth-first; when do you model and when do you code, and so on. Different methods will suggest different ways of attacking this problem.
Methods R Us
eXtreme Programming is based on the premise of mitigating risk, and in a way, so are all the other popular design methods of the day—whether they admit to it or not.
Published methods attempt to answer the question ``How do I and my team gain the most knowledge about the problem and solution domain in the most economical way possible in order to create a software system?’‘
Clearly, risk is inherent in the discovery process of a project. But that discovery is ongoing, and we can’t afford to make crucial discoveries late in the game. Ideally, we want to discover everything we’ll need to know up-front and understand it perfectly. It will never happen that way, of course, so every method tries to create an environment where you can make the important discoveries as early as possible. The golden Answer of a particular method is based on how to lower your risk of making crucial, late discoveries.
But those answers will be different for everyone. The answer depends on your team, your project, your experience, your problem domain, your work environment, etc. Once you have found a method that works well for one project, there is no guarantee that it will work well on the next. If any of these listed factors change, your process will likely need revising.
It ain’t Perfect
You see, unlike the pristine beauty of an algorithm or sequence of tightly-coded machine instructions, it ain’t perfect. Processes and methods depend on people to carry them out and to understand the material being examined. And people are fallible.
So in the end, it comes back around to the idea of managing trade-offs. You could spend six months on a project diddling around with an object model, only to discover that you misunderstood the customer’s performance requirements during implementation. You could jump into implementation too early, and lock yourself into a design or architecture that won’t support important requirements that you haven’t gotten to yet.
Or you can weigh the trade-offs to minimize overall risk, and analyze a little, design a little, and code a little. The relative proportion of each task, the order, the amount of feedback—all of these will be different for each project, and each practitioner. There are no right answers that are correct all the time, in all contexts. Once must be pragmatic, and make the choice that fits the current problem, the current team, the current environment, and so on.
So next time you enter into a discussion of computer as an engineering exercise versus an art, consider the art of trade-offs. Other disciplines must live with trade-offs concerning the physical world; we must live with trade-offs concerning ourselves.
And then we can proceed to develop software.