When you form a startup business, it’s a good strategy to spend money slowly, with the goal of generating revenue quickly. This is also a great one-line summary of the Agile method of software development. If time is your currency, spend it on only the most important tasks, and get something into production early to serve your users. This is well understood. But I still often hear the question, “Where does the software design phase fit into the Agile development process?” This is the topic I’ll explore here, especially as it relates to Java development.
Extreme Programming
With two-week or three-week sprints, it may seem challenging to find time for proper software architecture and design. To answer this challenge, I turn to a precursor of Agile, Extreme Programming (XP), as described in Kent Beck’s book, Extreme Programming Explained. In his book, Beck suggests a design strategy with goals that are aligned with both the process and the overall project goals. As far as I can see, Agile agrees with this strategy.
Just as it’s a myth that there’s little to no planning with the Agile development process, it’s a myth that Agile doesn’t account for design. For example, I’ve been part of more planning and retrospective meetings with projects that follow the Agile process than with any other process. Agile has enabled me to continually think about and consider software design with each sprint. This is quite the opposite of the waterfall software development process, where you consider design only once early in the process.
The notion of “sprint zero,” to me, is another myth. By definition, taking an arbitrary amount of time at the beginning of a project to design or build a backlog isn’t a sprint and runs counter to Agile. It’s effectively water-scrum-fall, a compromise.
There’s a better way to do Agile, and that’s to focus on simplicity.
Some things are inherently complex by their nature, but often complexity has simplicity at its base. The theory for design in Agile is to have just enough design to meet your immediate goals.
I believe in this approach, but I’ll admit there is a risk: If your design is too simple, technical debt can accumulate. There may be sprints where a concerted, collaborative effort is needed to make larger design decisions. However, it’s important to ensure that you’re always doing just the amount of design that’s needed.
Here are some strategies to employ; some are general, but some are Java-specific.
Have a continuous design strategy
It’s best to have a design strategy that carries through to each sprint. The strategy should enforce design concepts that are in line with the project goals and are easily communicated to team members. I’m in favor of small bouts of design with each sprint to continually improve the project structure. However, design is not an ad hoc process. The following elements of a good design strategy should be reinforced with each sprint:
◉ Reduce dependencies. Changes should be localized whenever possible, thus limiting their potential adverse effects.
◉ Consider cross-cutting facilities. Grow the common codebase as more features and components are added over time.
◉ Minimize abstraction. Add abstractions only as needed. (A funny story is recounted in Too Blue! by Dennis Andrews: In the early days of the IBM PC, if IBM identified the need for someone to begin a new role, instead of hiring just one person, the company would start a whole new department in anticipation of growing demand.)
◉ Choose simplicity. To handle both current and future needs, go with simple designs that are easily communicated.
◉ Emphasize collaboration. Communicate intent as you design and code for each sprint. If you cannot easily explain what you plan, consider a simpler approach.
◉ Define the test stories. Stories should include user requirements, new feature descriptions, and detailed directions on how to test the features. After all, if you cannot describe how to test something, how can you be sure you’re properly describing what is to be built? Usually when you describe the test process, you consider how to implement the feature accurately. The result will be a design that’s complete but also concise.
Agile, design, Java, and you
Good software design is key to long-term project success and to maintaining team morale, no matter the language or platform. Still, there are some approaches that apply particularly well to Java projects.
Encourage experimentation. Your Agile process needs to account for experimentation and even encourage it—not on every sprint, perhaps, but certainly experimentation can be helpful throughout the development lifecycle.
For example, the Java ecosystem is rich, and this means you may need to consider the use of one open source or commercial package among many, such as which NoSQL database to use or which storage paradigm to adopt. To make the best decisions, you might need to try the alternatives. This suggests dedicating a sprint, now and then, to conduct experiments.
That said, be realistic. Imagine a possible design decision to build your own NoSQL database for future flexibility. Experimenting with building a NoSQL database engine is likely not the best use of time.
Whereas past development practices stressed designing your own core capabilities to anticipate potential possibilities, Agile doesn’t necessarily support this. Something unanticipated may come along, such as the rise in graph databases, which better suits your application and data. If you built your own NoSQL database, switching means you have wasted all that effort. Or worse, making a big investment writing, debugging, and tuning a NoSQL database engine may delay the move to a graph database that’s perhaps a superior choice, due to an all-too-human desire to justify your prior investment.
Consider design by contract. Java interfaces are used to define contracts, and many developers know this. Contracts serve as a layer of abstraction, because they help break dependencies between discrete classes. In other words, if you write your code to interfaces, you can change the implementing classes without affecting those that use them. This is known as interface decoupling and is directly in line with Agile’s rule of simplicity, with a goal of limiting the side effects of code changes.
For example, say you are implementing an automated barista for a very busy coffee shop. There are different types of beans to use and different brewing methods depending on the order. You can write the Barista class to use different classes directly, each representing the different coffee beans and makers. It is better to abstract the implementations using interfaces: CoffeeBean and CoffeeMaker.
Although the details within the classes are different, each class implements the same set of methods. Therefore, the correct classes can be supplied to the Barista constructor, see the two lines that begin with the keyword new.
public class Barista {
CoffeeBean coffeeBeans = null;
CoffeeMaker coffeeMaker = null;
public Barista(CoffeeBean beans, CoffeeMaker maker) {
// …
}
public boolean brew() {
coffeeMaker.brew( coffeeBeans );
}
}
// …
Barista barista = new Barista(
new EspressoBeans(),
new EspressoMaker() );
barista.brew();
The result is a system that’s easy to extend, sprint by sprint, to automate the creation of new coffee drinks.
Avoid cyclic dependencies. Interface decoupling can result in risky dependencies, especially when you’re building software in small, rapid changes.
For example, consider a project that uses four Java packages: A, B, C, and D. Package A has dependencies on packages B and C. Package B has a dependency on package C. Package D has a dependency on Package B (see Figure 1).
In this scenario, what if package C later adds a dependency on package D? You have a nasty cyclic dependency between them.
0 comments:
Post a Comment