The biggest design shift of the AI era isn’t about who writes the code. It’s about how long the code is expected to live.
Churn has always been a dirty word in software. Microsoft researchers showed years ago that relative code churn predicts defect density. Adam Tornhill wrote a whole book called Your Code as a Crime Scene, where frequently changed, complex files are the first suspects. GitClear’s research projected that AI assistants would double the share of code rewritten within two weeks of being written, and framed that as downward pressure on quality. The instinct runs deep: code that keeps changing is code that was wrong.
But that instinct comes from a world where writing code was expensive. Durability was how you got your money’s worth. When a feature costs an afternoon instead of a sprint, the math flips. Rewriting something is often cheaper than carefully preserving it, and that’s only going to become more true.
Codebases are gardens now
We’ve always described software in building terms: architecture, foundations, scaffolding, load-bearing walls. Buildings are supposed to stand still.
I think codebases are becoming more like gardens. Specifically, gardens full of annuals. A gardener replants annuals every season, and nobody calls a tomato plant technical debt because it dies in October. The gardener’s real investment goes into what doesn’t turn over: the soil, the beds, the irrigation, the oak tree in the corner. Good structure is exactly what makes replanting cheap.
Code that churns a lot is essentially disposable, and disposable doesn’t have to mean sloppy. It can still be well made. It just isn’t made to last. Quality for an annual means it does its job, it’s tested at its edges, and it can come out without tearing up everything around it.
The problem is that we don’t have a picture of what high-quality churn looks like yet. Our mental model of quality is tangled up with longevity. We brag about code that has run untouched for six years. So churn happens anyway, but implicitly, and it lands wherever is most convenient.
Where churn actually lands
I looked at the last six months of Forem: 563 commits. The most-churned file is app/models/article.rb, touched in 37 of them.
Our spam handler is up there too, at 13. That one I’m happy about. Spam is a seasonal crop. Spammers change tactics constantly, and our code should churn right along with them.
article.rb is the oak tree. When the oak gets dug up every week, it means new features are rooting themselves in the core model because that was the easiest place to plant them. That’s the structural compromise. Churn isn’t the problem. Unplanned churn in load-bearing places is.
Designing for it
A few ideas for making churn explicit instead of accidental:
Name the layers. Say out loud what’s an annual and what’s a perennial. Our AGENTS.md already does a crude version of this by telling AI assistants to tread carefully around core auth, feed sorting, and cache pipelines. That should be more than a warning label. It should be the shape of the codebase.
Put contracts at the edges of the beds. Interfaces, schemas, API specs, and tests that pin behavior rather than implementation are the perennials. Whatever sits behind them can be rewritten freely, by a person or an agent.
Make removal cheap. The test of a good annual is whether you can pull it out without disturbing the bed. If something is meant to churn, it shouldn’t have tendrils in everything.
Measure churn by layer. High churn in the fast layer is a sign of health. Churn in the slow layer is the alarm.
Churn is a design input
Churn is going to happen either way. AI has made it the default rate of change for most of what we build. We can keep treating it as a symptom, or we can treat it as a design input and build the structure that lets us replant with confidence.
I will acknowledge that I have always been more of the perspective that churn is more acceptable than conventional wisdom of software design has implied. I’ve never seen the avoidance of churn as being a good thing in my practical experience. I used to see both sides of the argument, now I am staunchly in the camp that churn is positively associated with the quality and rigidity of our codebases and our systems.
I’d rather tend the garden than pretend it’s a building.
Top comments (8)
One place the annual/perennial line blurs in Next.js is caching. A feature can look easy to remove in the file tree and still be coupled to the rest of the app through a shared cache tag. If it calls
revalidateTagon something broad likeposts, that invalidates every cached entry carrying that tag, including ones used by parts of the app that have nothing to do with the feature. I'd treat cache tags as part of the boundary, not just the folder structure.Also, I love the cover image. Code growing out of the ferns and flowers and along the stepping stones captures the garden idea in one picture. 🌿
The contrast between the spam handler and article.rb makes the layer argument concrete. I’d also watch how often a change in an “annual” forces edits in the core model: that seems like an early sign that the boundary is leaking, even before defects show up.
The removal test should cover stored data too. If a fast-changing feature adds columns or writes new record types, deleting its code can still leave every future version of article.rb responsible for those records. I'd make the cleanup or compatibility path part of the feature's contract and test removal with a populated database. That separates a replaceable implementation from one that's only easy to replace on a fresh install.
This is one of the clearer framings I’ve seen of the shift.
The building metaphor really is breaking down. When writing code was expensive, durability was the rational goal. Now that rewriting is often cheaper than carefully preserving, treating everything as if it should last for years becomes the expensive choice.
The annuals vs. perennials distinction is especially useful. High churn isn’t inherently bad — high unplanned churn in the load-bearing parts is. Spam handling should turn over constantly. Core models and auth shouldn’t become the path of least resistance just because they’re convenient.
“Make removal cheap” feels like the practical test. If something is meant to be temporary, it shouldn’t grow roots into everything else.
I’d rather tend a garden too. The old instinct to treat all churn as a quality smell is starting to look like an outdated cost model.
The garden split matches Anthropic's Claude Code study: the human made about 70 percent of planning decisions but only about 20 percent of execution decisions. The oak is where people still decide, the annuals are mostly agent actions, which is why replanting them is so cheap.
Lower implementation cost changes the tradeoff, but it does not remove the need to distinguish exploratory churn from instability. One useful metric would pair churn with the age of the last passing contract/integration test; frequent changes behind a stable contract are a different risk than repeated changes that keep breaking callers.
This is the first I've heard of churn, thanks for sharing valuable info!
so many years have passed and I still use your garden analogy when teaching others. Always nice to see DEV alive and well 🤝