I’ve heard people say that you don’t need to do much architecture and design on Agile projects. In my experience this is not true. Because of the high degree of change, Agile projects require that attention is paid to architecture and design throughout development.
But the way we do architecture and design in agility does have some key differences to the way it is done in a waterfall approach. In waterfall we assume that we have all the requirements for a project up front and we can optimize our designs for those requirements. This is usually a false assumption and gets us into trouble when we realize that we left out a critical requirement that the current design does not handle.
Even if upfront architecture and design is able to handle all the features of the first release in a project we often find the need for a major redesign to build the next release of the product because the current design is not able to handle the features of the next version.
If we acknowledge that successful software will inevitably need to change, then somehow our designs must be flexible so that our code is easy to change. But how do we do that?
Instead of focusing a lot of effort up front on architecture and design, Agile practices say to approach these issues as minimalists looking for the simplest design that will not get us into trouble later. It turns out that we can defer design decisions for many things until we have to build them. The benefit is that we’ll know more later on and probably make better decisions.
So it is a good idea to put off important decisions until later, but this is not universally true. Knowing what decisions have to be made up front and what can be deferred is part of the art of Agile architecture. However, I’ve found a simple rule of thumb that can offer guidance on this.
This piece of advice that I’m going to give you has saved me more times than I can count. I live by it. It is very valuable, but like most advice is also very lofty. It won’t magically solve all of your problems, but I never found anything that could. What it can do is help you focus on opportunities that you might otherwise have missed to make your designs more expendable.
Here it is: As a rule of thumb, if you can encapsulate an issue, you can defer on it without paying a big price later when you want to change it. It turns out that there are many ways to encapsulate an issue if you think of encapsulation as more than just hiding data.
We will be discussing many ways to encapsulate different kinds of issues in upcoming posts, articles and rants. In fact, I believe that in addition to fulfilling the specification, a good design must also encapsulate as much as possible, so that when something needs to change, the impact to existing code is minimal.
For example, accessor and mutator methods are used to encapsulate data within an object. If we follow the simple convention of always providing a public get() and set() method for every data item in an object, we are then free to change how we store the data item without affecting any callers who use the get() and set() methods.
In a similar way, we can encapsulate a concept using an abstraction. For example, if we currently encrypt a message one way and want to be able to encrypt the message a different way later, we can create an encryptor abstract class that would allow all callers to use all encryptors in the same way, so that they would have no dependencies on any specific encryptor.
This has two advantages: if you set things up right then you can add new encryptors in the future with minimal code change, and it allows us to code callers so they are completely unaware of which specific implementation they are using. This lets us add new variations with minimal impact on the system.
But the biggest problems I’ve seen are not where there is a missing abstraction in a design, but rather a missing paradigm. For example, have you ever tried to internationalize a program after it is mostly written? It is very difficult. It can also be very difficult to switch platforms or user interfaces in applications that have not abstracted this layer.
If you’re writing an application that you know will only be used on a single platform then perhaps it’s over-design to spend time further abstracting the GUI. If you think it is likely that you will want to run on different platforms, then using a language or framework that supports this would be wise.
There obviously must be some thought given when architecting a system. Many successful Agile software-development projects do a lot of upfront architecture and design; they just call it by a different name.
Instead of writing long-winded specifications, Agile architects often play the role of product champion, defining stories and acceptance tests that specify system behavior. Developers can then build their unit tests from these acceptance tests.
I know that I am using the word “test” here, as in Test Driven Development, but these are not the kind of verification tests that QA runs. We call them tests, but they are actually more a kind of “executable specification.”
The very nature of iterative development lends itself to letting a design emerge organically. There is a lot to be said for finding the quickest path to the greatest value. If the greatest value for a project is generating a particular kind of report, then identifying the information needed to generate the report, and how to get into the system, might represent a good starting path.
However, I believe, on very large projects or projects that lend themselves to a lot of code reuse or common tools, it can be beneficial to establish efforts in building specific tools or libraries needed for a project. I don’t think the agile community at large has fully worked this out yet. Especially on large software-development projects, Scrum and Agile seem to be missing a few key distinctions, in my opinion. I’ve seen large agile projects succeed but mostly due to the outstanding efforts of a few superstars with forethought.
So what can we take away from this discussion?
Architecture and design still exist in Agile projects, but the way we do it is different than traditional waterfall development. Agile projects tend to:
• Defer making decisions until they are needed
• Constantly strive to provide value to the customer from Day 1
• Drive development from acceptance tests and unit tests
• Use TDD to create “living specifications”
• Seek to encapsulate as much as possible
• Know what decisions cannot be easily encapsulated and therefore must be made up front
What do you think? I’d love to hear your thoughts, ideas and suggestions. Please feel free to share your comments below. I plan to explore these and other ideas further in future posts.