In a recent talk on architecture and agile development, Philippe Kruchten told his listeners that refactoring can grow out of control when people decide not to do up-front design and instead decide to let the "architecture grow with the code", as many agile software developers like to see it.
Let me repeat his (real-life) example:
Agile Development by the Book On one big project, where Philippe got involved in, the teams were doing agile development by the book: one-week iterations, daily stand-up meetings, iteration planning with user stories, retrospectives, and regular refactoring to change and improve the design of the code. However, despite the fact that the teams were doing just about everything that the agile gods have prescribed, one problem just got bigger and bigger:
The time needed to do refactoring grew with the size of the code.
It appeared that, while the code base grew over time, so did the time needed to refactor the code to accommodate new features. Apparently, the teams were unable to stabilize the architecture. Halfway through the project the iterations had to be lengthened to three weeks just to be able to finish refactorings. And in the end the project simply ground to a complete stop.
What went wrong there?
Should We Not Grow Architecture with the Code? Philippe Kruchten's interpretation was that it is not enough just to do refactoring and let "the architecture grow with the code". He told us that some work in up-front architectural features is essential. He does not believe you can go on by just adapting all the time. Philippe says that the principle of deferring decisions does not hold true in every case. You also have to anticipate, or else in the end you will spend much of your time reworking everything.
However, my own interpretation is a little different. (This might not be a surprise to my regular readers, as I rarely seem to agree with any of the experts. I can't help it. It's just a gift, I'm sure.) I believe that, most of the time, people simply use the wrong approach to the idea of "deferring decisions". Let me give you an example:
How to Defer Decisions When I'm building a new application, I will have to decide whether my application is going to be multilingual or not. However, it may be better for me to defer that decision. But if I do, then what shall I do with all those form labels, button texts and abusive error messages that I need to put into my code now? I believe the wrong approach in this case is to simply use static strings all over my code base. Because if I do, I will not seriously have deferred the decision. I will have (unconsciously) favored one outcome of the deferred decision over the other. Because I can be certain that it's going to take a lot of effort to refactor my code later, when the decision finally falls to make the application multilingual. In fact, chances are that it will be too costly to change later, and I will have painted myself into a corner.
To properly defer a decision requires you to (consciously) keep all options open.
What you could do in such a situation is to create a simple factory method that provides strings to the entire application. All button texts, form labels, page titles and silly easter eggs will get their strings from that simple function. Of course, it would still be a single-language static-string solution. The implementation might be one huge ugly motherf*** of a switch statement concealing 10,000 strings! But at least it would be an easy monster to refactor later on in the project.
Philippe explained that…
"Architecture is any binding decision in your code that is hard to change later on."
To that I would like to add…
"The choice not to plan for a deferred decision, is also an architectural decision."
Deferred Decisions vs. Architectural Decisions I don't know the details of the big project that Philippe was referring to. But I wouldn't be surprised to learn that most of their ever-growing refactorings were a result of not properly applying the principle of deferring decisions. Choosing not to hide the details of string construction from the rest of the code is an architectural decision, that will make it hard for you to switch to multiple languages later on. Choosing not to hide the intricacies of the data layer is also a decision. It will make it almost impossible to add versioning to the data later. And choosing not to hide the implementation of user notifications will mean that a switch from email to SMS or paging is also going to be a lot of work.
The choice not to safeguard your code against the outcome of deferred decisions is an architectural decision in itself. What's interesting is whether you're making those decisions consciously or not!
It Takes a Decision to Defer a Decision So you see, I agree with Philippe that refactoring in itself is not enough. You have to anticipate and make conscious architectural decisions to let refactoring work for you. But I don't agree with Philippe that "growing the architecture with the code" is somehow not enough. Sure it can be enough.
The problem is not lack of up-front design. It is lack of planning for deferred decisions.
If you understand how to keep your options open, and make the necessary architectural decisions consciously, then I think all should be fine!
Afterword: I just got an email from Philippe Kruchten himself. According to Philippe we're actually "in furious agreement". But I worked on this bloody post for at least two hours, so I'm definately not going to delete it…