Once you’ve decided to abstract, and waited long enough to know the shape, there’s still the question that decides whether the abstraction helps or hurts: where does the boundary go? Two pieces of code have to end up on the same side of a line or on opposite sides, and the usual instinct is to group by resemblance — all the database code here, all the validation there, all the things that look alike filed together. It produces a tidy-looking structure. It also, quite often, produces a structure where making one real change means touching every folder, because the things that change together were scattered across boundaries drawn by appearance.

The more useful principle is that a boundary should follow the axis of change, not the axis of resemblance. The question isn’t “do these two things look similar” but “when a requirement shifts, do these two things move together or independently?” Code that changes for the same reason belongs on the same side of the line; code that changes for different reasons belongs on opposite sides. Get this right and most changes stay local — a new rule touches one module, because you put everything that rule affects in one place. Get it wrong and the same change smears across the codebase, not because the change is inherently big but because your boundaries cut perpendicular to how change actually flows.

You can feel the difference in maintenance long before you can articulate it. In a system whose seams follow the axis of change, tasks have a satisfying locality: “add support for this new case” turns out to mean editing one file, and the rest of the system doesn’t notice. In a system whose seams follow resemblance, the same task means a dozen small coordinated edits in a dozen places, each individually trivial, collectively a minefield where forgetting one leaves a subtle bug. The second system isn’t more complex in what it does; it’s just been cut along the wrong grain, so every change has to cross seams that shouldn’t have been there.

The reason this is hard is that the right axis isn’t visible in the code — it lives in the world the code models, and it only reveals itself over time as you watch what actually changes together. Early on you genuinely can’t know; you draw boundaries on your best guess, and some of them turn out to be perpendicular to reality. That’s information, not just annoyance. When you notice that a certain kind of change keeps forcing edits in the same three modules, the code is telling you those three things share an axis of change and want to be one thing. The repeated cross-cutting edit is the signal that a boundary is in the wrong place.

Which means good structure isn’t drawn once and defended — it’s re-cut as you learn what the real axes of change are. The three posts here are one argument: an abstraction is a bet about what varies, so it leaks when the underlying layer varies in a way it didn’t expect, it should wait until you’ve seen enough cases to know what varies, and it should be placed so the things that vary together sit together. All three are the same skill from different angles — understanding, before you draw the line, what is going to move and what is going to stay still. You will get it partly wrong, because that understanding only arrives with time. The craft is treating the boundaries as revisable, and letting the changes that keep hurting tell you where the lines actually belong.