Speak to any developer and they will tell you that code quality is important to them. Every developer’s definition of quality code will be affected by their experience, background and education, making it impossible to pinpoint a single definition. Defining code quality, then, is not an easy task.
There’s a huge collection of books, blogs and StackOverflow threads discussing code quality. Out of all those references, there’s 11 common code quality traits that stick out. But those traits are broad, and it’s often unclear how to put them into practice. For that reason, every trait in this article has a list of suggested best practices to get the most out of it in any project.
Traits of Code Quality
- It does what it’s supposed to. This one seems obvious, but it’s impressive how much software gets built without the development team having a grasp of what they are building it and why. There’s no way to evaluate code that doesn’t do its job, so it needs to be the first item on any code quality list. Suggested practices:
- Understand the goal of the feature before anyone writes a single line of code.
- Understand why this feature is of value.
- Ensure all acceptance criteria are met before releasing the code.
- Don’t work on a feature that lacks clarity. Ask for more information from stakeholders to resolve any pending questions.
- As simple as possible. Deciding if something is simple or complex seems easy at first glance. But in any non-trivial application there is bound to be some disagreement as to whether something is simple. It’s natural for a developer to think that anything they wrote themselves is the right level of complexity and that anything written by others is either under-architected or over-architected. Suggested practices:
- YAGNI Principle (You Aren’t Going To Need It).
- Keep dependencies (of all kinds) to a minimum.
- SOLID Principles (Especially the S, I, and D).
- Single Responsability
- Open to extension, closed to modification
- Liskov substituion principle
- Interface seggragation principle
- Dependency inversion
- Thoroughly Tested. There are many different layers of tests these days, and a combination of all of them will give you a good confidence level that your code is in a reliable and clean state. Suggested practices:
- Unit, Integration, Functional and Acceptance Tests.
- Manual testing of happy path and common error paths.
- Relatively Bug Free. It’s impossible to write non-trivial code without introducing some bugs. Critical and major bugs should be found and resolved in the testing phase, but it’s not unheard of to ship with some minor issues. Suggested practices:
- Track all bugs: major, minor or anywhere in between.
- Fix a few bugs from the backlog in every iteration.
- Get signoff from Quality Assurance Engineers before a build is put into a customer’s hands.
- Human Readable. Any line of code will be read more often than it is written, making readability highly desirable in code. An easy way to ensure the code is readable is to ask another developer to review the code. There’s work to be done if many questions arise in the review because odds are that in two months even the original author won’t understand what he wrote. Suggested practices:
- Follow the coding standard set forth by the team.
- Perform Pull Requests or Code Reviews for any non-trivial change.
- Easy to maintain. Maintainability is a cross between doing things simply and making the code readable. Suggested practices:
- Boyscout Rule
- Refactor often
- Secure. The reasons for having security top of mind are obvious. Not a day goes by without a new report of leaked private data. You don’t want to be the next Ashley Madison. Suggested practices:
- Don’t re-invent the wheel when it comes Authentication and Authorization.
- Use existing paradigms and libraries to implement security features.
- Read Troy Hunt’s blog
- Follows coding standard. Every team should have a set of rules that all members follow so that code written by one person will flow seamlessly into the code of another person. Suggested practices:
- Ensure all team members adhere to the standard.
- Codify the rules and have them run as part of the build.
- Self-documenting. Most comments are useless. Don’t believe me? Find a comment in the code you’re working on right now. Read it and ask yourself if there is any value added by it. Odds are that there isn’t. The focus should instead be turned towards writing code that does not require comments. Suggested practices:
- Give methods and classes descriptive names that don’t require a comment to clarify their use.
- When changing code that already contains a comment, try to elimate it by making the code clearer.
- Extensible. Adding a new feature or changing an existing one should not require a complete re-write of large parts of the application. It’s tricky to toe the line between extensibility and over-architecture but an experience developer knows when enough is enough. Suggested practices:
- Domain-Driven Design
- Ports & Adapters Architecture
- “O” and “D” from SOLID principles
- Scalable. The application should work for 1 user just as well as it would for 500,000 or 1 million users. Don’t go overboard with this at the start of a project, but keep it in mind so that you have options when a performance issue creeps up. Suggested Practices:
- Cloud infrastructure that has built-in scalability:
- Azure Service Fabric
- Azure Functions
- AWS Lambda
- Cloud infrastructure that has built-in scalability:
So, which one is it?
The truth is that quality can’t be defined as one or other of these traits. As in most things, a balance amongst them all is needed. But that balance is likely to be different for every developer.
A team needs to decide together what its core values are going to be. Without this discussion every developer will do their own thing. This can only end one way: conflict within the team and ultimately bad code.
Every team within an organization will bring different values to the table. That’s why organization-wide quality goals are also needed. Those goals should be the framework upon which teams build their own goals.
A team looking to establish its quality goals should start small with 2 or 3 objectives from the above list. This approach won’t overwhelm anyone and lets the team discuss, adapt, and implement features with those goals in mind.
The reality is that writing good code isn’t something that is ever ‘done’. There’s always ways to improve it. And as you add more features, you must constantly evaluate the amount of technical debt incurred to the product and adjust accordingly.
Good code is and always will be a moving target. Determining what that moving target is should be left up to the developers who are working on the product. But there’s one goal that should always be respected: the code should always do what it’s supposed to.