Continuous Integration

As a codebase grows, teams frequently submit working source code to a database called a version control system. Every time source becomes sufficiently better that others should use it, even indirectly, it should merge with the version controller’s database.

Some version controllers force developers to acquire exclusive locks on files before changing them. Configure your version controller to leave all files checked out all the time. No engineer should hesitate, or interrupt their Flow, before changing a file.

When the code improves enough for others to use, integrate following these steps:

run all tests

merge your source with the most recent integrated version

run all tests

commit your recent changes into the database

run all tests

announce you have checked in a new change.

If any step requires excessive keystrokes or mouse abuse, write scripts to automate environmental manipulations. Only novices need GUIs; they are training wheels for tools. Engineers take them off and ride.

Some teams require members to declare Step 1, using a mutually exclusive semaphore. Before checking in a change, engineers must move to an integration workstation, or obtain a unique token, such as an Official Rubber Integration Chicken.

Steps 1, 3, and 5 share the same script. If it takes too long to run, seek and remove performance bottlenecks.

If Step 1 fails, you are not ready to integrate.

If the same source files change at different workstations, most version control systems, at Step 2, will merge the changes together. If the same line changes on two workstations, this merge system breaks, and the version control system demands manual intervention.

If Steps 1, 2 or 3 fail, you have a choice:

debug the failing test

erase your own changes, get a fresh copy, and start your changes again.

No other Agile practice advises debugging a failing test, so now is not a good time to start. If the problem isn’t simple and obvious, then the choice “erase your recent changes” sounds a little counterproductive. Minimizing exposure to this loss is more productive than the alternatives.

The longer you delay each integration, the higher all integrations cost.

Small frequent integrations always accumulate less cost than large sporadic integrations. Delayed integration increases risks of conflicts, broken builds, or failing tests, at Step 3. Delayed integration raises the cost of throwing your local changes away, or even of copying them out to a scratch folder.

The more often a team integrates, the less likely become failures at Step 3. Each small change that leaves code demonstrably better deserves a check-in. Engineers should leverage the ability to play with source, attempt some experimental changes, and then discard them by erasing local files and downloading fresh copies. Frequently committing valid versions creates checkpoints that future bulk Undo operations can return to.

Test-First Programming enables personal Flow by reinforcing a mental model of a module’s state. Continuous Integration enables team Flow by reinforcing a collective mental model of a project’s state. So failing tests at Step 3 are a Good Thing—they efficiently warned you to stop developing, and to reevaluate your mental model. If the code only changed a little, the problem might be obvious enough to fix right away.

You always have the option to erase your own changes, get a fresh copy of the project source, and make your changes again. You should have only a few changes to make, they will be easier this next time around, and you will remember to integrate them more often.

To help everyone remember, automate Step 6. Many teams use build script systems (such as Java’s ANT) that can play a sound at integration time. Any build script could call a command line program to play a sound, too. If everyone sits together (and doesn’t listen to music on earphones), they will hear your integration event. They know their changes now live on borrowed time, so they will seek the next opportunity to integrate, too. And you will hear their sounds.

Contraindicating Integration

Not all code changes deserve instant immortality. Some code changes might contain embarrassing GUI experiments that not all reviewers would catch. Programmers must quarantine their workstations until finishing their experiments, and then clean their effects out of the code.

Never fork a codebase to create a Production Build. Fork a codebase to experiment, then erase the fork, and apply what you learned to the real codebase. Don’t integrate aggressive experiments.

Page 268 demonstrates this effect. We change a project experimentally, producing a display bug that only people fluent in a complex Asian language would recognize. If we integrated, then forgot to redact the experiment, not all of our colleagues could catch the error. In my experience, healthy software engineering lifecycles rely on forgetfulness.

Don’t integrate “Spike Solutions”. Normal development depends on many minor experiments. These must chronically remain ready for integration.

Don’t Leave reveal() Turned on

When you activate a Temporary Visual Inspection or Interactive Test, you need to forget integration. When you integrate, you need to forget any temporary windows you left turned on. Pass your USERNAME into the temporary window fixture, such as revealFor("phlip"). That fixture should match this name to the USERNAME environmental variable to trigger a display. The Case Study NanoCppUnit demonstrates this technique, on page 206.

An aggressive coding session, while adding a feature, might strew revealFor() calls across many modules. Write your name there to permit integration as often as the code improves; more often than you could seek and remove all the revealFor() calls.