>_devkit
catalogue
Stablev1.0.0

maintainability

by @chempa

Use when writing, reviewing, refactoring, or reviewing AI-generated code - change cost, coupling, blast radius, mechanical enforcement, deletion, and reversible decisions.

Maintainability

Every other skill in this catalogue is a specific answer. This one is the reason those answers were chosen. Reach for it when deciding how to structure something, when reviewing a change, when a codebase has started to feel slow to work in, or when accepting code a model wrote.

Why this is now the measure

Most things engineers were once judged on have moved.

Recalling an API, writing a function, scaffolding a service, producing a test, translating between languages: a model does all of it in seconds, at a quality that clears the bar for most working code. Typing speed stopped being a skill some time ago. Knowing the standard library stopped being one recently.

Three things did not move, and they are the whole job now.

Understanding a system well enough to change it safely. Reading is not automatable in the way writing is, because the thing you need is not the text, it is the model of the system the text implies. That still has to be built in a human head, and the cost is set by how the code is arranged.

Being confident a change is correct. Generation got fast. Verification did not. You can accept a thousand lines in a minute and you cannot read a thousand lines in a minute, which means the gap between what enters the codebase and what anyone has actually understood is now the defining risk of the era.

Owning the result. Code is cheap to produce and exactly as expensive to own as it always was. Every line is a line that must be read, kept true, migrated, and eventually deleted.

So the ratio inverted. When writing was the expensive part, volume was a rough proxy for effort and value. Now writing is free, which makes volume a pure liability and structure the entire game.

There is a second effect that matters more than it first appears.

AI amplifies whatever structure it finds. Point a model at a codebase with one consistent way of doing things and it produces more of that thing, correctly, at speed. Point it at four competing patterns and it picks one essentially at random each time, so the inconsistency compounds at machine speed instead of human speed. Consistency used to be a courtesy to colleagues. It is now a control input: the highest-leverage thing you can do to improve what a model writes for you is to make the existing code unambiguous about how things are done here.

The one rule

The cost of software is the cost of changing it.

Not writing it, not running it. Any decision that trades a slower first write for a cheaper tenth change is correct, and almost every argument about style, structure, or abstraction resolves once you ask which option makes the tenth change cheaper.

The corollary is the thing people get wrong: code that was fast to write and is hard to change is not a win you can bank. It is a loan.

The measure

Maintainability is not a feeling. Use an operational definition:

How long does a competent stranger take to make a correct change safely?

Three readers are that stranger, and they have more in common than they look:

  • you, in six months
  • someone who joined last week
  • a model, which has no memory of the conversation where you decided any of it

None of them have the context you have right now. All of them have only what the code, its names, its tests, and its structure actually say. Writing for the model and writing for the new hire are the same act, which is why this got more important rather than less.

Five properties

Everything below is one of these.

PropertyThe questionWhat failure looks like
LegibleCan this be understood without reading the rest of the system?Every change starts with an hour of archaeology
BoundedCan you state what else a change touches?"Simple" changes keep breaking distant things
EnforcedDoes the rule hold without anyone remembering it?The convention is true in the files written this month
ReversibleCan this decision be undone later?A choice made in week one is still being paid for in year three
DeletableCan this be removed when it stops earning its place?Nothing is ever removed, so everything must be understood forever

Legible and Bounded are about today's change. Enforced is about the hundredth change. Reversible and Deletable are about the changes you have not thought of yet, which are most of them.

Blast radius

The single most useful habit: before making a change, name the set of files that must change with it.

If you cannot name the set, that is the finding. It means the coupling is implicit, and implicit coupling is the thing that turns a one-line fix into a two-day incident.

Good structure makes the set small and predictable, and the way it does that is almost always the same move: put the things that change together next to each other. That is what feature folders are, what a design token file is, what a repository layer is. The opposite arrangement, grouping by technical kind so that one feature is smeared across four distant directories, looks tidy in a tree view and costs you on every single change.

Two coupling rules worth holding:

  • Depend on things more stable than you. A screen may depend on a button. A

button may never depend on a screen. When the arrow points the wrong way, the stable thing inherits the churn of the unstable one.

  • One direction only. A cycle means neither module can be understood,

tested, moved, or deleted alone. Cycles are the point at which a codebase stops having parts.

references/change-cost.md covers coupling types, seams, the locality principle, and how to find the blast radius of code you did not write.

The enforcement ladder

A rule is only as real as the mechanism that holds it. This is the table to argue from:

LevelMechanismHolds becauseDecays?
1Tribal knowledgesomeone remembersimmediately
2Written in a docsomeone reads itquickly
3Caught in code reviewsomeone noticesunder deadline
4Lint rule, CI checkthe machine rejects itno
5Type systemit does not compileno
6Impossible by constructionit cannot be expressedno

Every rule you actually care about moves down this ladder. Levels 1 to 3 depend on human attention, and human attention is the resource that disappears exactly when it is most needed. Levels 4 to 6 do not care that it is Friday.

This is why so much of this catalogue keeps saying enforce it mechanically: the ESLint boundary rules in react-native-expo, $jsonSchema validation in mongodb-production, the skill validator in this repo. The rule was not new in any of those cases. The mechanism was.

When you cannot get to level 4, say so out loud in the review checklist rather than pretending the doc is enough.

references/enforcement.md has the concrete mechanisms per level and how to pick one.

Volume is a liability

Now that code is free to produce, this is the property that changed most.

  • The cheapest code to maintain is code that does not exist. Before adding

an abstraction, a layer, an option, or a configuration flag, ask what breaks if it is simply absent. Most of the time the honest answer is nothing.

  • An abstraction with one caller is not an abstraction. It is indirection.

