Cultivating software
Some of my thoughts on software quality & AI-assisted software development.
The floor for quality is certainly lowering, but I’ve also seen the ceiling getting higher. More specifically, I see the potential for huge, long-lived software systems to remain flexible, maintainable, comprehensible, and refactorable as constraints, goals, and business needs change around them.
Even for those who cared deeply about code quality, the labor-intensive nature of writing code by hand inevitably created a heavy bent towards “it works, so don’t touch it”, and it’s a self-reinforcing trap:
- Refactoring is difficult and labor-intensive.
- Therefore the cost threshold at which refactoring is justifiable (either in terms of a programmer’s intrinsic motivation to do so, or the business justification of the cost) is high.
- Therefore it doesn’t get refactored.
- Time goes on, the codebase grows, constraints change, new consumers appear, and it becomes even more tangled and difficult to refactor.
- (repeat)
The high cost of revision forced architectural design to behave like prediction: anticipating constraints, consumers, and requirements, and hoping you weren’t too far off. We had to design around imagined future consumers because correcting the design after encountering real ones was prohibitively costly. Even a design that was “good” when written can become “bad” as its environment changes around it. The problem isn’t necessarily that the original prediction was careless, but that it remained embedded in the system long after reality diverged from it.
Now that agents have reduced the mechanical cost of system-wide change, architectural design can become more empirical:
- Establish a coherent model, with deliberate seams.
- Let real consumers test it.
- Notice where it breaks: leaky abstractions, awkward apis, recurring classes of bugs.
- Revise the model using evidence.
- Migrate the system comprehensively.
- (repeat)
The important thing to design for up front isn’t every future consumer, but the ability to revise the system once those consumers exist. Keep ownership clear, dependencies explicit, and important concepts represented authoritatively, so that assumptions remain visible (and replaceable) when reality disproves them.
Some classes of evidence that revision may be necessary:
- X depending on Y when the conceptual direction should be reversed.
- A caller recomputing information that has an authoritative source elsewhere.
- An api forcing callers to encode knowledge that should belong behind the boundary.
- Repeatedly finding and patching bugs with a similar root cause that could instead be made structurally impossible.
- A workaround becoming a copied usage pattern and therefore an unofficial api.
These are architectural problems, not necessarily aesthetic ones. Plenty of systems have ugly code that shouldn’t be touched, and plenty of systems have clean-looking code with a subtly wrong dependency direction that will become costly.
“Evolutionary design” isn’t a new concept, but its economics, and therefore its limiting factor, have drastically changed. As mechanical implementation becomes cheaper and more automatable, sustained architectural judgment becomes the scarce, high-leverage work. That means:
- Maintaining a coherent model of what the system is.
- Recognizing evidence that the model is wrong.
- Distinguishing incidental ugliness from structural error.
- Deciding whether an awkward consumer is misuse, an exception, or evidence against the abstraction.
- Designing the better model.
- Determining what evidence would establish that a migration is complete and safe.
Agents can help inspect the system and execute a migration, but can’t make these decisions reliably merely by being asked to “improve the architecture,” because the decisions depend on intent, history, future direction, and judgment about which irregularities are meaningful.
This also makes it more important to allocate human attention according to the judgment a change requires, rather than its size. A thousand-line mechanical migration within an agreed model may require less architectural scrutiny than a five-line change that relocates authority or reverses a dependency. That’s always been true, but by increasingly severing the association between the volume of a change and the human effort required to produce it, agents make the distinction far more consequential. This is the source of both the higher ceiling and the lower floor: with sustained architectural judgment, agents make far more ambitious revision possible; without it, orders of magnitude more change devolves into incoherence.
The stakes extend beyond any abstract ideal of “code quality”. A business benefits from producing more software only while that software remains responsive to new needs. Unchecked growth makes each subsequent change harder, as new capabilities have to work around old assumptions, duplicated knowledge, and boundaries that no longer match the business. More implementation capacity can therefore create more business value now while quietly degrading the system’s capacity to create value later. Architectural revision is what allows a system to continue delivering more as its scale, consumers, and requirements change.
Making that revision safe depends on several conditions, none of which are radical or new:
- Tests that protect behavior rather than implementation.
- Automated checks that expose incomplete or internally inconsistent migrations.
- Staging and compatibility strategies where old and new forms may coexist.
- Observability and rollback for runtime changes.
- A defined completion condition so the migration does not leave two permanent models.
These are established software engineering practices, but cheaper implementation increases their leverage: they let agents perform large amounts of mechanical work while humans concentrate on the model, invariants, migration strategy, and evidence. They make extensive change safer; they do not decide what changes will keep the system healthy.
I’ve been formulating this in my head through the metaphor of cultivation. A software system isn’t a finished artifact, meticulously built up piece by piece. Nor is it the output of a “software factory”; a factory assumes a stable process producing independent units, while every addition to a software system changes the structure in which the next addition must fit.
Software is living structure. Agents allow it to grow much faster, but cultivation (observing, pruning, reshaping, and sometimes cutting back) allows that growth to continue without degrading the system’s capacity to change and decides whether the result is a healthy system or an impenetrable bramble.