Skip to content

Maintainability

Maintainability is how easily the system can be understood, changed, and extended. It is a structural characteristic: users never see it directly, but it sets the cost of every future feature and the speed of every bug fix. Over a long-lived system, it is usually the characteristic with the highest total financial impact.

How it is measured

Maintainability resists a single number, so use a bundle of proxies:

Signal What it indicates
Lead time for a small change The end-to-end friction of the codebase
Cyclomatic complexity per function Local understandability
Coupling (afferent / efferent), dependency cycles Structural entanglement
Number of files touched per typical change Whether modules match how the system actually changes
Test coverage of changed lines Confidence to change anything at all
Code age and churn hot spots Where the pain is concentrated

The most honest question: how long does it take a new engineer to ship a small change safely?

Design tactics

  • High cohesion, low coupling. Group what changes together; separate what changes for different reasons. This is the whole point of SOLID Principles.
  • Align modules with the domain. Modules named for business capabilities localise change; modules named for technical layers scatter every feature across all of them.
  • Depend on abstractions across boundaries, so a replacement is a new implementation rather than a rewrite; see Hexagonal Architecture.
  • One obvious way to do each thing. Three HTTP clients, two logging conventions, and four date formats each multiply the cost of every change.
  • Delete aggressively. Dead code, unused flags, and abandoned experiments are read by every person and every tool forever.
  • Documentation where it rots least: an architecture decision record for the why, tests for the how, the code itself for the what.
flowchart LR
    subgraph Bad["Layer-aligned: one feature touches everything"]
        UI1[UI layer] --> S1[Service layer] --> R1[Repository layer]
    end
    subgraph Good["Domain-aligned: one feature lives in one module"]
        O[Orders] --- P[Payments] --- I[Inventory]
    end

The debt loop

Unmaintainable code slows delivery; schedule pressure then produces more unmaintainable code. Breaking the loop requires making the cost visible: track lead time, and refactor as part of feature work in the area being touched, rather than as a project that never gets funded.

Trade-offs

  • Against performance: the clearest structure is rarely the fastest one; optimise only where measured.
  • Against short-term delivery speed: the abstraction that helps in month six slows month one. Applying YAGNI matters here; speculative flexibility reduces maintainability.
  • Against modularity taken too far: dozens of tiny services can make a single change span many repositories, which is worse, not better.

Fitness functions

  • Dependency rules enforced in CI: no cycles, no import from domain to infrastructure.
  • Complexity and file-size thresholds failing the build on new violations.
  • Lead-time-for-change tracked as a DORA metric and reviewed like any other SLO.

Check Your Understanding

#

Why do domain-aligned modules usually beat layer-aligned ones for maintainability?

#

Adding a speculative abstraction "in case we need it later" typically...