Design’s Place in an Evolutionary World
Extreme Programming (XP) challenges many long-held assumptions about software development, but none more controversially than its rejection of significant up-front design in favor of an evolutionary approach. Critics see a retreat into “code and fix” hacking; supporters sometimes celebrate it as a wholesale dismissal of design tools like the UML, design principles, and patterns. The reality is more nuanced: XP demands a great deal of design, just practiced differently — and with more discipline — than traditional processes allow.
Evolutionary design, in its common form, is a disaster waiting to happen. Design grows alongside implementation, but without countervailing forces it degrades into a pile of tactical, ad-hoc decisions that make code progressively harder to change. Software entropy sets in: bugs breed more easily, become harder to find, and the cost of change climbs steeply. This is the infamous “code and fix” cycle.
Planned design emerged as a corrective, borrowing from other engineering disciplines. You can rough out a doghouse without drawings, but a skyscraper demands engineering blueprints, building codes, and careful analysis before any construction begins. The software analog: designers work at an abstract level — often with the UML — to think through big issues in advance, then hand a complete design to a separate team to build. Done well, this avoids the accumulation of tactical decisions that plagues code-and-fix projects.
But planned design has serious weaknesses. It is impossible to think through every implementation issue up front. When programmers inevitably hit something that challenges the design, the designers may have moved on, or the process of revising drawings and reworking code is slow enough that time pressure wins and entropy creeps in. A cultural divide often deepens the problem: designers stop coding, lose touch with a rapidly changing platform, and lose the respect of those who still write code daily. And the biggest issue — changing requirements — cannot be designed away. Foreseen volatility can be planned around, but unforeseen changes, particularly those driven by shifts in the business, will derail any up-front design.
So neither extreme works well. Planned design is preferable to code-and-fix, but it is not a cure.
What Makes Evolutionary Design Viable
XP’s bet is that the exponential change curve — the phenomenon whereby a change that costs $1 in analysis costs thousands if made in production — can be flattened enough to make evolutionary design a legitimate strategy. That flattening requires specific enabling practices, and critics who dismiss XP often do so from experience where those enablers were absent. They attempted evolutionary design, got burned, and rightly remember the fire.
The core enablers are testing and continuous integration. The safety net of thorough automated tests makes change less frightening; continuous integration keeps the team synchronized so a change doesn’t collide with others’ work. At Thoughtworks, introducing these two practices alone produced a marked improvement in development effort — enough to question whether every XP practice is truly required for substantial gains.
Refactoring provides a similar effect, but disciplined refactoring — as Kent Beck teaches it — is categorically different from looser restructuring. The difference in effectiveness is significant enough that it motivated a whole book on the subject.
Jim Highsmith’s analogy of a scale is apt: planned design sits on one tray, refactoring on the other. In traditional approaches, planned design dominates because the assumption is that you cannot change your mind later. As the cost of change drops — thanks to testing, integration, and refactoring — more design can happen later, as refactoring. Planned design doesn’t go away; it rebalances.
What we don’t yet know is exactly where the balance point lies. XP is not simply test-code-refactor. There is room for design before coding, both in initial project setup and, more often, within each iteration before a particular task. The new equilibrium between up-front design and refactoring is still being mapped.
Why “Do the Simplest Thing” Is Hard Advice to Follow
XP’s slogans “Do the Simplest Thing that Could Possibly Work” and “You Aren't Going to Need It” (YAGNI) sound straightforward but run against a deeply ingrained habit: building flexibility up front in the form of frameworks, reusable components, and extensible designs. The logic behind that habit is rational — you pay an extra cost now to save effort later.
XP rejects that trade-off for good reasons. The first is economic: any code you write today for a feature needed tomorrow is effort taken from the stories you committed to in this iteration. If that future work isn’t needed for the current release plan, the customer — not the developer — gets to decide whether it’s worth doing at all. The second reason is risk: without detailed requirements in hand, your assumptions about how that future feature will work are likely wrong. Solving the wrong problem early is more wasteful than solving the right problem early.
There’s a subtler cost, too. Even if the code you add today costs nothing to write and turns out to be exactly what’s needed later, it still makes the system harder to understand and modify in the meantime. That extra complexity taxes every change made between when it was introduced and when it actually earns its keep.
This advice only makes sense, though, when the rest of XP is in place. In a traditional environment, where change is expensive and refactoring is risky, YAGNI looks foolish. It becomes good practice precisely because XP lowers the cost of change — and only then.
What “Simple” Actually Means
Few people argue against simplicity, but agreeing on what counts as simple is another matter. In XPE, Kent Beck gives four criteria, in order of importance:
- Runs all the tests
- No duplication
- Reveals all the intention
- Fewest number of classes or methods
Running tests and eliminating duplication are unambiguous. The tricky one is “reveals intention.” XP treats “clever code” as an insult — but what looks intention-revealing to one person can look clever to another.
Josh Kerievsky uses JUnit as a classic example. JUnit employs the Decorator pattern to layer optional behavior onto test cases, such as concurrency synchronization and batch setup logic. That separation makes the core code cleaner — but only if you already know the Decorator pattern. For developers who don’t, the structure is baffling. Similarly, many people find JUnit's pluggable methods confusing at first. Is JUnit’s design simple for the experienced designer and complicated for everyone else?
The pragmatic answer is to stop obsessing over the question. Robert Martin’s advice is to remember that simple design is not a permanent destination. You can — and should — refactor later, and the willingness to refactor matters more than getting the “simplest” answer right the first time.
Of course, it helps to have a guide. The focus on eliminating duplication — XP’s “Once and Only Once” and the Pragmatic Programmer’s DRY (Don't Repeat Yourself) — is one of those rare pieces of advice that is both obvious and powerful. Following it alone gets you far, but it isn't sufficient on its own. One developer’s comment after a refactoring that stripped out over-design is worth remembering: “it's easier to refactor over-design than it is to refactor no design.” Aim for slightly simpler than you think you need, and accept that being a little more complex is not a disaster.
Refactoring Is Not a YAGNI Violation
This question recurs on XP mailing lists: refactoring takes time and adds no user-visible functionality, so doesn’t it contradict YAGNI’s present-focused design principle?
No. YAGNI is about avoiding unneeded complexity in the current stories. Refactoring is the tool that removes complexity once you realize it exists.
The relationship is mutual. Simple design exploits XP practices — testing, continuous integration, refactoring — but it also enables them. Keeping the design simple is what keeps the change curve flat over time. Unnecessary complexity makes a system harder to change in every direction except the one you predicted when you added the flexibility. Since people are poor predictors, the best strategy is to keep things simple, and refactor to get closer to that goal when you miss it.
Patterns: Still Essential Knowledge
The role of design patterns in XP is a common point of confusion, largely because patterns are often misused. Anyone who has seen the cult classic of a developer fresh off of the GOF book drizzling sixteen patterns into 32 lines of code knows the stereotype — there may or may not be a single malt involved. But the parody has a point: patterns are overused, not because they’re bad, but because knowing how to use them is a real skill. The answer is not to abandon them.
One theory holds that by simply following the rules of simple design, refactoring will naturally lead you toward the right pattern — or that you’ll reinvent it yourself. While plausible, that approach seems inefficient. If you know roughly where you’re heading, a pattern catalog helps you navigate the issues instead of inventing the entire solution from scratch. I certainly reach for the GOF book whenever I feel a pattern coming on.
Kerievsky makes the point that patterns are under-emphasized in XP, despite the fact that most prominent XP proponents were also leaders of the patterns movement. It’s tempting to wonder whether they’ve moved beyond patterns, or whether patterns are so embedded in their thinking they don't notice them anymore. For me, the answer for XP is not that patterns are dead. XP offers a different way to apply them: don’t reach for a pattern until it’s needed, and gradually evolve toward it from a simpler implementation. That is not a contradiction — it means that XP should put more emphasis on acquiring knowledge of patterns, and use a different discipline when applying them.
If you are an XPer working with patterns, the practical guidance is:
- Invest time learning about patterns before you need them
- Concentrate on when to apply a pattern — not too early
- Learn how to implement a pattern in its simplest form first, and add complexity later
- If a pattern you applied isn’t pulling its weight, remove it without hesitation
What an Architecture Is For
An architecture, in the software sense, is the set of core elements that are hardest to change later. It is the foundation on which the rest of the system has to sit. The critics of evolutionary design argue that XP ignores this entirely, jumping straight to code and trusting refactoring to fix whatever design problems emerge. They are not entirely wrong, and it is a fair weakness to point at. The most aggressive XPers — Kent Beck, Ron Jeffries, Bob Martin — increasingly steer away from any up-front architectural design. Don’t add a database until you know you need one, they say; use files first, then refactor the database in on a later iteration.
I am known as a more cautious XPer, and I have to disagree with that position. There is a role for a starting-point architecture, set broadly: how you will layer the application, how you will talk to the database if you need one, what approach you will take to the web server. These areas are, essentially, patterns we have learned over the years. As your pattern knowledge grows, you should have a reasonable first guess at how to apply them.
The key difference from traditional up-front design is that these early architectural choices are not set in stone. The team knows it may err and should have the courage to fix those errors late. One project I know of, close to deployment, realized it did not need EJB anymore and removed it entirely. That was a sizeable refactoring, done late, but the enabling practices made it not just possible but worthwhile.
The reverse question is harder: if you start without EJB, would it be more difficult to add later? Should you never start with it until you have tried going without and found it lacking? Many factors come into play. Working without a complex component keeps things simpler and faster. But it is often easier to rip something out than it is to put it in.
My advice is to assess the likely architecture first. If you foresee a large amount of data with multiple users, use a database from day one. If the business logic is complex, use a domain model. But defer to the gods of YAGNI when in doubt, and be ready to simplify as soon as you see that a part of the architecture isn’t earning its keep.
Diagrams That Earn Their Place
People often ask how my involvement with XP squares with my work on the UML. The two seem incompatible. XP certainly plays down diagrams, and even though the official line is “use them if they are useful,” there is a strong subtext that real XPers don’t draw them. Kent Beck, for instance, isn’t comfortable with diagrams at all — I have never seen him voluntarily draw a software diagram in any fixed notation.
The issue has two causes. First, some people find diagrams helpful and some don’t; the mistake is to assume everyone should behave as you do. Second, diagrams get associated with heavyweight process, where a great deal of time is spent drawing things that don’t help and can do harm. The answer is not to discourage diagrams but to advise people on how to use them well.
The primary value of a diagram is communication, which means selecting what matters and neglecting the rest. That selectivity is the key to using the UML well. Don’t draw every class — only the important ones. For each class, don’t show every attribute and operation — only the important ones. Don’t write sequence diagrams for all use cases and scenarios — only the ones that matter. The code is the best source of comprehensive information; the common mistake is to try to make diagrams comprehensive, when comprehensiveness is the enemy of comprehensibility.
A common and legitimate use of diagrams is to explore a design before coding. XP does allow this, despite the impression some people have. It is worth getting a small group together for a quick design session on a sticky problem. When you do, keep the session short, don’t chase all the details, and treat the result as a sketch rather than a final design. You will inevitably find some aspects of the sketch are wrong only when you start coding. That is fine — provided you then change the design. The trouble comes when people think the design is done and ignore what they learn from coding.
Changing the design does not mean changing the diagrams. It is perfectly reasonable to draw diagrams that help you understand and then throw them away — the drawing helped, and that is enough to make it worthwhile. The best UML diagrams are not artifacts. Many XPers use CRC cards, which is not in conflict with the UML; I use a mixture of both depending on the task.
For ongoing documentation, the usual form — a model living in a CASE tool — often helps little. It takes too long to keep up to date, so it falls out of sync with the code, and it is hidden in the tool or a binder so nobody looks at it. The advice that follows is simple: keep only diagrams you can maintain without noticeable pain; put them where everyone can see them, such as on a wall, and encourage people to edit with a pen for small changes; and pay attention to whether anyone is using them — if not, throw them away.
For handover documentation, treat it as a user story like any other, whose value the customer decides. The UML is useful here if the diagrams stay selective, since the code remains the detailed repository and the diagrams serve to summarize and highlight key points.
The Metaphor Gap
I might as well say it publicly: I still haven’t got the hang of the metaphor thing. I saw it work on the C3 project, and work well, but that doesn’t mean I know how to do it, let alone explain it to others.
The XP practice of Metaphor builds on Ward Cunningham’s notion of a system of names — a vocabulary drawn from a well-known source that you use to talk about the domain and to name the classes and methods in the system. I have built such systems of names myself by forming a conceptual model of the domain with experts, using the UML or its predecessors. The discipline is to keep the notation minimal and to keep technical concerns out of the model. If you do that, the vocabulary can be one that domain experts understand and use to communicate with developers. The model won’t match the class design perfectly, but that is enough for a common vocabulary.
I don’t see why that vocabulary can’t be a metaphorical one, like turning payroll into a factory assembly line. But I don’t see why basing the names on the domain’s own vocabulary is a bad idea either, and I am reluctant to abandon a technique that works for me. People often criticize XP for lacking up-front design, and XPers answer that the metaphor covers it. But I still don’t think metaphor has been explained convincingly. That is a real gap in XP, and one the XPers need to sort out.
Leadership Without the Title
Over the last decade “software architect” became a popular term. I don’t use it myself — my wife is a structural engineer, and the relationship between engineers and architects is interesting. Her favorite line is that architects are good for the three B’s: bulbs, bushes, and birds. The notion is that architects produce the pretty drawings, but engineers make sure the thing stands up. In software, “architect” carries gravitas: “I’m not just a programmer, I’m an architect,” which often translates to “I’m too important to program.” That raises the question of whether separating yourself from the code is the right way to exercise technical leadership.
The question generates strong emotion; people get angry at the suggestion that XP has no place for experienced architects. But I don’t think XP dismisses experience or design skill. Many of its proponents — Beck, Martin, Cunningham — are people from whom I have learned a great deal about design. What changes is the role: technical leadership becomes a teaching role, not a decision-making one.
Consider Dave Rice, a technical leader at Thoughtworks on a fifty-person project. His role means spending time with all the programmers, helping those who need help, and seeing who does. He could have his pick of offices, but he chose to sit in the open bays with the programmers, in the war-room style XP favors, so that he sees what’s happening and can lend a hand where needed.
XP has a name for this role: the Coach. The meaning is clear — technical leadership is shown by teaching programmers and helping them make their own decisions. It requires people skills as much as technical ones. Jack Bolles noted at XP 2000 that there is little room left for the lone master; collaboration and teaching are key. Dave and I once talked with a vocal opponent of XP at a conference dinner, and the similarities in our approaches were striking — we all liked iterative, adaptive development, and testing mattered. Then he said, “the last thing I want is my programmers refactoring and monkeying around with the design.” Everything became clear. Dave’s comment afterward was sharp: if he doesn’t trust his programmers, why does he hire them? The most important thing an experienced developer can do is pass on skills to others. Instead of an architect who makes all the key decisions, you get a coach who teaches developers to make them—and as Ward Cunningham pointed out, that amplifies the coach’s skills, contributing more than any lone hero can.
Evolutionary Design and the Cost of Decisions
Enrico Zaninotto's talk at XP 2002 drew an instructive parallel between agile methods and lean manufacturing: both reduce complexity by reducing irreversibility. When decisions are easy to change, the pressure to get them right the first time drops sharply. Evolutionary designers, therefore, should seek to postpone decisions until more information is available, or to make them in ways that leave room for reversal later. This is a large part of why agile teams invest so heavily in version control: not because it guarantees reversibility — especially for decisions with long lifetimes — but because the safety net gives a team the confidence to move forward.
Supporting reversibility also means making errors visible early. Iterative development lets customers watch a system grow, so requirements mistakes surface while they are still cheap to fix. The same logic applies to design. Teams should set up risky areas for rapid testing and should not hesitate to run throwaway experiments — for example, prototyping a future change on a branch just to gauge how difficult it would be, without committing to the change itself.
Design Needs a Keeper
Technical practices alone will not keep evolutionary design on course. It needs a human driving force strong enough to make the design converge rather than drift. Someone — usually one or two people who take on the architect's role — must accept the responsibility of watching the code base, spotting areas that are becoming messy, and making sure they get cleaned up. That person does not have to make every fix, but must ensure someone does. When this will to design is missing, even teams that know all the techniques in this article will watch their designs fail to take hold.
The Hard Question: What Can't Be Refactored In?
Are there design concerns so pervasive that they cannot be deferred? XP orthodoxy holds that they can be added when truly needed, so YAGNI applies universally. Internationalization is the classic contested example. The evidence base for a definitive answer is thin, and reasoning on this topic is easily skewed: adding something late makes the effort vivid, while the cumulative cost of building and maintaining it early stays invisible. Early additions may also end up wrong in the details, requiring refactoring anyway.
The YAGNI argument rests partly on how often speculative needs turn out not to match reality, and partly on refactoring the simple solution into the real one being cheaper than maintaining the speculative structures in the meantime. A further consideration is experience: if you have internationalized several systems, you know the patterns, and an early commitment is easier to judge and more likely to succeed. If you are new to the problem, you cannot assess the costs reliably and are likely to implement it poorly — in that case, defer it. Adding it later with a more seasoned team, deeper domain knowledge, and clearer requirements may well be less painful than struggling with an early guess. Hindsight makes it tempting to assume the early path would have been easy, but it often was not.
This debate connects to story-ordering. Kent Beck favors business value as the sole driver, a view Ron Jeffries came to share; I still hold that technical risk deserves a voice in the balance, which would push at least some internationalization work earlier when the feature is needed for the first release. If it is not needed until later, however, do whatever brings that first release sooner. Shipped code focuses attention, earns credibility, and teaches more than any internal milestone, so the extra cost of adding something after release is a fair price to pay.
Advocates of any technique naturally lack clarity about its boundaries. Most XPers have heard that evolutionary design cannot work in some context, only to find it can. Conquering such "impossible" cases breeds confidence that all cases yield — an overgeneralization, to be sure, but the community will not know where the real limits are until it pushes against them and fails. Notably, Jim Shore's recent work on internationalization and similar supposedly intractable issues found the barriers were imagined, not real.
How Can Anyone Tell Design Is Happening?
Because design is intermingled with programming, it is hard to tell whether design is happening at all. Programming can certainly proceed without it, and that is precisely when evolutionary design diverges and collapses. Developers sense this in the code base — rising complexity and diminishing workability signal insufficient design — but that judgment is subjective, and objective metrics for design quality remain elusive. For non-technical stakeholders, the problem is worse, since design quality directly affects future modification costs. Two signals can help.
- Listen to the technical team. Complaints about the difficulty of making changes are legitimate warnings; take them seriously and allow time to fix the underlying problems.
- Watch for deleted code. Healthy refactoring steadily removes bad code; if nothing is ever deleted, refactoring is probably not happening and the design is degrading. Like any metric, this can be gamed, so trust good people over numbers.
Not Dead, but Different
Evolutionary design still demands a fearsome set of skills, and it does not make the designer's job any easier. The skills it values are these:
- A constant desire to keep code clear and simple.
- Confident refactoring skill, applied whenever the need appears.
- Real pattern knowledge — solutions, plus when to apply them and how to evolve into them.
- Designing with future change in mind, knowing today's decisions will be revisited.
- Communicating design through code, diagrams, and above all conversation.
What has changed is that design is again a plausible evolutionary strategy. That is reason enough to keep pursuing it.



