How Do We Maintain Code Quality and Technical Debt in Our .NET Codebase?

Maintaining code quality and managing technical debt is a never-ending journey in any codebase — and it’s no different for us working with .NET. As a maintainer of the ABP Framework and someone who regularly provides support and training for developers building real-world enterprise applications with ABP, I want to share some practical strategies and lessons we apply to keep our codebase healthy.
This article is also inspired by the insightful Reddit thread, which includes many community experiences worth checking out.
How do you maintain code quality and technical debt in your .NET codebase?
by u/StudyMelodic7120 in dotnet
1. Code Reviews Are Not Optional
Every change, big or small, goes through a peer review. We focus not only on correctness but also readability, performance, and adherence to our internal conventions. Code reviews are also an opportunity to ask: "Is this solving the root problem, or just adding a workaround?"
Here are some examples of reviews I made in our internal repository:



These comments reflect our approach to naming conventions, encapsulation, and avoiding unnecessary complexity. They may look small, but they compound over time to create a much cleaner codebase.
We also:
- Prefer small pull requests
Large pull requests are harder to review, understand, and test. We encourage developers to split big tasks into smaller, logically separated PRs whenever possible. That said, sometimes large pull requests are unavoidable — for example, during framework-wide refactorings, or when introducing a new feature that touches many parts of the system. In those cases, we pay extra attention to commit messages, review scope, and clear explanations to make the review process smoother. This allows reviewers to focus better, and helps us give faster and more meaningful feedback. A good rule of thumb is: if a review takes more than 20 minutes, it's probably too big. - Focus on intent, not just syntax
We're not just looking for style issues or formatting. We try to understand the purpose behind each change. Is this solving a real problem? Could it be simplified? Does it align with the overall design? These kinds of questions help us keep the codebase clean and consistent — not just technically correct. - See reviews as collaborative, not personal
We don’t treat code reviews as a gatekeeping process. Everyone gets feedback, no matter their title or experience. Our tone is respectful, and we try to explain why we’re suggesting something, not just what should change. It’s about sharing ownership and helping each other grow.
2. Enforce a Clean Architecture
We follow a modular and layered architecture, often Domain-Driven Design (DDD) inspired, with clear boundaries between application, domain, and infrastructure. This is baked into the ABP Framework itself, which helps keep dependencies clean and manageable.
We also use solution templates and naming conventions to scaffold new modules and reduce ambiguity.
At the code style level, we rely on .editorconfig
files to ensure consistency across contributors and environments. This eliminates distractions during reviews and helps keep the codebase uniform regardless of who writes the code.
3. Refactoring is Continuous, Not a Sprint
We follow the Boy Scout Rule: "Always leave the code cleaner than you found it." In practice, this means that whenever a developer modifies a method or class — even for a small bug fix — they are encouraged to improve the surrounding code if they notice outdated naming, unnecessary conditionals, or code duplication. It’s a small effort in the moment, but it contributes to long-term maintainability.
4. Limit Framework and Tool Overload
One thing I’ve personally seen go wrong is mixing too many third-party libraries or introducing unnecessary layers of abstraction. We aim for minimalism in our stack and avoid the temptation to "over-engineer."
That said, our case is slightly different from a typical application team — we are building a framework. Not only that, but ABP also includes pre-built application modules that are meant to be reused and customized by final applications.
Because of this, we design many parts of our codebase to be extendable and overrideable by consumers. For example, an application may want to change how a particular module behaves, or swap out a default service implementation. This flexibility is intentional — and necessary — for a framework like ABP.
However, to outsiders or newcomers, this can sometimes appear as unnecessary abstraction or over-engineering. But in our context, these patterns are critical for long-term maintainability and reusability.
Before introducing a new library or architectural layer, we always discuss:
- Why it’s necessary
- Whether we can achieve the same with existing tools
- How easy it is to test or remove later
One of the top comments in the Reddit thread really resonated with me:
"Consistency and encapsulation is key. Build everything as constrained as possible. The worst that happens to a codebase is a dev not being limited by the existing API. Not being limited to doing things 'correctly'."
"Basically any public setter is a ticking time bomb (if it’s not just a response or request property bag). [...] Technical debt is one thing, but I like to divide it into 2 categories: complexity you chose and complexity that you don’t choose."
This reflects a lot of what we also believe while building and maintaining the ABP Framework. Limiting public surface, thinking deeply about constructors and validation, and constraining how modules interact are all core parts of how we work. We design for extensibility, but with strict boundaries — allowing consumers to customize behaviors without compromising the integrity of the framework.
Codebases age, and every decision we make either compounds technical debt or pays it off. By keeping quality in mind every day — not just during "refactoring weeks" — we’ve found a sustainable way to manage complexity in our .NET projects.
Thanks for reading. If you're curious how other teams approach this, or want to share your own experiences, check out the Reddit thread.