The Modern Developer, Part 4: Code Review and Quality Assurance
As a software developer, your job isn’t just to write code. It’s to deliver quality solutions to complex problems.
Is the code you’ve written high quality, allowing the software to do the job it is supposed to? When the software encounters an error, does it handle it gracefully? Is the software secure and performant enough?
There are two processes that are used to measure and raise the quality of a software project: code reviews and quality assurance. Let’s examine each of them and see how you can implement them in your workflow to deliver better results.
A while ago, I wrote an article about high quality code. There are a few things I would like to revisit in this article:
Self Code Reviews
The first thing to know is that self code reviews are absolutely mandatory.
The second is that there are codebases that are of such low quality and with so many inconsistencies that everyone has given up on them. This means that they are merely patched up and developed further only when absolutely necessary.
For this type of code base, I’ve created a “lite” version of the code quality checklist from the initial article. I also use this lite version while writing code on the fly.
When I write code, I usually leave out quite a lot of to-do’s to refactor later on. This way, the code reaches an acceptable level of quality, and I don’t have to distract myself from the business logic. Since I’ve started using this list, the to-do’s in my code have gone down significantly.
Here is the checklist:
- Does the new code follow the guidelines and style of the file you are editing ? – Even if the convention is old, ugly, and not optimal, if you don’t have the time to fix everything, keeping with the current convention is most likely a better approach than introducing a second style.
- Are the entry points as restricted as they should be? – Entry points to a module/class should have as many checks as they need. If you guarantee that all of your private functions and business logic functions work with clean parameters, this will remove a lot of boilerplate code and will make the crucial functions much smaller and easier to read and debug.
- Are the variable and function names descriptive? – Even if they are long, this is a much better solution than having small, cryptic names. For “rotting” code bases, this helps with reading the code in the future. For newly developed code, a name that is long will eventually help you choose a good alternative, since you can rework the name itself to be shorter but with the same meaning.
- Is the error checking strict enough and logged? – Every error must be logged. Additionally, you want to make as many checks as possible—as long as they don’t overlap. Checking the same thing twice makes no sense. Try to handle multiple cases with a single check.
- If an error occurs, will I be able to understand the entire problem from a single line? – When an error occurs, the log should be so descriptive that you don’t need additional debugging information to understand what went wrong.
Team Code Reviews
The more common approach to code reviews is to have them performed by someone from the team. The process itself depends on the company and may vary, but the important thing is that feedback must be given.
With regard to the reviewer, they must give constructive feedback. The reviewer should know what the code is supposed to do. A hallway-code review—when you get feedback on a piece of code from a random colleague—is an exception, but this is rarely used.
When it comes to you, the reviewee, always assume good intentions. Chances are both you and the reviewer want to deliver the highest quality code possible. Do not take any criticism about your code personally—you are not your code, and you are not your work.
Don’t forget that the purpose of the code is to solve a problem. If you disagree with the reviewer on something, approach them and have a short discussion. I suggest you skip the email, unless it is company policy, since verbal communication is better when it comes to solving these kinds of misunderstandings.
Code Review Software
There are tools that do code review automatically. Although they can catch some bugs and follow rules such as “no function name longer than XY symbols,” they aren’t that useful … yet.
To me they are extended code and style linters. Even though some of them can point out cyclomatic complexity issues, they can’t catch business errors. Feel free to use them, but they can’t replace the self code review or the team code review.
Don’t Skip Code Reviews
Although time concerns and pressure to release as soon as possible may tempt you to skip code reviews, this is always a bad idea.
Software is a team effort, and as a team it is your responsibility to deliver the highest quality software you can. Code reviews are one of the simplest ways to improve the quality of your work and verify the success of the feature you are releasing.
Remember that even a 10-minute self code review can end up catching business-critical bugs that would be shipped into production. Don’t compromise quality. Do code reviews.
Quality assurance (QA) has passed through many trends. The industry is heading toward automated testing, but interestingly enough, there are still quite a few positions open for manual testers.
Let’s break down some of the most popular QA approaches and see how each one raises the quality of the software.
Quality Assurance Done by the Developers
This is when a developer, usually someone from the team, tests the feature for correctness, acting as an end user. This is one of the oldest approaches, partly because management doesn’t want to hire manual QA personnel. The end results are usually pretty poor.
Most of the time, developers have enough on their mind already and won’t treat the testing with the same level of detail as their development tasks. In theory this shouldn’t happen since it’s unprofessional, but in practice this is exactly what happens.
Nowadays, the only legitimate way testing is done by developers is internally between the devs, and often by the team leader, before a feature is pushed for real QA.
In the oldest form of QA, one or more people manually test the software. Software with Graphical User Interface is primarily tested manually. Although its market share has gotten smaller, the predictions that it would disappear completely have so far been wrong.
Although quality assurance done by humans will not be perfect, humans learn—unlike automated tests. An experienced quality assurance specialist who has tested the system for months will often catch issues and cases that the developers and designers haven’t considered. Knowledge about the system and how it’s used is worth its weight in gold.
Since QA is done manually, some of the most useful user experience and usability feedback can come from the experienced quality assurance engineers. When this feedback comes, use it and don’t dismiss the QA engineer when they have ideas about improving the product.
Automated tests are great! Write them once, and then run them as many times as you need and for whatever reason.
Want to check your code before committing? Sure! Want to run regular tests on production to ensure that the system is running correctly? No problem! How about running them automatically after every merge in master? Let’s do this!
Use cases for automated tests can be numerous. The examples above are just a small fraction of them. But why aren’t automated tests done by everyone?
Unfortunately, as great as automated tests are, they come at a considerable price. Since automated testing is done with code,you have to do quality assurance and testing on the tests themselves. Furthermore, any change in the software will require change in the tests.
In short, automated tests require significant resources to function. This is why they are absent from so many companies.
For me, automated tests are worth the investment if done correctly. My checklist for writing automated tests is as follows:
- Have automated tests for business-critical features.
- Make them as simple as possible even if the code is ugly.
- Do rigorous testing on the tests to ensure that they work in all use cases and don’t return false positives.
- Start implementing the tests when a feature is nearly in QA to minimize the number of tests that will be thrown away due to major code or functionality changes.
Automated tests can save hundreds of hours in debugging for big projects. They are a big advantage when done correctly. The only real downside is they require resources allocated to them constantly. At the end of the day, automated tests add a lot of value to a project, so you should advocate for them as a developer.
Functional and Nonfunctional Tests
So far we have categorized tests according to who or what executes them.Now we’ll explore some types of tests focusing on what is being tested.
There are two main categories: functional tests and nonfunctional tests.
Starting with the former, functional tests are those that test what the system does. Examples include:
- Unit testing – typically performed by the programmer and tests the smallest components of a software product, such as functions and classes.
- Integration testing – examines whether the different modules and subsystems work well together and communicate correctly. It’s especially effective for distributed applications.
- Acceptance testing – performed by the client or the product owner and tests the entire functionality of the system. Successfully passing acceptance testing usually means the system is close to a production release.
Despite being tempted to skip unit and integration tests, most of the time, skipping them will slow you down more in the long run. Don’t try to cut corners—do things properly so once the project is released, you will have very light maintenance instead of having bugs haunt you and bring your productivity down into the next one or two quarters.
On the other hand, nonfunctional tests are those that check how the system should perform certain tasks:
- Performance testing – examines the speed in which the system will work under normal load.
- Stress testing – checks how the system will behave when there are many concurrent users.
- Recovery testing – reveals whether the system can recover from a crash. Usually, the processes start running without many problems, but the storage—file system and databases—will go into recovery mode. Sometimes, the only way to get the system operational will be by using a backup.
- Security testing – If you can afford a security audit, have one. If you can’t, there are a lot of lists with the most common vulnerabilities for a given technology, e.g., web. Make sure that you don’t have these security holes, and your system will be better off than 95% of all software.
Functional tests tend to take precedence over nonfunctional tests in most organizations. This is quite unfortunate because each type of test is made to catch specific errors, and projects usually encounter all manner of errors sooner or later.
Even if the resources allocated for QA aren’t sufficient, all the main types of tests should be performed before the system goes into production.
Don’t Forget Functionality Testing
A feature can have no bugs, work fast, and be user-friendly, and still not be a good feature. More important are questions like: Does it perform the task that it is supposed to? Is it useful to the system? Does it add value?
All too often, features don’t bring nearly as much value as they could if they went through a couple of feedback loops with the intended users. This isn’t something that can be solved with a code review or by a great QA engineer. This is something that exists at a company-culture level.
Software Quality Is a Journey, Not a Destination
As long as software changes, keeping the quality up is a constant battle. It won’t always be straightforward, and it won’t always be easy, but it is a necessity that keeps the project vitalized and with low technical debt.
Doing self code reviews is one of the cheapest and most effective ways to deliver higher quality software. Similarly, the use of automated linters and style checkers will help you concentrate on the business logic.
But even beyond that, involving your colleagues will help you deliver a better architectured code, and doing quality assurance, both manual and automatic, will reduce the number of bugs that make it to production.
As a software developer, always remind yourself that keeping the quality of the code high is just as important a part of your job as adding new features.