Talking about code quality

When we talk about code quality, there's often a lot of confusion about what it actually means and the trade-offs it involves. Every software engineer seems to have their own definition of a quality codebase, so I want to start by saying that what follows is just my take on it. As you grow as a software engineer, you'll develop your own opinions on what makes code good or bad, since there's no one right answer. But that won't stop me from confidently sharing my perspective, which I hope you'll find useful. So, let’s dive into some common misconceptions and get a clearer picture of what quality in software really means.

What Do We Mean by Code Quality?

When we talk about code quality, we're not referring to functionality improvements such as runtime performance or test coverage. Instead, code quality is something less tangible.

A well-written codebase is easier to work with, regardless of the project's size. This is illustrated in the graph below, showing the relationship between the difficulty of working on a project and the project's size.

This graph shows that a project becomes more difficult to work in the larger and more complex it becomes. A “high quality” code base is one which manages to stay low on this curve even as it grows in size. 

Components of Quality

Several components contribute to a high-quality codebase:

  • Low Complexity: Simple and understandable code is easier to maintain and extend.
  • Readability: Code should be easy to read and understand for anyone who might work on it in the future.
  • Reliability: Code should function correctly under expected conditions.
  • Portability: Code should work across different environments without requiring significant changes.
  • Maintainability: Code should be easy to modify and update.
  • Testability: Code should be easy to test to ensure it functions correctly.
  • Extensibility: Code should be easy to extend with new features.
  • Ease of Error Logging/Reporting: Code should provide clear and useful error messages.
  • .. And many more

In essence, quality code tries to:

  • Section off functionality to its own scope in such a way that the programmer doesn’t need to be thinking about it when working in a different scope
  • Make it harder to introduce a bug through a simple mistake or lack of knowledge.

Misunderstanding Tradeoffs

One of the biggest challenges in discussions about quality is the difficulty people have in talking about tradeoffs between multiple objectives. Quality improvements in code rarely come without a cost. Most enhancements involve some form of tradeoff, whether it's developer time, performance, or complexity. Unfortunately, programmers often focus on the small fraction of quality improvements that require little to no tradeoff, overlooking more significant changes that could have a profound impact on the project.

No-Cost Improvements

When trying to improve code without any trade off, the options are limited. However, some improvements are straightforward and cost-free. For example, choosing a consistent naming standard for your project, such as using camel case for local variables, is a flat improvement to readability. It doesn't matter what the standard is, as long as everyone follows it. Implementing this standard has no cost because it takes just as long to write a variable name in camel case as in any other case.

A Real-World Example

Consider the following scenario in a C# code base:

A team member has written functionality where a List<float> property of a class is publicly exposed. You suggest that this isn't great practice and recommend exposing an IEnumerable instead to protect it from being modified unexpectedly. 

You argue that this change will protect the internals of the class. When working inside the class, you will only need to know about the local scope, with no fear of the object changing out from under you. If the list is exposed, who knows what strange state it could end up in 2 years from now when someone else edits it in place!

However, the team member argues that they need to pass this collection into a function that uses indexing to search through it. If the user ends up making a List of thousands of entries, making a copy would be slower. Isn’t  it risky to assume the number of elements the user would add to the list? Sure, quality is important, but so is run time!

In this case, you can't claim the IEnumerable implementation is strictly better because it involves a tradeoff.

Prioritizing Code Quality

Most people tend to prioritize the material properties of a software product, such as speed, functionality and performance, over code quality. However, this approach is flawed. Software projects are alive and evolving, and the ease of future modifications is critical. A project with poor code quality but correct functionality and decent performance can be extremely difficult to extend or modify. Conversely, a project with excellent code quality that is lacking in features or has slower performance can be easily improved.

Therefore, code quality should be the top priority when designing new software. Only when a particular component proves to be too slow should a faster solution be considered. If the code is well-written, such substitutions should be localized and straightforward. If the project's scope changes or extends, new features can be added quickly.

Takeaways

  1. Code quality is more important than most people realize.
  2. It should be prioritized above other concerns until they become pressing.
  3. This importance extends beyond non-tradeoff decisions and includes material concerns like functionality and speed.

By prioritizing code quality from the start, you set your project up for long-term success, ensuring that it remains maintainable, extensible, and adaptable to future needs.

Conclusion

‍Code quality is often misunderstood and what makes good code differs among engineers. It's more than just performance or test coverage—it's about making a codebase easy to work with as it grows. Key aspects include low complexity, readability, reliability, portability, maintainability, testability, and extensibility.Most quality improvements involve trade-offs. Sacrificing run time or development speed for quality. Most programmers prefer to avoid those difficult decisions and instead focus on the easiest fraction of positive code quality changes. This approach is flawed. Prioritizing code quality from the start is crucial for long-term success, making future modifications easier and ensuring the software remains maintainable and adaptable.

Testimonial Image

Mike Stimson