Skip to main content

Code Maintainability: Managing Technical Debt in Regulated Environments

Every organisation has a technical debt problem. What differentiates high-performing engineering organisations from the rest is not the absence of technical debt — it is the discipline to manage it deliberately. In banking, technical debt carries a weight that most industries do not experience. A poorly maintained codebase is not just a velocity drag; it is a regulatory risk. When auditors ask you to demonstrate the lineage of a calculation, or a regulator requires you to explain how a credit decision is made, the answer cannot be "we think this function does the right thing, but nobody who wrote it still works here."

Robert C. Martin wrote in Clean Code that "the ratio of time spent reading versus writing code is well over 10 to 1." Across 25 years of engineering leadership, this ratio has proven even more extreme in regulated environments. Code in financial services is read by developers, testers, auditors, risk analysts, and compliance reviewers. It is read during incident investigations, during regulatory examinations, and during merger due diligence. Code that is difficult to understand is not merely inconvenient — it is a liability. During the DevSecOps transformation at a Tier-1 bank, code maintainability was embedded as a first-class capability alongside security and delivery speed, because in a regulated environment, you cannot have confidence in your controls if you cannot understand your code.

Why Code Maintainability Matters

Maintainable code is easy to understand, modify, and extend, which is essential for long-term software health. In organisations that maintain systems for decades — and banking systems routinely operate for 20 to 30 years — the cost of maintenance dwarfs the cost of initial development. Michael Feathers observed in Working Effectively with Legacy Code that "legacy code is simply code without tests." By that definition, a startling amount of code in financial services is legacy from the day it is written.

Key Components

  • Version Control: Using a version control system to track changes and collaborate effectively.
  • Code Readability: Writing clear and understandable code with proper naming conventions and comments.
  • Modular Design: Structuring code into small, reusable, and independent modules.
  • Automated Testing: Implementing automated tests to ensure code quality and catch issues early.
  • Documentation: Providing comprehensive documentation for code, APIs, and processes.
  • Technical Debt Management: Deliberately tracking, prioritising, and paying down technical debt as a regular engineering activity.

Version Control

Version control is the practice of tracking and managing changes to code. It allows multiple developers to collaborate on a project, maintain a history of changes, and revert to previous versions if needed. In regulated environments, version control is also an audit trail — it provides evidence of who changed what, when, and why.

  • Git: A distributed version control system widely used in the software industry. Git's branching model and pull request workflow provide natural review gates.
  • Subversion (SVN): A centralized version control system that tracks changes to files and directories. Still found in some legacy banking environments.
  • Mercurial: A distributed version control system similar to Git.

Example: Version Control as an Audit Control

At the bank, we enforced a policy that every change to production code required a pull request with at least one approving review. This was not just an engineering practice — it was a SOX control. The Git history served as evidence during audit that changes were reviewed and approved before deployment. We augmented this with signed commits and branch protection rules to ensure the integrity of the audit trail. When regulators asked "how do you ensure that code changes are reviewed before they reach production?", we could point to an automated, tamper-evident process rather than a manual sign-off spreadsheet.

Code Readability and Clean Code