Wait for the third case, then extract the thing all three actually share, which is usually not what you would have guessed at the first.

  • Every option multiplies the states you must reason about. A flag is two

codebases sharing a file. Three flags are eight.

  • Deletion is a feature. Removing a dead endpoint, an unused index, an

abandoned feature flag, or a component nobody imports is real work with real return, and it is the work that never gets prioritised because it does not demo.

The test that settles it: could you delete this feature in an afternoon? If not, you do not own it. It owns you, and it will keep charging rent in every future change to anything near it.

references/deletion.md covers finding dead code, the deprecation path for things with external consumers, dependency hygiene, and why a feature flag needs a removal date at birth.

Reversible and irreversible decisions

Most decisions are cheap to undo and should be made fast, by whoever is closest, without a meeting. A small number are effectively permanent, and those deserve disproportionate care.

The permanent ones share a tell: something outside your codebase has already copied the decision. Persisted data has a shape. Published URLs are in someone's bookmarks. A domain is in every login name. An identifier is in a customer's integration. Once the decision has left the building you cannot take it back by editing code.

Each skill in this catalogue names its own one-way doors, and they are all this same shape:

SkillThe irreversible decision
mongodb-productionEmbed or reference, and the multi-tenancy model
zitadel-productionExternal domain and TLS mode, fixed at first boot
deploy-pipelineBranch names, which the environment names derive from
naming-conventionsAny name that reached a database, a URL, or a client

Before an irreversible decision, write down what you believe and why, in the repo, in a file that ships with the code. Not for ceremony. For the day in eighteen months when someone reasonably asks why it is like this, and the choice is between a recorded answer and a rewrite driven by a guess.

references/decisions.md has the lightweight record format, the one-way door test, and how to write a decision down in a way that survives.

Maintaining code you did not write

Half of new code is now generated. That changes the review bar, not the standard.

Never merge code you cannot explain. This is the whole discipline in one line. If you cannot say what a block does and why it is there, you have added a dependency on a thing nobody in the organisation understands, and the fact that it passes tests is not the same as knowing it is right.

  • Read the diff, not the summary. A model's description of its own change is

a plausible account, not a verified one. It is usually right, and the times it is not are exactly the times the summary is most reassuring.

  • Generated code trends verbose and defensive. Redundant null checks,

speculative error handling for conditions that cannot occur, comments that restate the line below. All of it is volume, and volume is liability. Cut it.

  • Watch for plausible-but-unused structure. Helper functions with one

caller, config options nothing sets, abstractions for a second case that does not exist. A model will happily build the general version of a problem you do not have.

  • Duplication is the most common failure. A model that cannot see your

existing helper writes a second one. Two functions that do the same thing with different names is worse than either alone, because now every future change has to find both.

  • Tests are the specification. When you cannot review volume, you review

behaviour. A test that a human wrote and understands is worth more than a hundred lines a human skimmed.

  • The conventions file is load-bearing infrastructure. CLAUDE.md, the lint

config, the type definitions: these are how the model learns your house style, and they now pay back on every prompt rather than every onboarding. Treat time spent on them as engineering, not documentation.

references/ai-era.md goes deeper on reviewing at generation speed, structuring a repo so an agent works well in it, and the failure modes specific to generated code.

What to measure

Most code metrics are noise. Three signals are real, and all three are about time rather than about the code:

  1. Time from intent to safe merge for an ordinary change. If this is

growing, nothing else matters.

  1. Blast radius per change. Files touched for a typical one-behaviour

change, tracked as a trend. Rising means coupling is increasing.

  1. Change failure rate. How often a merged change has to be reverted or

hot-fixed. Rising means verification is not keeping up with generation, which is the specific failure mode of this era.

Ignore line counts, ignore raw coverage percentage, and be suspicious of complexity scores. They measure the text. Maintainability is a property of the relationship between the text and the people changing it.

Anti-patterns, in rough order of damage

  • Rules that live only in review. They are true for a month and then they

are folklore. Move them down the ladder or drop them.

  • Grouping by technical kind at scale. Forty files in services/ means

every feature change is a tour of the repo.

  • Cyclic dependencies. The point where the codebase stops having parts that

can be understood, tested, or removed alone.

  • The premature abstraction. Built for a second case that never arrives, and

now every reader pays the indirection tax forever.

  • The utility landfill. utils.ts, helpers.py, common/. A folder with no

criterion for what belongs in it accumulates everything and can never be split.

  • Config that is really code. Enough flags and branches in configuration that

the real behaviour is untestable and lives in an environment, not a repo.

  • Copies that drift. The same fix landing in twelve forked repos by hand, or

not landing in nine of them.

  • Comments explaining what, not why. The what is in the code below and goes

stale. The why is the only thing a comment can say that the code cannot.

  • Accepting generated code you cannot explain. The one that is genuinely new,

and the one that will define the next few years of legacy systems.

The review checklist

Reject a change that does any of these:

  • Adds an abstraction with exactly one caller
  • Introduces a second way to do something the codebase already does
  • Depends on something less stable than itself
  • Creates or deepens an import cycle
  • Relies on a rule that exists only in a document or in review
  • Adds a flag, option, or branch with no removal condition
  • Cannot be deleted later without touching unrelated code
  • Contains code the author cannot explain
  • Restates in a comment what the next line already says
  • Makes an irreversible decision without recording why

The rest

locality, blast radius, and refactoring safely

practice, per language and per rule type

dependencies, feature flags

decision record, and recording the why

and structuring a repo an agent can work in

Copy: no em-dashes

Never use an em-dash (U+2014, the long dash) in code, comments, commit messages, or documentation. It is the loudest tell that text was generated. A full stop or a colon is almost always the better edit.