What's the single most important activity your team can perform to get your project moving along? An automated and repeatable build! Why? Because an automated and repeatable build produces the only artifact that really matters to everyone associated with the project: the executable application. Certainly, a team may produce and use other valuable artifacts. But all those pretty documents will be quickly forgotten if you don't deliver the final product.
Defining the Build
Embodied in Extreme Programming's Continuous Integration practice, an automated and repeatable build can work wonders for your development team. First and foremost, it forces you to integrate early and often. You're guaranteed to always have a system that works. Of course, you need to follow two rules. First, all compile errors that surface must be resolved immediately. Since you should be using a version control system, such as CVS, this means that the CVS projects comprising your application should always be free of any compilation errors. Second, any unit test that fails must be immediately fixed. Unit and acceptance tests should be run after each change you make, and you should treat any test failure with the same urgency you'd give a compile error. While simple, these two rules are key elements because they're part of what defines an automated and repeatable build.
Inspired by Martin Fowler and Matthew Foemmel's take on Continuous Integration, here, in order, are the steps that define an automated and repeatable build.
- The build must be a clean compile, with no compiler-generated syntactical errors. While you may encounter certain warnings, such as those generated by deprecated method usage, you should strive to remove them before future builds.
- The build should be done using the most current version of all source files. The source should be pulled from a central version control repository, not from a developer's workstation. Occasionally, you'll want to compile older versions of code to revert to a previous version. Typically, when compiling these older versions, you'll use the source files consistent with the version of the application you're building.
- A clean build implies that all application source files are compiled. Conditional compiles or partial builds don't prove the syntactical correctness of the entire application.
- The build should generate all files representing the units of deployment. For Java applications, this means all JAR, WAR and EAR files should be created.
- It should verify that the application is functionally correct, which means that all unit tests for the application should be run. If they all pass, the build is successful.
- Upon completing a successful build, it's good practice to deploy it to a development region to be further verified by developers and reviewed by any interested stakeholders beyond the development team.
Frequency of the Build
How often should your team perform the build? Frequency is really a product of your development team and environment. As part of a team that performs an hourly build, I've found a great deal of comfort in knowing that the application is always in a functional state. But I've found that daily builds also prove successful. I wouldn't recommend going more than a day without performing a build, however, because errors—compile errors, test case failures and the like—will probably creep in, and you'll wind up devoting more time to correcting bugs than growing the system.
Keep in mind that if your build ever fails due to a compilation error or a failed test case, your team must fix the problem immediately before adding any new functionality. So, daily multiple builds are beneficial, since they reduce the periods of time between builds that allow abundant bugs to surface. Of course, you're still at the mercy of developers who choose to work extended hours without releasing their changes to a version control system. Try to establish ground rules advocating that developers release early and often, preferably multiple times per day. Enforcing the rule that failed builds must become the team's immediate focus establishes a peer review system that usually works well. Like most developers, I'd prefer to avoid the guilt of knowing that I'm the reason the team must quit working to correct errors.
Promoting Agility
Aside from continuously proving integration, an automated build facilitates a number of other very useful practices.
The build can be performed at any time. Ideally, you should schedule the build to run at certain times throughout the day or each time code is checked into a version control system. With some experimentation, you'll find what works best for your development team. Remember that the build is repeatable, so you can also run it on demand.
The build process is consistent. Because it's automated and repeatable using a build tool such as Ant, you're confident that the build will be run the same way each time. Without an automated build, you can't guarantee that a developer won't forget to do something important. You should rarely consider using a development tool's export utility to generate your deployable unit. Too much manual intervention is typically required, leaving the door open for problems.
No compile errors are present. Even though most project teams are under incredible pressure to deliver, piling code that doesn't work on top of more code that doesn't work is an all-too-common practice. An automated and repeatable build adhering to the rules above ensures that this won't happen.
The code is verified and always functionally correct. With a robust build process, unit and acceptance tests are run immediately after the source has been compiled successfully. Most developers learned long ago that just because something compiles, there's no guarantee that it works. Unfortunately, though we may have learned that truth, we don't always practice it. A variety of tools can help you write high-quality test cases to verify your code, including JUnit and FitNesse.
The physical dependency structure can be enforced. Builds can be performed in different ways. You can construct the entire application with all source code in the classpath—a useful technique. But you can also perform a levelized build, whereby those deployable units with no dependencies are built first, followed by those components that have dependencies on previously compiled components. A levelized build is valuable because it can enforce the dependency structure of your application components. If an undesirable dependency surfaces, the build will fail. Another option is to write JDepend test cases that validate the package dependency relationships between the deployable units.
The build can drive other important lifecycle activities. Frequent customer feedback is essential to ensure that you give customers what they need, instead of what they ask for. If you have a system that works, it's much easier to get early and frequent feedback. To garner feedback from your customers, show them the system and let them experiment by simulating their work. Frequent system demos involving customers and developers are a great way to facilitate discussion and iron out any wrinkles associated with especially tricky or complex areas of the application. Full system demos help everyone stay in tune with the project's overall vision, especially as some folks become detached as they drown themselves in the details of a specific use case or subsystem.
Objective feedback can be generated. Build tools, such as Ant, often have tasks that allow you to analyze your code base. For instance, JDepend and JavaNCSS can offer feedback that lets you analyze the quality of your source code.
Code reviews can be automated. Other build tools, such as PMD, can eliminate mundane and often useless code reviews. Tools like this can scan your source code and identify potential problems, or violations of rules that you define.
The way you develop software remains consistent, even after you deploy. Hopefully, you'll eventually release your application in a production environment. Once you do, continue to perform the build just as you always did. This makes maintenance much easier since you don't have to radically redefine how you're working. In fact, every line of code you write after the first is maintenance—so don't treat maintenance as a phase of a macro lifecycle; respect it as a way to grow and maintain your application.
A frequently run build process promotes iterative development. You develop, test, integrate and deploy every time you build, helping you avoid the big bang—you know, that noise you hear when your application explodes as you try to move it from a local development environment to a shared server, or integrate with another developer's work after a long solo period. Instead, integration is continuous, performed each time you execute the build.
A frequently run build process is a grassroots approach to agility. Advocating for an automated build process probably won't spur much controversy, so it's doubtful you'll encounter resistance from management or process purists along the way. In fact, everyone knows that at some point you have to compile and deploy the application, so nobody views it as a threat to the old-school approach to software development. I've never encountered anyone balking at the thought of creating build scripts early in the project. And once the build is working, it's hard to deny the benefits associated with each of the previous points.
In fact, all of the previous points are steps toward a more agile process that doesn't make you wait until a few weeks before the ship date to integrate or roll the application out for testing. Instead, you roll it out early and often.
An automated and repeatable build is a valuable asset for any development team, enabling a number of other useful lifecycle activities. Instead of wasting valuable time debating the merits of XP, RUP or your other favorite process, devote your efforts to creating and refining your build process. Then use the build to perform other activities that make sense for your project. An automated, repeatable build is a marvelously apolitical creature that works—and works well—regardless of methodology.
Acknowledgment:
I'd like to thank Robert C. Martin for his thoughtful input that led to a much improved article.
Kirk Knoernschild is a hands-on software consultant and author of Java Design: Objects, UML and Process (Addison-Wesley, 2001). He frequently updates his website, www.kirkk.com, with new articles and whitepapers on a variety of software development topics and is the founder of www.extensiblejava.com. Contact him at [email protected].