What the Grooves Explain
Last time I argued that coupling isn’t a number — it’s a directional resistance field. Past decisions carve grooves. Fluidity measures overall steerability.
A good theory retrodicts what we already know. So let’s pressure-test this one against practices every developer has opinions about.
monorepos
Companies keep consolidating into monorepos despite the operational pain. Google, Meta, Microsoft — billions of lines in a single repository. Why?
Multi-repo creates walls in the complection field. Cross-repo changes hit infinite resistance at the repository boundary: coordinate releases, manage version compatibility, negotiate API contracts between teams who have different priorities. Each repo boundary is a cliff in the field.
Monorepos make the field continuous. No infinite barriers. Everything can in principle change together in a single commit. The resistance is still there — large systems are large — but there are no artificial discontinuities.
This predicts when each wins. Monorepos win when cross-cutting changes are frequent. If intent vectors regularly cross what would be repo boundaries, those boundaries are grooves pointing the wrong direction. Multi-repo wins when teams have genuinely orthogonal directions — rarely coordinate, value independent deployment, aren’t constantly reaching across boundaries.
The interesting case is the in-between. Most organizations have some cross-cutting concerns and some independent work. The monorepo trend suggests most teams underestimate how often their intent vectors cross boundaries.
DRY
The most universally recommended practice. Also the source of some of the worst maintenance nightmares I’ve seen.
DRY reduces information — less code to maintain. But it reshapes the resistance field in a specific way: things now depend on one shared abstraction. You’ve created a low-resistance groove along “use the shared thing as-is” and a high-resistance wall along “make the shared thing behave differently for different callers.”
When the shared thing is stable — a date parser, a logging utility, core domain logic that genuinely applies everywhere — DRY is a gift. The groove points the right direction.
When the shared thing is on the critical path of diverging needs, DRY becomes a trap. Two teams share a component. Team A needs it to handle edge case X. Team B needs it to handle edge case Y. The shared abstraction accumulates conditionals, feature flags, and “if caller is Z” branches. It becomes the thing everyone’s afraid to touch. The shared utility library that’s nominally reducing duplication but actually increasing in every direction that matters.
The grain framework makes the diagnostic clear: DRY helps when the shared thing’s groove aligns with future intent. DRY hurts when it forces diverging intents through the same groove.
A rule of thumb: if you find yourself adding parameters to a shared function to handle special cases, the grain has shifted. The abstraction is fighting you. Let it split.
tests as frozen intents
Everyone agrees tests are good. Every project has a folder of tests that rot.
Tests are frozen intents. Each one encodes an assertion: “this behaviour must remain cheap to preserve.” That’s valuable when the behaviour is an actual invariant — something that genuinely shouldn’t change regardless of where the system goes.
But tests freeze implementations too. Test that a function returns a specific JSON structure? Now that structure is a groove. Test that a method gets called in a specific order? That order is a groove. You didn’t mean to carve grooves, but you did. Every assertion is a groove, whether you intended it or not.
Well-chosen tests freeze invariants. Poorly-chosen tests freeze implementations.
The difference: invariant tests remain valuable when the codebase moves in a new direction. Implementation tests fight you — they break on changes that are functionally correct but structurally different. You end up updating tests not because something is wrong, but because your tests are resisting the new direction.
This is why TDD works brilliantly for stable domain logic and becomes a drag for exploratory development. In a stable domain, you know which grooves matter. In exploration, the grooves keep shifting, and frozen intents become anchors.
Conway’s Law
“Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.”
Everyone knows this. The resistance field explains why.
Communication cost between teams is comprehension cost — literally across team boundaries. Each team naturally minimizes cross-boundary work. They build toward the directions where resistance is lowest, and resistance is lowest within their own domain of understanding.
Over time, code structure converges on communication structure because that’s the path of least resistance. It’s not laziness. It’s physics. The complection field gets carved by the pattern of who-talks-to-whom.
This means fighting Conway’s Law is paying a perpetual tax. Every cross-team change costs more than an intra-team one. You can do it, but you’re working against the grain of the organization, not just the grain of the code.
The practical implication: if you want a different architecture, reshape the org first. Match team boundaries to the architectural boundaries you want. Then the natural groove-carving will produce the structure you’re after instead of fighting it.
software as steering
One more lens. So far we’ve treated code as something you transform — push it from here to there, pay the resistance cost. But software in production is something you steer.
The cybernetic perspective frames it differently. You have a goal state, an actual state, and a gap between them. Your correction bandwidth — how much you can change per unit time — needs to exceed the rate at which the gap grows. If the system drifts faster than you can correct, you lose control.
CI/CD reduces the time from “something’s wrong” to “fix deployed.” Feature flags expand your correction options. Monitoring tells you when drift is happening. Technical debt is accumulated drift you’ve chosen to tolerate. A rewrite is an admission that drift has exceeded correction bandwidth.
Software development isn’t construction. It’s piloting. You’re flying a plane already in the air, toward a destination that keeps moving, with instruments showing a subset of reality and controls that respond with lag.
This matters for the grain because different regimes need different field shapes. Artifact software — ls, sqlite — has a fixed goal. Fluidity barely matters; the grooves are the product. Adaptive software — modern SaaS, recommendation engines — has a moving goal and a dynamic environment. Fluidity is everything, because you can’t predict which directions will matter tomorrow.
Most dysfunction comes from misidentifying your regime. Treating adaptive software like an artifact leads to frustrated engineers, mounting debt, and inability to respond. Treating an artifact like adaptive software leads to over-engineering and churn for no reason.
Each of these practices — monorepos, DRY, testing discipline, org design — is a strategy for managing the complection field given assumptions about future directions. They become anti-patterns when the assumptions stop being true.
Explaining after the fact is easy, though. The real question is whether we can measure any of this before making the change.