Code readability refers to writing clear and understandable code that is easy to read and maintain. Robert C. Martin's Clean Code distilled decades of software craftsmanship into actionable principles:

  • Meaningful Names: Variables, functions, and classes should have names that reveal intent. calculateRegulatoryCapitalRequirement() tells you what it does. calcRCR() does not.
  • Small Functions: Functions should do one thing, do it well, and do it only. Martin's rule of thumb is that functions should rarely exceed 20 lines.
  • No Side Effects: Functions should not produce hidden changes to system state. In financial calculations, side effects are not just a code quality issue — they are a correctness risk.
  • DRY (Don't Repeat Yourself): Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. Duplicated business logic in banking code has caused some of the most expensive bugs in the industry's history.
  • Code Comments: Comments should explain why, not what. If you need a comment to explain what the code does, the code should be rewritten to be self-explanatory. Comments that explain regulatory context ("This calculation implements APRA APS 110 paragraph 42") are genuinely valuable.
  • Consistent Formatting: Following a consistent code style and formatting guidelines, enforced by automated linters (Prettier, ESLint, Checkstyle).

Example: Naming Conventions in Financial Services Code

Consider the difference between these two function signatures:

# Poor: What does this calculate? What are the inputs?
def calc(a, b, c):
return a * b * (1 + c)

# Clean: Intent is clear, parameters are meaningful
def calculate_interest_accrual(principal, rate, day_count_fraction):
return principal * rate * (1 + day_count_fraction)

In a regulatory examination, the second version can be understood by a risk analyst without developer assistance. The first version requires an engineer to explain it, creating a dependency that slows down audit response and introduces the risk of misinterpretation.

Modular Design

Modular design involves structuring code into small, reusable, and independent modules. This approach promotes code reusability, maintainability, and testability. Martin Fowler's Refactoring provides a comprehensive catalogue of techniques for improving the structure of existing code without changing its behaviour.

  • Separation of Concerns: Dividing code into distinct modules, each responsible for a specific functionality. In banking applications, this often means separating business rules, data access, regulatory calculations, and presentation logic.
  • Encapsulation: Hiding the internal details of a module and exposing only the necessary interfaces. This is critical in financial services where business rules change frequently due to regulatory updates — encapsulation ensures that a change to a regulatory calculation does not cascade through the entire application.
  • Dependency Injection: Injecting dependencies into a module rather than hardcoding them. This enables testing in isolation and makes it possible to swap implementations (for example, replacing a real payment gateway with a test double).

Example: Separation of Concerns in a Lending Application

In one loan origination system, the original design mixed regulatory calculations (LVR, serviceability, responsible lending checks) directly into the HTTP controller layer. This meant:

  1. The calculations could not be unit tested without spinning up a web server.
  2. Changes to regulatory rules required modifying request-handling code.
  3. Auditors could not review the business logic without wading through HTTP parsing and database queries.

We refactored the system to separate concerns cleanly: a domain layer containing pure business logic with no infrastructure dependencies, a persistence layer for data access, and a thin controller layer for HTTP handling. The regulatory calculations became independently testable, auditable, and deployable. The refactoring took three months, but it reduced the time to implement regulatory changes from weeks to days.

Automated Testing

Automated testing involves writing tests that run automatically to verify the correctness of code. The test pyramid — a concept popularised by Mike Cohn and refined by Martin Fowler — provides the structural guide:

  • Unit Testing: Testing individual components or functions in isolation. These should be fast (milliseconds per test), numerous (thousands in a mature codebase), and focused on business logic.
  • Integration Testing: Testing the interaction between different components or modules. In financial services, this includes testing database interactions, API contracts, and message queue integrations.
  • End-to-End Testing: Testing the entire application flow from start to finish. These are slow and brittle, so they should be used sparingly — only for critical customer journeys.
  • Contract Testing: Verifying that the interfaces between services remain compatible. Essential in microservice architectures where multiple teams own different services.

Example: Testing Regulatory Calculations

For financial calculations, testing is not optional — it is a regulatory requirement. We implemented a property-based testing approach for interest rate calculations: rather than writing individual test cases, we defined properties that must hold (interest on a zero balance must be zero; interest must scale linearly with principal; interest for zero days must be zero) and let the testing framework generate thousands of random inputs. This approach uncovered edge cases that hand-written tests had missed, including a rounding error that had been silently misallocating fractions of a cent across millions of accounts.

The introduction of AI coding agents adds a new dimension to code maintainability. Agarwal, He, and Vasilescu (2026) measured the impact of LLM-based coding agents on open-source projects and found that while agents produce significant velocity gains, static-analysis warnings increased by approximately 18% and cognitive complexity rose by 39%. This finding has direct implications for code maintainability strategy: AI-generated code must be subject to the same quality gates — clean code analysis, complexity thresholds, test coverage requirements — as human-written code. In regulated environments where code readability is an audit requirement, the convenience of AI-generated code cannot come at the cost of comprehensibility. The bank's golden path pipeline, which enforces SonarQube quality gates on every commit regardless of authorship, provides the necessary guardrails.

Technical Debt Management

Technical debt, a metaphor coined by Ward Cunningham, describes the implied cost of future rework caused by choosing an expedient solution over a better approach. In regulated environments, technical debt carries additional risks:

  • Regulatory Risk: Code that is difficult to understand cannot be confidently explained to regulators.
  • Audit Findings: Undocumented or poorly structured code frequently generates audit findings, which consume engineering capacity to remediate.
  • Change Risk: Tightly coupled, poorly tested code makes every change a potential production incident.
  • Talent Risk: Engineers leave organisations where they spend all their time working around technical debt rather than building valuable features.

A Framework for Managing Technical Debt

Not all technical debt is equal. We used a quadrant model (inspired by Martin Fowler's technical debt quadrant) to categorise and prioritise:

DeliberateInadvertent
Prudent"We know this is not ideal, but we need to ship by the regulatory deadline. We will refactor in the next sprint.""We did not know about the new framework when we started, but now that we do, we should migrate."
Reckless"We do not have time for testing.""What is a design pattern?"

Prudent deliberate debt is a legitimate engineering trade-off. Reckless debt — whether deliberate or inadvertent — is what sinks engineering organisations. We tracked technical debt as first-class backlog items with the same visibility as feature work, and allocated 20% of sprint capacity to debt reduction.

Documentation

Documentation is the practice of providing comprehensive information about code, APIs, and processes. It helps developers understand the system, onboard new team members, and troubleshoot issues.

  • Code Documentation: Adding comments and annotations to explain the code, with emphasis on why decisions were made rather than what the code does.
  • API Documentation: Providing detailed information about the APIs, including usage examples, parameters, error codes, and authentication requirements. Tools like OpenAPI/Swagger automate this.
  • Architecture Decision Records (ADRs): Documenting significant architectural decisions, including the context, options considered, and rationale. These are invaluable when a new team member asks "why did we choose this database?" two years after the decision was made.
  • Process Documentation: Documenting the development processes, workflows, and best practices.

Example: Architecture Decision Records in Practice

At the bank, we adopted ADRs (Architecture Decision Records) as a standard practice. Each ADR was a short markdown document stored in the repository alongside the code it described. When we chose to implement event sourcing for the transaction ledger, the ADR captured the context (audit requirements, temporal query needs), the alternatives considered (traditional CRUD, change data capture), and the trade-offs accepted (eventual consistency, increased storage requirements). Two years later, when a new architect questioned the design, the ADR provided the full context without requiring anyone to reconstruct the reasoning from memory.

Tools and Technologies for Code Maintainability

  • GitHub / GitLab: Platforms for version control, code review, and collaboration using Git.
  • SonarQube: A code quality tool that analyses code and provides actionable insights on bugs, vulnerabilities, code smells, and technical debt.
  • Jenkins / GitHub Actions: Automation servers for continuous integration and continuous delivery (CI/CD).
  • Prettier / ESLint / Checkstyle: Automated code formatting and linting tools that enforce consistency.
  • Swagger / OpenAPI: Tools for documenting and testing APIs.
  • ArchUnit / Fitness Functions: Tools for encoding architectural rules as executable tests, ensuring that modular boundaries are maintained over time.

References

  1. Martin, R.C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall. The definitive guide to writing readable, maintainable code. Its principles on naming, function size, and comment discipline remain as relevant today as when they were published.

  2. Fowler, M. (2018). Refactoring: Improving the Design of Existing Code (2nd Edition). Addison-Wesley. A comprehensive catalogue of refactoring techniques with clear mechanics, motivations, and examples. The second edition updates the examples to modern JavaScript.

  3. Feathers, M. (2004). Working Effectively with Legacy Code. Prentice Hall. The essential guide for engineers who must modify code that lacks tests, clear structure, or documentation — which is to say, most code in most organisations.

  4. Cunningham, W. (1992). "The WyCash Portfolio Management System." OOPSLA '92 Experience Report. The original formulation of the technical debt metaphor, where Cunningham argues that shipping imperfect code is like taking on financial debt — acceptable if managed, ruinous if ignored.

  5. Fowler, M. (2009). "Technical Debt Quadrant." martinfowler.com. Available at martinfowler.com/bliki/TechnicalDebtQuadrant.html. A useful framework for categorising technical debt by intent and prudence.

  6. Forsgren, N., Humble, J., & Kim, G. (2018). Accelerate: The Science of Lean Software and DevOps. IT Revolution Press. Demonstrates the empirical relationship between code maintainability practices (trunk-based development, automated testing, continuous integration) and software delivery performance.

  7. Agarwal, S., He, H., & Vasilescu, B. (2026). "AI IDEs or Autonomous Agents? Measuring the Impact of Coding Agents on Software Development." arXiv:2601.13597. https://arxiv.org/abs/2601.13597