People almost never see the underlying code that drives the software they use. Their interactions with the software are limited to the text, graphics, and controls that make up the user interface, or the UI, of the software. Because people’s experiences are limited to the UI, it is common for people to over-emphasize its role when commissioning custom software development.
We have seen this over-emphasis on the UI lead to a variety of problems and misunderstandings with our clients, usually centered on two issues:
- Not realizing the amount of non-UI-related work that needs to be done for a project.
- Not understanding or even being aware of the importance of high quality code.
The first misunderstanding can lead to mis-matched expectations concerning the size of a project or the amount of progress made towards its completion. The second misunderstanding is more subtle, but can lead to large and expensive problems that often only appear towards the end of a software project. The main reason for this misunderstanding is:
The UI is visible and tangible, and it is possible for non-technical people to gage whether it is built well. Poorly written code, i.e. bad code quality, is usually hidden from non-technical people. Furthermore, a high quality UI does not necessarily mean the underlying code is written well.
Poor code quality can be an extremely expensive problem to fix. This article describes what code quality is, why its important, and how to handle issues related to it. We hope this article will help managers, business-owners, or entrepreneurs who are interested in building custom software avoid some common problems related to code quality.
The symptoms of poor code quality 🔗
Poor code quality doesn’t usually become apparent until one of the following occurs:
- Progress on the project slows dramatically as it seems to near completion (and occasionally it never is completed)
- The software, once in use, is very unstable and crashes frequently
- The software works when you use it the “normal way” (e.g. what you may see in a demo), but can’t handle “unusual” behavior
- There is a steady stream of bugs, even many months after the software has been released
- Whenever new functionality is added to the software, other parts break; said another way, it is very difficult to modify or extend the software while maintaining good quality assurance
- When you hire a new agency to take over the code, or to help with the project, they complain about how difficult it is to understand the code; they may even suggest re-writing all of it.
Most of these issues doesn’t arise until the project is far along, and a large amount of poorly written code has accumulated.
It is often cheaper to completely discard poorly written code than it is to try and improve it.
A significant number of the projects we have worked on are re-writes of existing software that had exceptionally bad code quality.
What makes some code good or bad? 🔗
Not everyone agrees on how best to structure a program, and there is a large spectrum between terribly written code and high-quality code. However, there are many agreed-upon coding best practices that, when followed, will improve the software that uses them.
Steve McConnell in Code Complete (a well-known if slightly outdated programming book) said:
Managing complexity is the most important technical topic in software development.
We completely agree. Even the smallest projects, which may appear simple on the surface, involve many different systems that must work together in complicated ways. Software is extremely complicated and can stretch the limits of what the human mind has the capacity to keep track of at once. Most programming best-practices stem back to managing complexity well, and answer the question: how do we keep the code as simple as possible, while performing the task that it needs to perform?
In the next few sections, we discuss some specific aspects of code quality.
Lack of automated tests 🔗
When a piece of software is small, manually verifying its functionality is feasible. As it grows in size and complexity, the number of verification steps quickly becomes too large to perform consistently. As a result, developers will often skip verification steps after making changes to the code, and often times this is when new bugs creep in. During the initial development, these bugs are less common, however once the software is being used, it becomes a much larger problem.
Fortunately, there are many ways to automate the testing process, so that computers will test a variety of aspects of the software to ensure it is working properly.
We believe that automated testing is a critical component of quality code, and it is often the single biggest technique that can help reduce the number of bugs in a software system.
Automated tests take time to write, and when the software is updated, the tests must be updated as well. For this reason, tests add additional up-front work when building software. Unless the project is very small (or there are other good reasons, as discussed further along in the article), this extra up-front cost will be paid back several times over during the lifetime of the code.
Another advantage of automated tests, besides providing quality assurance, is that it acts as implicit, up-to-date documentation about how the software is supposed to function. Automated tests also make it easier for new developers to work on the code, because they will be warned by the automated tests if they accidentally break something (the tests act as a safety net).
Unfortunately, because tests don’t directly contribute functionality to the software, it is easy to skimp on writing them (or even skip them completely). The problem is further exacerbated because the issues caused by a lack of tests aren’t visible for a while. Agencies under tight deadlines (or who have over-promised results) will often use this technique.
Code readability 🔗
For all but the most short-lived software projects, the code being written for it will be read many times more than it is written.
High-quality code is written so that it is as easy as possible for other developers to read it.
Difficult to read code is problematic for a lot of reasons. Identifying and correcting bugs takes longer, adding new functionality is more difficult and error-prone, and new developers will take much longer getting started.
There are many factors that affect codes readability.
- Poor or outdated names
- Dead code interspersed alongside living code
- Inconsistent formatting
- Placing related code in far apart from each other in the files
- Special hard coded cases
the software is upgraded, and some old features are no longer used, but the code is never removed)
All of these factors occur naturally over time. For example, a button in the UI that was named the
blueButton in the code has its color changed from blue to red, but the name isn’t updated in the code. Another common example is: a client requests specific functionality for one employee using the software, so the developer goes in and hard-codes the employee’s id number in several places to make this happen. Now, a year from now, nobody will know what this magical number means or what its purpose is.
Code duplication 🔗
Poorly written code often has a lot of duplication.
Imagine your web application sends emails from fifteen different places in the code. The sloppy way to handle this is to copy and paste the code that sends emails to each location, and then make little modifications where necessary.
This code duplication is bad.
What happens if you change email servers, or want to add analytics to see how many people open the emails? Now, the code must be updated in fifteen locations! It is all too easy, especially for a developer that is new to the project, to forget a spot. Whats worse, the place they forget will likely be in a rarely used feature (because if its not it will be identified quickly), and the newly introduced bug won’t show its head for several weeks.
High quality code avoids duplication by combining shared functionality into reusable pieces of code that can invoked whenever it is needed. This makes the code easier to read, and repairs or extensions only need to be applied in one place.
Not handling “exceptions” 🔗
In programming lingo, an “Exception” is a rare-event that should be handled differently in the code.
For example, imagine you commission software that must send emails when a rare business events occur. The software works well for over a year, then you stop receiving emails completely. After a few months of missing emails, you realize that the custom software you had built is failing silently! It turns out that your IT department changed a setting on the firewall a few months back, and the custom software has been failing because of this without warning.
The custom software probably should handle this “exceptional” situation gracefully—perhaps by waiting fifteen minutes and trying to send the email again. Then, if it fails a second time, it should display a message to all the administrators.
There are many types of exceptional situations that can occur during software use. Many of them never occur during development, because the software is only being used by a single developer. Examples include:
- Two users editing a piece of data at the same time
- A user impatiently clicks a button 10 times while something is loading
- The server runs out of memory during an automatic security update
- Handling missing or bad data (e.g. an invalid email address)
- A user logs into the site on an older web-browser
Poor quality code often does not handle exceptional situations well.
That being said, it is usually unnecessary to handle every exceptional case that can occurs in an application. Ideally, the trade offs involved and the likelihood of the various exceptions occurring will be discussed up front, instead of being revealed by strange and hard to find bugs after the software has been released.
Technological debt 🔗
Writing high-quality code takes more time up front than poorly written code. Sometimes, it can make business sense to “get something out the door”. This is often the case for early stage startups; getting a buggy, error-prone version of software into the hands of early users can be more valuable than having high-quality code, and then realizing that nobody wants to use your software anyway.
The accumulation of poor quality code is often referred to as technical debt. Technical debt is similar to taking out a loan to get the software up and running quickly, but just as with a real loan, you will pay interest in the form of more expensive maintenance and future development. The more bad code you have, the more interest you will have to pay. Eventually, you can build up so much technical debt that it is better to abandon the software and rebuild a new version from scratch.
Sometimes it is also possible to “pay down the technical debt” by investing time cleaning up the code. This process, often referred to as refactoring, involves making changes to the code to make it easier to read and work with, without actually changing the core functionality.
Technical debt isn’t a bad thing in-and-of-itself! It can be a viable strategy for a startup to bootstrap itself on technical debt. However, if you own or work for a more established company it will likely make more sense to avoid accumulating technical debt, and have the software written properly from the beginning.
Strategies to help ensure code quality 🔗
It can be very difficult for a non-technical person to monitor the code quality of a project.
Imagine that you are having your house custom built. You walk into the half-finished house and look around. Maybe you can identify some suspicious construction, however you will naturally be very unsure of yourself. If the general manager assures you this is normal, where do you go from there? Also, what about deeper problems that you may not be able to identify?
Here are a few strategies that you can use to help ensure you have the level of code quality you need for your project:
- Discuss these issues with your development team, before signing the contract. If you require very higher levels of code quality and uptime, realistic agencies will alter their estimates for the project.
- Discuss testing strategies with the agency, as well as what methods are in place to ensure enough tests are being written. There are tools that will determine what fraction of the code is covered by tests—these metrics can be misleading, but they are better than nothing in many cases. You can set a certain required level of code coverage.
- Use an automated code quality tool. For example, we have had good experiences with Code Climate.
- Ask the agency if they would mind having the code audited once or twice by another agency. An experienced developer should be able to give you reasonably good feeling for the level of code quality in a project after looking through the code for an hour or two.
- If nothing else, ask to look through the code yourself! In some cases code quality is so poor that it will be readily apparent even to a non-technical person.
Software is complicated. Although the user interface is a very important and visible component of software, there is a lot going on under the surface that is less visible. Even though it is hidden from sight, it is important that the level of code quality throughout the project meets your business needs, as poor code quality can be an extremely expensive problem to fix.
Hopefully this article helped you understand some of the common issues related to code quality, and provided some advice on how to ensure you are receiving the level of code quality you expected.