Being able to make a code change and quickly see the result is critical. The longer we have to wait, the more code quality suffers, and the more productivity drops. Though often neglected, our build systems need to be well tuned to maintain rapid iterations.
The Problem of Slow Turnaround
I measure the turnaround time as how long it takes from making a change in the code to seeing that change live in the program. Live could be visually in the UI, the result of a test case, or some other observable effect. I include the entire process of saving the file, invoking the build system, and running the resulting program.
The longer this process takes, the more difficult it becomes to make exploratory code changes.
To give a hard number, I feel that I should be able to make a code change and see the result less than 10 seconds later. Anything longer than 30 seconds is unbearable and has a chance of making me task switch.
One of the first things to suffer are unit tests. I tend to find they are best written as an interactive exploration of the software, and thus require many builds. I write the main test, then I add several sub checks, or variants to the test, to improve the coverage. If I can’t quickly see the results of my new tests, then I’ll be significantly less inclined to actually write them.
I consider test driven development to be a good approach. If long build times are getting in my way, it basically breaks the way I think software should be written.
I actually subscribe to the idea of use driven development. It needn’t be a unit test, but I always think from a top-level feature, or use-case, down towards the internals. I implement things piecewise in small iterations to complete the high-level use.
The second aspect to suffer is debugging. Debuggers are useful, but I find that code instrumentation yields the most consistent and useful results. Adding print
statements and small code variations produces a wealth of information. The quicker I can add and remove such statements, the quicker I can narrow down the source of the defect. Long turnaround times directly impinge on my ability to debug code.
Abandoned Thoughts
A third problem is that slow turnaround tends to derail my train of thought. The longer I watch the build, the more likely it is my mind drifts to something else. Even if short, these critical lapses can result in massive losses in productivity.
And that’s just the short lapses. If the build is long enough that I have time to switch to my browser, then we have a major problem. Not only have I lost my train of thought, I’ve gone off on a tangent. We all know situations, in the afternoon, or working on a nasty defect, where these tangents just take over for the next hour, or till the end of the day.
Incremental Delays
It is incremental building and running that is the issue here. I don’t expect a fresh clone to build within 10 seconds. What I want is the typical turnaround from code change to running. Whether this involves a partial rebuild, new library, or some runtime loading doesn’t matter.
My change doesn’t need to be reflected through the whole project either. If I’m working on a unit test, all I really want to see is that unit test running. If I’m writing a single dialog, then I just need to see that dialog working, perhaps not even with real data, maybe it’s just a mock.
It’s these various partial requests that make a fast turnaround time possible. Even in slow to compile languages, like C++, one can always structure the libraries, and tests to reduce these incremental build times. In slow to start languages, like Java, we can use live class reloading.
Most languages and frameworks allow some way to get fast turnaround time. If they don’t, then they’re broken, inadequate technologies.
Architecture of a project also plays a role here. Large monolithic apps tend to suffer the most. Rapid turnaround favours a modular architecture. Good modularity is actually a recurring theme in programming for many reasons.
Appeal of Dynamic Languages
The desire for fast turnaround is perhaps a major driver in dynamic and interpreted languages. Using JS, CSS and HTML for web development is perhaps the best example here. We can avoid the compile-stage, and usually don’t even need to restart the server. Just press reload in the browser and we can see the results instantly.
On the server, Python and NodeJS are appealing for the same reason. On my own projects, I’ve been known to implement rough scripts and mini-languages to improve the turnaround time. It can often be valuable to trade some runtime performance for improved development speed.
Keep It Fast
Maintaining a well tuned build system that allows us to rapidly iterate and test our code is essential. It should be easy to justify investment in the build system; each improvement helps everybody on the team, and for the remainder of the project. Don’t forget about this amplified affect while planning.
A slow turnaround directly hurts the quality of the product and morale of the team. It makes development harder. It makes debugging harder. It makes it harder to focus. It makes us annoyed and angry.
Give your build system the care it deserves. You’ll be happier for it.
I’ve been fortunate to work on a wide variety of projects, from engineering to media, from finance to games. Follow me on Twitter to share in my love of programming. If you’re interested in something special, just let me know.