The Case for Reviewing Code Long After Merge

Code review is usually framed as an explicit step in the development workflow, most commonly as a pre-integration check on a pull request. That framing can drift into dogma, with some teams assuming that without pull requests there is no code review at all. That narrow view misses a whole range of explicit review mechanisms, and more importantly it overlooks what may be the most powerful technique of all: continuous refinement of existing code by the whole team.

The dominant metaphor for software is the building: something constructed and completed, hence the persistent architectural language. But software’s defining property is that it is soft, as easy to modify after release as during initial composition. Erik Dörnenburg has argued that architecture is the wrong metaphor and that town planning is a better one. Valuable software rarely sits still; it changes as we learn more about the value it can deliver. Those lessons apply not only to new features but also to refining what is already there, letting the code evolve with the team’s growing understanding.

Given the right environment, you can look at code written six months ago, spot problems in how it is written, and fix them. The flaws may have been present from the start, or may have emerged because the codebase around it changed and the old code no longer fits. Either way, the right time to fix a problem is as soon as it starts getting in the way. And when you gain an understanding of the code that isn’t obvious from reading it, you have the responsibility, as Ward Cunningham put it, to take that understanding out of your head and put it into the code, so the next reader doesn’t have to work as hard.

Refinement as a Review Trigger

This refinement process is essentially the same as a code review, but triggered each time code is examined rather than when it is first added. That insight matters. Many of the problems code reviews try to catch only become problems later, when the code is read again. There is a strong case for not addressing them until then: just as a new apartment complex changes traffic patterns, six months of change alters the context of old code and therefore the kind of fix it needs. And this form of review involves more people: every developer who reads the code becomes a reviewer, one who judges it against their actual experience of using the code rather than against vague general guidelines.

A useful test of any practice is the monopoly thought experiment: what if this were the only review mechanism available? With refinement alone, review attention would concentrate on the code that is read most often, which is mostly where it belongs. Code that is never read would never be reviewed, but that is largely acceptable. A team with solid testing practices can be confident the code works; performance tests can flag performance problems. If the code is never touched again, comprehensibility hardly matters. Such cases are likely vanishingly rare, but the exercise is informative.

Most is not all, though. Security is the obvious exception. Code can behave correctly for years until an attacker finds an exploit, at which point its lack of review is lamented. These high-impact but rare safety concerns deserve special scrutiny. That does not mean abandoning refinement; it means recognizing when rare, high-impact problems warrant targeted attention. Threat analysis should identify the modules that need extra scrutiny and the risks they face, and focused security reviews can then be scheduled effectively because they pursue a specific class of problem.

What Refinement Requires

Perpetual code refinement depends on other practices. Changing code requires confidence that existing functionality won’t break, so you need self-testing code. You need continuous integration to avoid large merge conflicts for others. You need solid refactoring skills to alter code effectively, and since this approach expects many developers to modify any part of the codebase, you need collective or at least weak code ownership. A team with those capabilities can rely on routine refinement as a substantial component of its review strategy, reserving synchronous methods for the situations that call for them.

The broader point is to think more seriously about the role of refinement in review. A team that focuses exclusively on pre-integration reviews risks neglecting how change works in a codebase. Keeping a mainline pristine and ensuring each merged commit is pristine does not guarantee the codebase is still pristine six months later. It cannot be: a good decision then is often no longer a good decision now. Refinement is how old code gets re-evaluated against changing usage, which is what sustains a codebase’s health over time.