top of page
  • Writer's pictureCraig Risi

Designing for Quality

I have spoken many times before in my articles about the importance of design in software quality and hope you shouldn’t merely just be testing your software to verify its quality, but rather look to build that quality in front the onset. However, today I wanted to just go into further detail on this subject and detail some more specifics of how architects, developers and testers alike can go about designing their software and code to be of high quality. I take many points raised in previous articles and try and consolidate and expand on them further to hopefully provide a good guide for teams to follow if they aim to design better quality software from the onset.

I guess to start with, I should just reveal what I mean by high-quality software because that in itself can be an ambiguous statement. By quality I mean not just software that is relatively low on defects, but also meets the needs of its customers and core purpose, systems that can be recovered quickly from failure and also that is adaptable, scalable and maintainable. Quality really encompasses all of these things and so when I say you need to design for quality, it pretty much requires thinking about all these different aspects of software design.

So, given that rather broad, but important, definition of quality, let’s share some ideas on how you can build your systems to be able to achieve these goals:

Keep your design small, simple and modular

Firstly, we often fail to achieve quality because we are over complicating the solution. Even a complex piece of code that deals with varying different calculations can be made simple. The problem is we tend to add too much complexity to cater for too many different calculations within the code rather than keep the core code for this module simple and rather writing another module elsewhere to deal with some separate calculations. This might sound like a lot of extra work and lead to additional modules that only make integration more complicated, but in keeping the codes purpose easy, it makes it far easier to code, test and maintain.

And if you apply the same sort of thought to your architecture and rather have microservices that are small in size, it also makes it easy to alter and scale your software to better adapt and grow as you and your system do.

Along with having a simpler test design, it is vital that in your actual code repositories and design that all forms of data, configuration and testing be in the same pipeline, but isolated functionality from the core logic. All code should have its purpose and stick to that purpose and you shouldn’t have any piece of code that is trying to do more than one thing.

Fewer independent modules of code might feel easier to manage, but it is actually just more complex, and complexity is what leads to uncertainty in coding, increased mistakes and lack of good test coverage. Rather have lots of independent modules of code that are small ad far simpler, yes it might increase the lines of code you have in total, but it’s a small price to pay for more reliable code.

Design for testability

Going along with the above is coding for testability. We should code in a way that we can easily write unit tests for everything that it needs to do. If we’re writing big, complex pieces of code, we are making it difficult to test and automate, which is not ideal. If I am writing code that I don’t know how to get 100% code coverage through my test cases, then there is a good chance your design is wrong or that a new approach is needed that better meets your testing needs.

Test-Driven Development is a buzz word that many people like to say they practice, but all they end up actually doing is write a lot of unit tests after they’ve designed their code and try to achieve a high code coverage. True TDD is where design your tests first and make the code adapt to the needs of the test. Something that we all know, but seldom practice.

I guess, it should also go without saying, that you need to test your system effectively at all tiers: unit, component, integration and bigger.

Write readable code

Code shouldn’t just work and get the job done; it should be readable too. You are not just coding to finish your story and have deployable code you need to also think about the long-term maintainability of the code and the easier it is to understand the code and see what each line of code does, the easier it is to debug and maintain.

I’ve seen code that is difficult to work with before, that teams end up having to rewrite entire pieces of functionality just tot replicate it because it was so unworkable. Valuable time wasted and something that added a lot of extra risk to a project. Do not underestimate the value of code that reads well.

Start your traceability early

Too often when companies are looking to do POCSs on their code and figuring things, and not making an effort to document what they do or aligning their testing efforts appropriately. The problem is that this leads to lots of code that might be throwaway, poorly structured, or not clearly defined and that makes the testing and future maintenance of it difficult. While there will also be experimental work in a dev environment, teams should also try to put together clear specifications as they figure them out and then map them (to ensure traceability) to the automated tests and code straight away. All further refactoring of the code will then become far simpler and you will save a lot of effort in future trying to support the code.

Design around the non-functional too

The majority of testing is focused at a functional level, largely because it’s the tangible parts of our code we can use and easily test, but don’t side-line the importance of security and performance early on and ensure you have the appropriate tests in place to do so. You want to catch these issues early. It may be an immediate urgency early on in the design phase when you’re simply looking to build an MVP (Minimum Viable Product) without worrying about scalability or performance but by putting these in early, you will already get a sense of how different parts of your application will perform at differing loads and it will help you land on the right, performant design earlier. And like with a testable design if you are unable to performance test you code in a simple fashion, there is a chance that your design may not be fit for purpose.

Have a deployment and monitoring plan before you go live

No code or system works in isolation and to truly know what goes on with our software we need to have effective and sufficient monitoring in place to better understand how it is performing in production and also be able to respond to errors fast. This may not seem like something to worry about as you first start out, its something you will want to have in place early so that you can respond to failure and support your systems better. Yes, if you’ve followed some of the tips above, you will hopefully have fewer failures, but things can still easily go wrong and how fast you can recover and correct plays a big role in establishing the quality of your system.

Quality design leads to quality code

These points apply to both a code and design level because not only should code be easy to test, but so should your core UI design of the software. Making your software design more modular, simpler to navigate and with the right tags that work with automation tools, allows for a design that leads to simpler, more testable code. So, if you are a designer, also ensure that you are focusing on the simplicity of flow and find ways of reducing complexity to the backend. And even then, it should be easy to map UI functionality to small backend code where possible, as this not only makes the testing effort.

Any well designed and high-quality application doesn’t happen by accident. It is the result of well-designed intent and you want to ensure you design your system and the underlying code with quality design in mind. 



Thanks for subscribing!

bottom of page