Code quality: what it means for business and how to ensure it
Translated automatically from Russian. Read the original →
For the client, code quality is not a matter of the programmer’s taste. It is money: how much it costs to add a feature, how quickly a bug can be found and fixed, whether the project can be handed over to another engineer. Bad code works only until the first change, and then each edit becomes more expensive than the previous one. I explain how I ensure quality and what you get for it. The overall process is described in the article Software development from scratch turnkey: from idea to release.
What quality code is
This is code that:
- Works correctly and this is confirmed by checks.
- Is readable by another person without the author’s help.
- Is easy to change: an edit in one place does not break five others.
- Is secure: takes into account Secure software development: how protection is built in from day one.
- Works predictably under load and in abnormal situations.
Why this affects the cost of ownership
The first version of a system usually costs less than the next ten years of its life. If the code is tangled, expenses grow:
| Symptom | Consequence for the business |
|---|---|
| No one understands how it works | Any edit carries risk and costs more |
| No tests | Every release is a lottery, users find the bugs |
| Everything is connected to everything | A new feature requires reworking the old ones |
| Knowledge is in one person’s head | Dependence on a specific contractor |
| Technical debt has accumulated | Development speed declines over time |
My task is to keep the cost-of-change curve flat rather than letting it take off over time.
Practices I apply
Architecture and module boundaries
Quality begins with Software architecture: design and planning before the first line of code. If the system is divided into modules with clear boundaries and responsibilities, the code is easier to understand and change. I hide external dependencies behind interfaces.
Strict typing
I choose languages and settings that catch errors before launch. In Rust, Java, and TypeScript, the compiler cuts off entire classes of problems. For example, the sqlx library in Rust can verify SQL queries against the database schema at build time. The more errors are found by the compiler, the fewer remain for production.
Unified style and automatic checks
Style is not discussed in review — it is applied by tools: formatters (for example, Prettier, rustfmt) and linters (clippy, ESLint). This saves time and removes arguments. I introduce code style from the start of a project, as I did on FlameApp.
Code review
Every change is reviewed before merging. In review I look not only at “does it work,” but also at:
- whether the names and structure are clear;
- whether there is duplication and hidden coupling;
- whether errors and edge cases are taken into account;
- whether a vulnerability has appeared, more on this in the article about Secure software development: how protection is built in from day one.
When I work with a team, review is part of training newcomers. How I built this process as a team lead is described in the projects Tezish and FlameApp.
Automated tests
Tests are insurance against breakage as the system evolves. I write tests at different levels: unit tests for logic, integration tests for integration with the database and external services, and end-to-end tests for key scenarios. Details are in the article Тестирование и приёмка ПО: как убедиться, что система работает до релиза.
Continuous integration
Every change is automatically built, checked by linters, and run through tests. If the check fails, the change will not get into the main branch. I set up the pipeline on GitHub Actions, in particular, on the project FlameApp.
Documentation and recorded decisions
Code answers the question “how,” while documentation answers “why.” Key architectural decisions are recorded in short ADRs, and each module has a description of its purpose. The documentation is small but up to date.
Technical debt control
Technical debt is inevitable, but it must be managed. I record conscious compromises: what was simplified, why, and when to return to it. This distinguishes debt from negligence. Part of the time in the plan is allocated to refactoring so that the system does not age faster than it evolves.
How to measure quality
Subjective “I like it” is not enough. I look at:
- the share of code covered by tests in critical parts;
- build time and check run time;
- the number of comments in review and recurring problems;
- the number of defects that reached users;
- the time needed for a new person to understand the project and make their first change.
What the client gets
- Source code that can be handed over to another engineer or team.
- Automatic checks running in your infrastructure.
- Documentation and recorded architectural decisions.
- Predictable cost of further changes.
Need an audit or code you are not ashamed to own?
If you have an existing system, I can conduct a code audit: find problem areas, assess technical debt, and propose an improvement plan. If you are just starting, we will build in quality from scratch. Write to me: Telegram @w1shmaster.