T S Vallender

The qualities of quality software

Software engineering is a vast, complicated field. What defines “good” software is just as wide and varied. I’ve come to believe, however, that generally when we talk about these things, we’re talking about three things: simplicity/understandability, capability and ease of change. Furthermore, technical debt, that great saviour and devil of the engineer’s life, can generally be classified as such because it compromises one or more of these qualities—and perhaps when we talk about technical debt it would be more useful to clarify which.

Simplicity & Understandability


In order to work on software effectively we need to understand the software (or at least the portion of the software we’re working on). The simpler something is, the easier it is to understand. This seems obvious, but achieving simplicity is often non-trivial: what constitutes simplicity is often not immediately apparent, and achieving a desired outcome in the simplest form possible is often very difficult (and part of, I would argue, what distinguishes truly great software engineers).

As an example of both the importance and difficulty of achieving simplicity, take the introduction of new dependencies to an application, in the form of a library or external service accessed via an API. Is it simpler to code a new feature yourself, or simply offload that workload onto a pre-existing solution? Perhaps the latter: we have no need to fully understand the solution as others will do it for us, we don’t need to muddy our own codebase with its complexities or even maintain it on an ongoing basis. But perhaps the former: if there is a problem with the dependency, shouldn’t we be able to fix that problem? If the maintainers walk away, what then? If we require new functionality it doesn’t support, what then?

Clearly the answer is it depends (isn’t it always), but deciding which way to fall for any particular occurrence of this dilemma requires experience and thought. How well established is the dependency? How much of its functionality do we need? How difficult would it be to write and maintain ourselves? These questions are not as simple as catchphrases such as “not invented here syndrome”, or the suggestion that modern software engineering is just “gluing things together” would suggest.

Capability


Perhaps the most obvious quality of good software is does it do what it’s supposed to? Wrapped up in this question are layers of other meaning—does it do what it’s supposed to all the time and without unintended side effects (i.e. is it relatively bug-free). Are users able to use it without making errors or lose half their day working out how (i.e. is it user-friendly). And most importantly—does it do what the customer needs it to do, and not just what they said they need it to do (this last point being why we need to work with customers in a truly little-a agile way. Noone really knows what they do or don’t need until you start building and iterating. Not you, not I, not the customer).

Ease of change


How simple a codebase is to make changes to is a key part of its quality. Partly, this is directly related to the first point—if code is simple and understood, we can feel comfortable and confident doing this. Similarly if we are confident the codebase has a strong test suite. Following common design patterns which promote separation of concerns will also help—the cleaner the separation of any one section of the codebase, the more confident we can be changing it won’t change everything else.

We never know in advance what changes are going to be needed, so this is a quality we need to pursue across the entire application. A true marker of the health of an application is how quickly a chunk of it can be entirely refactored or replaced.

YAGNI—You Ain’t Gonna Need It—is an oft-repeated phrase in software engineering, encouraging developers to only build the bare minimum. Don’t go throwing in “might be useful” features just in case because they won’t be used but will add to the maintenance burden. It’s easy to take this to an unhealthy extreme, however. Building the bare minimum for every feature will result in every feature being hardcoded and brittle. Yes, you could argue YAGNI means you don’t need to make those fields database driven, just throw them in an enum in the code and anything else is overengineering and premature optimisation—but a little extra work now to allow things to be flexible and changeable will pay dividends in future, allowing us to respond much more quickly to change and keep the application healthy and maintained.

Consistency


Again interlinked with simplicity and understandability but worth raising on its own, the consistency of approach across a codebase is paramount. There are little things such as indentation style and which language features are used primarily which can be caught with linting tools, but much more important than that is a general consistency of approach. Do we do the same things in the same ways in different places? Or is it clear that every class was written by a different engineer with little communication? If a change in approach is called for, prioritising that the approach be changed everywhere is important.

In conclusion


Good software engineering is hard. Striving to achieve these things allows us, as software engineers, to achieve much higher quality systems. There is a limit to what any one developer can keep in their head at any time, so by achieving simplicity and consistency we minimise the noise and allow for the important information to be our focus. We make our own work easier and our goals more achievable.

Updated at 2024-01-29 10:53 | 2024-01-24 16:43