More compliance with coding principles can make software harder to change. A shared abstraction can force unrelated business domains to change together, while an unnecessary service boundary can turn a local operation into a network dependency. For engineering leaders, maintainability means choosing practices that reduce the cost of the next change and help an unfamiliar engineer understand the code quickly.
Getting those choices wrong is costly. CISQ’s 2022 report estimated US costs from poor software quality at $2.41 trillion and accumulated technical debt, the future work created by unresolved software problems, at $1.52 trillion. The first figure describes a quality-related cost burden; the second describes accumulated obligations. Stripe, a payments company that benefits when developers have more time to build products using its services, adds a measure of working capacity: its Developer Coefficient survey found that developers reported spending 42% of their working time dealing with technical debt and bad code.
Work on that debt competes with feature delivery through debugging, onboarding, incident response, and remediation. Elegant code helps when its simplicity removes unnecessary complexity and makes a Friday-afternoon change reasonably easy to complete. The practical question is which standards, checks, and maintenance investments make that change easier without creating more work elsewhere.
Choose standards that reduce change costs
A useful standard addresses a recurring reason changes become expensive. Applying every named principle with equal force can create new work, so enforcement needs to follow the problems in the codebase. SOLID groups five design principles, each addressing a different concern. A selective approach gives the Single Responsibility Principle and Dependency Inversion Principle the most attention, then applies the others where they solve a specific problem.
The Single Responsibility Principle, or SRP, matters when several teams modify one class for unrelated reasons. Their changes can conflict, and a modification for one purpose can disturb another responsibility. Separating those responsibilities can reduce coordination costs, but the boundary determines whether the separation helps: splitting services too aggressively replaces function calls with network calls. The resulting latency and extra opportunities for failure can outweigh the benefit.
Infrastructure creates another kind of coupling when business logic depends directly on databases, external services, or other implementation details. The Dependency Inversion Principle, or DIP, isolates those dependencies behind interfaces so business rules become easier to test and infrastructure can change without rewriting the rules. The Open-Closed Principle, also called the Open/Closed Principle or OCP, encourages extending behavior while limiting changes to existing code; the Liskov Substitution Principle concerns whether replacements preserve expected behavior; and the Interface Segregation Principle favors interfaces tailored to their users. Apply these selectively in library and application programming interface, or API, design, where OCP can otherwise encourage a plugin architecture before anyone needs extensibility.
Premature extensibility has a close relative in premature reuse. Don’t Repeat Yourself, or DRY, encourages teams to remove duplicated knowledge, but two implementations may look alike while representing different business domains. Those domains may later need different behavior, so combining them too early ties future changes to an abstraction that no longer fits both. A practical rule of thumb is to allow the first repetitions and reconsider abstraction at the third instance, examining whether the implementations genuinely belong together.
Demand offers another way to judge whether an abstraction is worth maintaining. KISS, Keep It Simple, Stupid, and YAGNI, You Ain’t Gonna Need It, both argue against unnecessary capability. Consider a hypothetical flexible plugin framework supporting a feature with fewer than 10 user requests per day: its flexibility creates work during every onboarding and review cycle, while the documentation needed to explain it may never be written. Does actual demand justify that continuing cost?
Before a business establishes product/market fit, evidence that customers want the product, the same reasoning can justify delaying architectural cleanup. Months spent optimizing features that customers may reject can consume capital without establishing business value. Postpone speculative work, record the resulting debt, establish product viability, and then address the recorded problems. Recording the debt keeps a deliberate delay visible in planning, where someone can later take responsibility for resolving it.
Make the next change easy to understand
Delaying architecture work still leaves a team responsible for code that new colleagues can understand. A useful onboarding goal is a production-quality pull request, a proposed code change submitted for review, in days rather than weeks. Consistent naming across modules, variables that express intent, and concise descriptive function names reduce searching and guessing. Cryptic abbreviations and unexplained numeric literals add to that work even when the architecture stays the same.
Clear names help engineers find the right code; clear function structure helps them understand what happens there. Short, reusable functions focused on one task let an engineer inspect a change without reconstructing several unrelated operations. Splitting a complex function improves navigation when the smaller functions make its responsibilities and sequence easier to follow. Mechanical division can leave those responsibilities just as unclear, so function length needs to serve understanding.
Comments should preserve the understanding that names and structure cannot convey. Review guidelines can distinguish “what” comments, which describe visible behavior, from “why” comments, which explain an unusual business rule or a workaround for a third-party library bug. Behavior descriptions may lose relevance within months as the implementation changes, while the reason behind a decision can remain useful for years. The test is whether the comment helps a future engineer judge a proposed modification.
Unusual implementations sometimes need a behavior-oriented comment to show that an odd detail is intentional. Quake III’s fast inverse-square-root implementation includes the comment “evil floating point bit level hacking,” described as retaining its significance for 25 years. It warns a future maintainer: “do not ‘fix’ it”. Its value lies in explaining how to treat the unusual implementation during a change.
Understanding a function also depends on how much surrounding code an engineer must inspect. Organizing modules around business capabilities keeps related rules together, whereas separate controller and model modules can spread one business operation across technology layers. Authentication and logging can justify their own modules because they apply across business capabilities; other shared functions should stay aligned with their domains. As team membership changes, preserving these choices requires a process that directs human attention toward decisions machines cannot make.
A project in mind?
Schedule a 30-minute meeting with us.
Senior experts helping you move faster across product, engineering, cloud & AI.
Automate mechanical checks and preserve reviewers’ attention
That process can combine three enforcement layers: automated analysis, commit hygiene, and human review. Automated analysis checks code without running it, commit hygiene keeps version-control records focused and informative, and reviewers evaluate design and correctness. Each layer removes work from the next, which matters because human review requires someone to reconstruct intent and reason about consequences. Leaders need to assign ownership and capacity to these controls so enforcement continues under delivery pressure.
Formatting is a good first responsibility for automation because tools can apply its rules consistently. Prettier and Black for Python handle formatting, while ESLint Stylistic handles stylistic checks for JavaScript/TypeScript, or JS/TS. Running these checks before review keeps contributors from spending review time debating layout. Consistent formatting also makes code from several authors easier to read as one codebase.
Once formatting is handled, automated analysis can examine structure. ESLint, used for JS/TS with extensions to other languages, can identify unused imports and unreachable code; SonarQube adds cognitive complexity scores, which estimate how difficult control flow is to understand, and module-level duplication detection. Knip provides dead-code tooling for JS/TS. Duplication checks and dead-code tooling belong in pre-commit hooks, which run before a commit is created, and in continuous integration and continuous delivery, or CI/CD, pipelines that check changes as they move toward release.
Artificial intelligence, or AI, code generation makes these controls more important because a working implementation can conceal duplicated logic and unused code. GitClear, a code-analysis vendor that benefits from demand for tools to detect these problems, examined 211 million lines of code and reported structural changes between 2020 and 2024. Its findings show deterioration across the analyzed code; attributing every change to AI would go beyond that relationship.
| GitClear measure | Reported change between 2020 and 2024 |
|---|---|
| Frequency of duplicated code blocks | Increased eightfold |
| Refactoring’s share of changed lines | Declined from 25% to under 10% |
These changes can accumulate even when code compiles and passes its tests. Engineers use the label “AI slop” for generated code that works immediately but worsens the codebase’s structure over time. Automated checks make duplication and dead code visible before further changes depend on them. People still need to judge whether names and module boundaries express the intended business meaning.
Those judgments become easier when version-control history explains how the implementation developed. In Git, each commit should contain one logical change and explain its reason, allowing reviewers to assess the decision without disentangling unrelated edits. The conventional commits standard uses type and scope to make history searchable, and commitlint can validate that format in pre-commit hooks. Format validation cannot supply the rationale that a future engineer would otherwise have to infer from the diff, the record of changed lines.
Pull request, or PR, descriptions can preserve the broader reasoning behind several commits. Informative descriptions support automatic changelog generation while recording the alternatives and constraints behind the implementation. For large features, stacked PRs divide dependent work into separately reviewable changes; GitHub Stacked PRs is one named approach. Applying the SmartBear/Cisco review-size recommendation of 200–400 lines of code, or LoC, to each stacked PR keeps the full feature from becoming one immediate review task.
Review size matters because attention has limits. The SmartBear/Cisco study covered 2,500 reviews, 3.2 million lines of code, and 50 developers, and associated effective defect discovery with bounded scope, pace, and session length. SmartBear sells software testing and quality tools, so findings that encourage investment in quality practices also serve its commercial interests. The study’s recommendations give teams concrete limits for organizing review.
| Review constraint | SmartBear/Cisco recommendation |
|---|---|
| Code inspected in one review | 200–400 LoC |
| Maximum inspection pace | 500 LoC per hour |
| Maximum session duration | 60–90 minutes |
Each limit addresses a different failure mode. Restricting scope keeps the immediate task manageable, restricting pace prevents a small review from becoming a rushed scan, and restricting duration limits inspection after concentration declines. SmartBear/Cisco reported a sharp reduction in defect discovery when reviewers exceeded these bounds. Leaders can enforce PR-size limits programmatically and require oversized work to be divided before review.
Within those limits, authors can improve review by explaining how to inspect the change. SmartBear/Cisco calls this “author preparation”: the author identifies which files to examine first and explains implementation decisions. Writing that explanation prompts the author to inspect their own work before someone else begins, and the study associated the practice with finding more defects. So preparation belongs in the review workflow as an assigned responsibility.
Prepared reviews also give colleagues a practical way to learn one another’s work. Regular cross-review spreads knowledge of the codebase and the reasoning behind changes, while personal checklists based on recurring mistakes give that learning structure. When a senior engineer reviews a junior contributor’s work against such a checklist, the review becomes coaching directed at known weaknesses. As contributors learn the patterns they previously missed, their checklists can become shorter.
Some reasoning is easier to share through immediate discussion. Pair programming provides that route, particularly when complex logic would otherwise take several asynchronous review rounds to explain. It complements prepared reviews by helping engineers build shared understanding as they write the implementation. Both practices support human judgment, but neither replaces the behavioral checks supplied by tests.
Test behavior at the boundaries that matter
Testing adds a question that structural review cannot answer: whether the implementation meets its requirements. The right test depends on the behavior and failure boundary a team needs to protect. Stable business rules, database interactions, service agreements, and complete user journeys need different scopes of inspection. Matching the scope to the risk keeps the suite useful as the system changes.
| Test type | What it examines | Where to apply it |
|---|---|---|
| Unit test | A small piece of logic in isolation | Stable business rules with explicit inputs and outputs |
| Integration test | Components and their dependencies working together | Database access and calls to external services |
| Contract test | Whether services meet one another’s expectations | Interactions among distributed services |
| End-to-end, or E2E, test | A complete user journey | Mission-critical paths |
The broadest scope also carries the greatest maintenance burden. E2E tests run more slowly and are sensitive to changes across a complete journey, so teams should concentrate them where that journey provides necessary confidence. Testing every behavior at that level increases the effort needed to keep the suite useful. Narrower tests can protect specific rules and boundaries with less surrounding setup.
Stable rules also suit Test-Driven Development, or TDD, which follows the sequence Red, Green, Refactor. The engineer writes a failing test, implements enough behavior to pass it, and then improves the implementation while preserving that behavior. When interfaces are still changing, integration tests can check the same regressions from outside the implementation at lower cost. Design stability therefore helps determine where TDD provides the most value.
Once tests exist, mutation testing can examine whether they detect deliberately introduced mistakes. Stryker, for example, can alter production code by changing >= to < or modifying a return statement, then run the test suite against the changed version. If the tests still pass, the surviving mutation exposes behavior that the suite may fail to distinguish. The team then has a specific weakness to investigate even when its coverage percentage appears strong.
Coverage measures how much code the tests exercise, so it can create misplaced confidence about what those tests actually check. For example, an application with 95% coverage can still leave a critical production edge case untested. Tests reveal defects and protect code during changes, but cannot establish the complete absence of defects. Structural analysis remains necessary because correct behavior under tested conditions says little about the effort needed for the next modification.
Even useful behavioral checks lose value when their signals become unreliable. Flaky tests produce failures that do not consistently identify a code defect, and repeated false alarms can make real failures harder to recognize. Teams should quarantine flaky tests and track their share of the suite as a team metric. This keeps unreliable checks visible for repair while protecting confidence in the remaining tests.
Fund demonstrated cleanup incrementally
Tests and structural checks reveal problems, but resolving them requires maintenance capacity. Once a product has demonstrated viability and recurring friction is visible, refactoring becomes a continuing expense that leadership needs to fund. Established design patterns can help simplify the parts of the code that repeatedly consume time. The funding decision should begin with identifiable effort, expected recurring benefit, and an owner responsible for completing the work.
A hypothetical calculation makes that decision concrete. Assume an 80-hour refactoring effort removes five hours of monthly friction for each developer on a 10-person US team. The team would recover 50 developer-hours per month. An assumed average salary of $150K, valued at approximately $75 per hour, gives the following estimate.
| Calculation item | Hypothetical assumption or result |
|---|---|
| Initial refactoring effort | 80 hours |
| Monthly friction removed per developer | 5 hours |
| Team size | 10 developers |
| Monthly capacity recovered | 50 developer-hours |
| Average salary assumption | $150K |
| Hourly value assumption | Approximately $75 |
| Payback period | Under 2 months |
| Annual productivity value | Approximately $45,000 |
The calculation converts recurring time saved across the team into an estimated payback period and return on investment, or ROI. The result represents potential capacity for other work, with any financial benefit depending on how that capacity is used. Explicit assumptions let leadership judge whether the expected benefit warrants the initial effort. They also provide a basis for checking whether the cleanup actually reduced friction.
Approved work needs a place in the delivery calendar to produce that benefit. A proposed allocation protects 15–20% of every sprint for debt reduction; another approach reserves dedicated cleanup cycles. Either requires capacity that survives feature pressure, because repeatedly reassigning it leaves the problems unresolved. Budget, ownership, and follow-up turn an approved maintenance task into completed work.
Dedicated cycles can make that commitment particularly visible. 37signals reserves a full cycle for bugs and longstanding problems, calling the practice a “bug smash”. These cycles commonly occur around holidays, when normal project progress already slows. The timing gives maintenance an explicit place in the schedule while using a period less suited to ordinary project delivery.
Alongside these operating practices, debt management has a reported association with business performance. McKinsey, a management consultancy that benefits from demand for advice on improving business and technology performance, examined 220 companies in its Tech Debt Score analysis. It found that organizations at the 80th percentile for managed technical debt had 20% higher revenue growth than those in the bottom 20th percentile. The finding links stronger debt management with stronger performance without establishing cleanup as the cause of the revenue difference.
Recurring investment also reduces pressure to make modernization depend on a “big rewrite”. Replacing an entire system exposes the business to budget overruns, schedule delays, and continuity risks, including projects that finish years late. A prolonged replacement delays useful improvements while the business continues depending on the existing system. Incremental work lets the team address a particular source of friction while that system remains operational.
For legacy systems, the Strangler Fig pattern provides a phased replacement method named by Martin Fowler in 2004. The team first places a proxy, a component that receives and routes requests, in front of the legacy system, then selects its highest-friction components and rebuilds them as separate services. As each replacement becomes available, the proxy redirects the relevant traffic to it. The legacy system’s responsibilities shrink as more traffic moves to the replacements.
Phased routing makes each extraction independently testable and reversible, limiting the commitment required for an individual replacement. Selecting the most painful components first also lets benefits accumulate before modernization is complete. This follows the earlier approach to deferred work: recorded problems receive investment once their continuing cost and the product’s viability justify it. The next task is to measure that cost well enough to choose the work.
Measure delivery performance alongside structural deterioration
Refactoring priorities need evidence from how people work with the codebase. Onboarding, production incidents, diagnosis, and review flow expose different forms of friction, so leaders should examine them together. The following proposed benchmarks give management a starting point for discussion. Their interpretation should account for the work being done and whether the measures are improving or deteriorating.
| Signal | What it indicates | Proposed healthy benchmark or direction |
|---|---|---|
| Time-to-First-PR | Onboarding difficulty and code navigation | Days rather than weeks |
| Change-Failure Rate | Share of deployments causing production incidents | 0–15% of deployments |
| Mean-Time-to-Debug | Time needed to understand and diagnose a problem | Declining quarter over quarter |
| Reviewer Cycle Time | Time needed to move a PR through review | Under 24 hours for standard PRs |
Delivery measures add context about how these difficulties affect production work. DORA, DevOps Research and Assessment, uses four measures to describe how quickly and reliably changes reach production. Together they help leaders see whether the team can release changes and recover when a release causes trouble. They cover delivery outcomes rather than the internal effort needed to understand the implementation.
| DORA measure | What it measures |
|---|---|
| Deployment frequency | How often changes reach production |
| Lead time | Time from first commit to production |
| Change failure rate | Share of deployments causing an incident |
| Time to restore service | Time needed to recover service after a failure |
A team can sustain those delivery outcomes while making the implementation progressively harder to modify. SonarQube or CodeClimate can track cognitive complexity by module, showing where understanding the code is becoming harder; both are commercial code-quality tools whose vendors benefit from demand for this monitoring. The technical debt ratio compares estimated remediation cost with estimated rewrite cost and can be followed over time. Healthy delivery results alongside rising complexity or debt burden can indicate that current throughput is consuming future capacity.
Make vendors meet the same maintainability controls as internal teams
The same future capacity matters when an external vendor builds the software. A contractual promise to “deliver working code.” leaves the cost of later changes too vague. A technical service-level agreement, or technical SLA, should define measurable maintainability requirements such as complexity thresholds, minimum mutation-test kill rates, the share of deliberate mutations detected by tests, and PR-size limits. These requirements turn the buyer’s future maintenance responsibilities into obligations during delivery.
Before assigning the work, a representative coding task can test whether a prospective vendor can meet those obligations. The evaluation should examine naming consistency, the choice of tests, and commit hygiene within a small piece of relevant work. Those details show how the vendor makes code understandable and preserves reasoning for another engineer. Evaluating both the result and the process gives the buyer a basis for judging how confidently its own team can maintain the software.
Verification needs to continue throughout the engagement because an initial task cannot govern every later change. Vendor engineers should participate in the client’s shared review process, use matching static-analysis checks locally and in CI/CD, and follow the same first-PR onboarding measure as internal hires. Shared checks expose drift while the vendor can still correct it. Acceptance tests establish useful behavior, while continuing maintainability controls protect the client’s ability to change that behavior after the contract ends.
Final thoughts
Maintainability is a decision about future engineering capacity. Every abstraction, review requirement, and maintenance investment should help the team make the next change with less effort and acceptable risk. More rules do not necessarily produce better code, and working software alone does not show how costly it will be to modify.
For leadership, the starting point is recurring friction: slow onboarding, difficult diagnosis, repeated review delays, or changes that disturb unrelated parts of the system. Assign an owner to the most costly problems, fund improvements, and check whether the expected savings materialize. Protect maintenance capacity from repeated reassignment to feature work, while keeping speculative cleanup separate from problems with demonstrated costs.
Delivery results and structural measures should inform those decisions together. A team that releases frequently may still be accumulating complexity that makes future work slower. The same scrutiny belongs in vendor agreements, where acceptance should cover both working behavior and the client’s ability to maintain the code after delivery.
The business value is capacity to respond. Code that engineers can understand, test, and change gives the organization more room to pursue customer needs without turning each new requirement into a costly reconstruction of earlier decisions.
A project in mind?
Schedule a 30-minute meeting with us.
Senior experts helping you move faster across product, engineering, cloud & AI.


