In lieu of adopting in complete form an Agile software development process, such as Scrum, XP, or Crystal, injecting practices can help ease the pain. Such an approach makes an agile transition easier, less risky, and ultimately more beneficial.
These practices take a very narrow view. If you develop a codebase that is infinitely malleable, fully tested, and frequently deployed, your team possesses the ability to adapt. By emphasizing creation, growth, verification, and deployment of your code from initial project inception to final solution delivery, these practices serve as the foundation of an agile environment.
To realize the greatest benefit from these practices, they should be adapted based on the constraints, attitude, and culture of your social and technology environment. After injecting and adapting these practices, allow them to evolve in a way that helps your team continue to increase agility.
Automated and Repeatable Build
The most important artifact produced by the software development team is the source code. It is the only reliable measurement depicting the current state of the application. If the source doesn’t compile, the application is not functional. Building on a regular schedule, such as hourly, helps ensure the team is on track. Incorporating e-mail notification into your build process informs all developers of the status of the build. Additionally, producing a build website allows the team to review important statistical information related to the application such as design quality, code quality, and percentage of code under test.
Continuous Integration is a strategy employed by teams to deliver functional software as frequently as possible. The cornerstone of a robust continuous integration strategy is an automated and repeatable build.
The build must be repeatable. Performing a local build in an integrated development environment (IDE) is a great way for developers to analyze the isolated impact of their changes, but it is no way to build, package and deploy an application in a team environment. Local builds are a manually intensive process performed by individual developers with no guarantee of consistency. Skipping a step, working with older copies of source or incorrectly performing a build task are common mistakes that can result in a corrupt application. A repeatable build helps solve these problems by ensuring the steps executed to build, package, and deploy the application are performed consistently each time. Fundamentally, a repeatable build consists of at least the following:
- It must be a clean compile with no syntax errors.
- It must be performed on the most current version of all source files.
- All application source files are compiled.
- The build should generate all units of deployment.
- The build should execute all relevant tests successfully.
Depending on your platform, tools are readily available that allow you to develop a repeatable build script. Ant is the most popular utility for J2EE development.
The build must be automated. Even with a repeatable build, time must be spent manually invoking the build process. Eliminating waste is a core tenet of successful agile teams, and that which is repeatable can be automated. Build automation frameworks allow you to schedule the build so that it is run at predictable intervals. Frequency of the build is a product of team dynamics. For smaller teams, it’s useful to execute the build each time a change to the source code is released to the source repository. Larger teams may choose to execute the build according to a defined schedule, such as hourly, due to the heavy volume of released code. Project teams should experiment with frequency to address their specific needs.
CruiseControl is an example of a build automation framework for the J2EE platform that sits atop an Ant build script and executes the script based on configuration. Maven is a software management tool that helps manage the build, while also generating feedback aimed to provide the development team with additional project information.
The build must have a supporting infrastructure. In addition to build and automation tools, additional supporting infrastructure must be in place to support an automated and repeatable build.
Extending the Build
The build should be extensible. Once an automated and repeatable build with supporting infrastructure is in place, the team can think about other aspects of the development lifecycle that can be automated. Generating quality, dependency and coverage metrics metrics as part of a build process and subsequently publishing the results to a website or pushing the information to developers via e-mail gives team members the type of rapid feedback they need.
An automated and repeatable build is the cornerstone of any successful agile development team, enabling a plethora of important lifecycle activities. An agile environment is a living and breathing entity where progress is tracked in minutes, not weeks and months. Here are a few of the advantages you’ll realize as the result of an automated and repeatable build.
- Incremental Growth. Iterative methods advocate incremental growth of the software system. Whereas some less agile methods encourage iteration plans attempting to predict incremental growth, agile teams experience incremental growth daily.
- Frequent Deployment. Early and frequent deployment to a shared environment avoids integration risk, and offers the team the opportunity to tweak application parameters and execute tests that are best suited for a shared environment. Delaying deployment increases the likelihood of experiencing configuration problems or application packaging issues.
- Functional Demonstrations. Early in the software lifecycle, most development teams aim to understand user requirements. Typically, meetings are held that emphasize driving out these requirements and documenting them based on discussions. As the software grows, functional demonstrations can be used to help gain additional insight to requirements in a different context. Regularly scheduled prototypes and functional demos involving team members and business clients ensure that all team members remain in touch with the core business objectives surrounding the development effort. For the development team, prototypes and demos offer insight into other processes that developers may not be intimately familiar with. For business clients, it gives them a complete view of the integrated system. It’s important to begin hosting prototypes and demos early in the development lifecycle to help identify inconsistencies across processes, system navigation and workflow issues, and duplication of effort. Wire frames and screen mock-ups can be used as prototypes before a functional codebase is available. As the system grows, functional demos replace the prototypes.
- Constant Feedback. Extending the build with coverage, design and quality metrics allows the team to gain feedback on the integrity of the software. Potential areas of concern can be identified and corrected immediately upon discovery. Additionally, because the build is scheduled, teams can track progress and setback trends.
- Lifecycle Automation. An automated and repeatable build automates many of the mundane tasks previously requiring manual intervention. Build, tests, packaging, and deployment are automated and therefore occur regularly and often times without manual intervention.
- Adaptability. Your build serves as a system of checks and balances for the development team. Tests provide the courage to refactor. Refactoring is validated by frequent builds. And builds are deployed to a shared environment where they can be further tested and verified. The combination of these factors contributes to an environment where change is more easily accommodated.
Software tends to rot over time. When you establish your initial vision for the software’s design and architecture, you imagine a system that is easy to modify, extend, and maintain. Unfortunately, as time passes, changes trickle in that exercise your design in unexpected ways. Each change begins to resemble nothing more than another hack, until finally the system becomes a tangled web of code that few developers care to venture through. The most common cause of rotting software is tightly coupled code with excessive dependencies. For large teams and large applications, managing dependencies is especially important. Excessive dependencies cause numerous problems.
- Hinder Maintenance. Dependencies hinder the maintenance effort. When you’re working on a system with heavy dependencies, you typically find that changes in one area of the application trickle to many other areas of the application. In some cases, this cannot be avoided. For instance, when you add a column to a database table that must be displayed on the user interface, you’ll be forced to modify at least the data access and user interface layers. Such a scenario is mostly inevitable. However, applications with a well thought dependency structure make change easier since developers have a more complete understanding of the impact of change.
- Prevent Extensibility. The goal of flexible software architecture is to remain open for extension but closed to modification. The desire is to add new functionality to the system by extending existing abstractions, and plugging these extensions into the existing system without making rampant modifications. One reason for heavy dependencies is the improper use of abstraction, and those cases where abstractions are not present are areas that are difficult to extend. Abstraction aids large teams by exposing well-defined extension points, as well as encapsulating implementation complexity.
- Inhibit Reusability. Reuse is often touted as a fundamental advantage of well-designed object oriented software. Unfortunately, few applications realize this benefit. Too often, we emphasize class level reuse. To achieve higher levels of reuse, careful consideration must also be given to the package structure and deployable unit structure. Software with complex package and physical dependencies minimize the likelihood of achieving higher degrees of reuse.
- Restrict Testability. Tight coupling between classes eliminates the ability to test classes independently. Unit testing is a fundamental principle that should be employed by all developers. Tests provide you the courage to improve your designs, knowing flaws will be caught by unit tests. They also help you design proactively and discourage undesirable dependencies. Heavy dependencies do not allow you to test software modules independently. Teams with few tests cannot respond to change easily due to the inability to access the impact of change.
- Hamper Integration. It’s easy for separate teams to plow forward, usually under tremendous pressure from looming deadlines. They operate under the false assumption that if they can simply reach the final feature destination, they can quickly pull things together toward the end of a project. This Big Bang approach to integration does not work. As individual modules are pulled together, common issues that surface include degradation of overall system performance, incorrect levels of behavioral granularity provided by system modules, and transactional incompatibilities. More frequent integration brings many of these issues to the forefront earlier in the project.
- Limit Understanding. When working on a software system, it’s important that you understand the system’s structural architecture and design constructs. A structure with complex dependencies is inherently more difficult to understand. Clear and concise dependencies that are well-thought allow teams to more easily access the impact of change.