When Change Becomes Cheaper Than Commitment

Systems change faster than teams can agree on what must remain true. Code evolves, features accumulate, and test suites grow — yet confidence quietly erodes. Tests stop acting as guardrails and start adding noise.
Divergence and convergence
A while back, I came across an insight from Khalil Stemmler that reshaped how I think about software systems. He frames creation as a balance between two forces: divergence and convergence.
To create anything in the world relies upon these two creative forces: divergence and convergence.
Divergence is about:
expansion
emergence
Where convergence is about:
contraction
contracts
— Khalil Stemmler, Why You Have Spaghetti Code
Divergence is the phase where we explore. We try approaches, generate variants, sketch solutions, and learn by doing.
Most divergent work is cheap and reversible: ideas can be discarded, code can be rewritten, tests can be deleted.
The goal here is not correctness, but understanding — expanding the space of what could work before deciding what should.
Convergence is the phase where we decide. We collapse many possibilities into a small set of commitments, making the system resilient to future change.
Convergent work is deliberate and more expensive, because it creates constraints that future work must respect.
This is where contracts are formed: decisions about structure, behavior, and invariants that the system will actively defend over time.
Healthy systems need both forces. When they fall out of balance, entropy creeps in. In another article on legacy code, Khalil uses entropy as a metaphor for what happens when divergence is not followed by convergence:
Just like entropy, the natural state of the universe, code has a tendency towards disorder over time. Tests act as a sort of ‘entropy reversal’ mechanism.
— Khalil Stemmler, How to Improve Legacy Code w/ Characterization Tests
Exploration continues, but decisions are never fully locked in. The system expands, but it never settles.
This framing resonated deeply with me, especially as I dove deeper into AI-assisted development.
What went wrong
At first, I was excited to see my test suites blossom, but that excitement didn’t last long. Schema changes and refactors became increasingly difficult. Small changes propagated through the whole system. My confidence in the test suite slowly began to drop. I soon realized, very few of those new tests actually represented decisions I remember making myself.
LLMs are exceptionally good at divergent work. They explore quickly, generate alternatives, and iterate at a speed that is hard for humans to match. This makes them powerful tools for learning, experimentation, and early exploration.
What has changed is not the function of divergence, but its cost: modern AI tools dramatically reduce the effort required to explore the solution space.
When divergence becomes nearly frictionless, systems expand faster than humans can converge them — reviews get shallower, tests get merged by inertia, and contracts accumulate that no one remembers agreeing to.
That’s why AI often feels empowering at first — and destabilizing over time.
Used well, AI supports divergence and informs judgment. But letting AI take over convergent decisions — locking in requirements, boundaries, contracts without human ownership — does not merely fail to help. It actively accelerates entropy by weakening the very mechanisms meant to push back against disorder.
Those decisions are small in volume, but their impact is disproportionate. And I learned that the hard way.
How I see tests now
Interfaces and schemas converge structure; tests converge behavior.
Tests are often described as verification tools: a way to check that the system behaves as expected. While this is not wrong, thinking of them as convergence mechanisms makes them even more powerful.
They should not simply describe how the system currently works — that’s what the source code does — they should define what the system must continue to do as it evolves.
Writing tests, like writing code, can involve exploration. We try scenarios, probe edge cases, and learn how the system behaves. The convergent act of test engineering begins when we decide which signals are stable enough to encode, which behaviors should become contracts.
When tests serve this role well, they make change safer. Refactoring becomes less frightening because the system has agreed on what must remain true.
When they don’t — when tests mirror implementation details or hardcode incidental behavior — they fail to converge the system. Instead of reversing entropy, they amplify it, by committing the system to things that were never meant to be stable.
What actually made the difference
The value of a contract depends on how long it is expected to hold.
Some contracts are short-lived, others are meant to survive years of change. Convergent effort should be proportional to that lifespan.
Unit tests, for example, encode contracts close to the implementation. They are valuable, but often volatile. They change as code is refactored, abstractions shift, or responsibilities move. That’s by design. These contracts trade longevity for precision.
User-facing tests are different.
End-to-end tests encode contracts at the boundary between the system and its users. They describe what the product does, not how it is built. As a result, they often outlive internal changes: refactors, API changes, sometimes even complete architectural overhauls.
Problems arise when teams apply short-lived contracts in places that require stable ones. Metrics reinforce this mistake by rewarding the presence of tests, not the strength of the commitments they encode. Coverage goes up, but confidence does not — because the system is converging on the wrong things.
It's tempting to use AI to generate large amounts of test code or record user flows that satisfy coverage metrics. But these artifacts rarely encode long-lived intent. They give the appearance of convergence, while the underlying commitments remain shallow.
The longer a contract is meant to endure change, the more deliberate we must be in defining it, and no contract is longer-lived than the promises we make to users.
Balancing the scales
The forces here aren’t new. Software has always required exploration followed by consolidation. What has changed is how cheap exploration has become.
AI accelerates divergence. Convergence — deciding what must remain true — is the bottleneck.
That decision shows up as contracts.
Some contracts are short-lived and can change freely. Others are meant to survive refactors, rewrites, and architectural shifts. The longer a contract is expected to hold, the more deliberate we need to be about defining it.
Testing is where this tension becomes visible.
Seen through this lens, ask yourself:
Which of your tests are actually reducing entropy — and which are quietly increasing it?
Subscribe to my newsletter
Notes from my ongoing battle with legacy codebases and myself, delivered to your inbox.
Comments
No comments yet. Be the first to share your thoughts.
Ábel Énekes