I typically use the newsletter as a way to share interesting embedded news, summaries of new parts, libraries I've been investigating, and other miscellanea. However, I thought that the topic I discussed in this newsletter was worth sharing with a wider audience.
It is my sincere hope that your teams adopts at least one new process to help improve code quality.
Improving Our Software With 5 Lightweight Processes You Can Adopt This Month
It's that time of year when the Barr Group releases their yearly Embedded Systems Safety & Security Survey results. Last year's results were eye opening, as nearly 50% of respondents reported not using static analysis and 36% reported that they do not perform code reviews. The 2018 results were no better:
- 38% of safety-critical products don't comply with a formal safety standard
- 43% of teams working on safety critical products don't perform code reviews
- 41% of of teams working on safety critical devices don't perform regression testing (54% for IoT product teams)
- 33% of teams working on safety-critical products don't perform static analysis (49% for IoT product teams)
These numbers are even more alarming given the fact that 25% of the reported "internet-connected devices" could kill or injure people if hacked. 22% of respondents mentioned that security for connected devices wasn't even on their to-do list. We're becoming increasingly connected, but our standards for safety, testing, and verification are not keeping pace. I want to be clear: this is not acceptable.
Many teams skip crucial development processes and justify them with scheduling pressure or by blaming the boss. There will always be scheduling pressure, so we must adjust our approaches or we will continue to flounder. Bugs are expensive in time, money, and morale. Debugging accounts for 50% of most project schedules. We all make mistakes. Anything we can do to keep bugs out of our code or catch them as early as possible will save money and time.
I've selected five simple processes to improve quality that your team can adopt over the next month. These processes are cheap or free, apply across languages and platforms, and best of all - they work. While each process requires a bit of time to get up and running, there is little-to-no maintenance involved in continuing to use them.
Here are five lightweight processes for improving code quality and identifying problems early:
- Fix all of your warnings
- Set up a static analysis tool for your project
- Measure and tackle complexity in your software
- Create automated code formatting rules
- Have your code reviewed
Fix All of Your Warnings
The first thing I do when working on a new project is fix all of the compiler warnings. It's amazing to me how developers will ignore warnings or rationalize their presence. Occasionally you even run into a team who will fight tooth and nail to prevent you from fixing them!
The compiler knows the programming language better than you ever will. You should not ignore the compiler when it is alerting you to an issue. The way you are using the language is dangerous and likely has unintentional side effects. Depending on the warning, you might be introducing undefined behavior into your software. That's not our idea of quality software.
If you have warnings in your code base, fixing them is one of the fastest ways to improve quality. You will fix bugs and flaws in your program, regardless of whether or not they are currently problematic.
For more on compiler warnings:
- Warnings: -Weverything and the Kitchen Sink
- Simplify C++: Compiler Warnings Part 1: Treat Them Right
- Simplify C++: Compiler Warnings Part 2: Tune the Compiler
Set Up Static Analysis Support
Static analysis tools provide us with even better feedback than the compiler. Your compiler will happily allow some problematic cases which are legal in language, such as out-of-bounds pointer accesses or missing initialization values. Your analyzer will catch these problems, and also report red flags such as unused or redundant code. When used throughout the development cycle, your static analysis tool can help you catch & prevent latent problems even earlier than your testing cycle.
Some governmental and industrial organizations are starting to require static analysis data for certification processes. Companies such as PRQA provide tools that can check for compliance with safety critical standards in a variety of industries.
There are many free static analysis tools available and their commercial counterparts are also inexpensive (most are less than $1000). At Embedded Artistry, we use Clang's static analyzer alongside clang-tidy.
Here are some resources you can use to find a static analysis tool that fits your needs:
- Awesome Static Analysis - a curated list of static analysis tools, linters and code quality checkers for various programming languages
- Review of contemporary C/C++ static code analyzers
- Clang Static Analyzer (free)
- clang-tidy (free)
- CppCheck (free)
- CppDepend ($599+)
- PC-Lint ($389)
- PC-Lint Plus (paid, pricing hidden)
- Frama-C (free)
Measure and Tackle Complexity
By the time you've eliminated warnings on your project and cleaned up glaring problems exposed by your static analysis tool, you've already made significant progress with software quality. The next goal is to measure complexity in your software. Because highly complex functions tend to be hard to understand, test, and maintain, these functions are prime candidates for refactoring and simplification.
By using a metric to measure complexity, we have a quantitative way to evaluate our code and identify pieces that need special attention. We can see how our changes are impacting the code base over time and trigger automatic alerts and reviews whenever a threshold is exceeded. We can focus our code reviews on functions with high complexity scores, making sure they get the most of our limited attention. Metrics aren't perfect, but they increase our insight into our software quality.
These are the simplest and most popular metrics for measuring code complexity:
- Lines of code (LOC): a count of the non-blank, non-comment source lines in a function, module, or project
- McCabe cyclomatic complexity (MCC): provides a complexity score based on the number of branches (e.g. conditional statements)
- Strict cyclomatic complexity (SCC, CC2): expands MCC by considering the number of conditions within each branch, which provides an approximation for the number of test cases needed for full coverage
These free tools will calculate complexity metrics for C/C++. We currently use Lizard at Embedded Artistry.
For more on software complexity:
- Taming Complexity
- McCabe Metrics
- Avoid High Cyclomatic Complexity
- The Spaghetti Factor - A Software Complexity Metric Proposal
- Spaghetti Code and Complexity Tutorial
- Toyota Unintended Acceleration and the Big Bowl of "Spaghetti Code"
Create Auto-formatting Rules
Automated code formatting might seem like a strange recommendation to put into the top five, but it serves three purposes:
- Automated formatting reduces a programmer's cognitive load by eliminating an entire category of details and decisions they need to keep in mind
- Automated formatting improves the quality of our peer code reviews (the next recommendation) by eliminating arguments about style
- Automated formatting is the first step toward implementing and enforcing a coding standard
Every team that I've encountered with a written style guide inevitably ignores those guidelines, and multiple programming styles run rampant. Instead of relying on developers to constantly keep an arbitrary set of rules in mind, we can automate the process to make it simple and impersonal. At the very least, it's worth eliminating the pointless, time-wasting arguments that cause friction within our teams.
For more on automated code formatting:
- Creating and Enforcing a Code Formatting Standard with
- A Strategy for Enforcing Formatting With Your Build Server
clang-formatWrapper Script Examples
Have Your Code Reviewed
Writers accept the fact that first drafts are generally garbage and need heavy editing. Before I send this newsletter out into the world, it has usually gone through 2-3 self-editing sessions and 1-2 peer reviews. Along the way, the newsletter is trimmed and restructured. The result is a much better product than the initial draft.
Yet, for some reason, programmers seem to think that perfect code is produced on the first try. The 2018 Barr Group survey results showed that 54% of IoT product teams don't perform regular code reviews. The survey results also show that a painfully scary 43% of teams working on safety-critical software don't perform regular reviews.
Perfect code on the first try might be possible if you're a prodigious programmer. But remember: even Hemingway had an editor. A second set of eyes can identify flaws that you missed in your first pass. Another developer may have different experiences that provide insight into the merits or risks of your approach. The architect on your team probably has input on how a module should interface with other pieces of the program. The "ego effect" also comes into play: knowing that our code will be presented and reviewed by another human can dramatically improve the overall quality. We will spend time cleaning up and checking the logic before putting code up for judgment.
Code reviews can waste time and become unproductive if poorly implemented. Best Practices for Peer Code Review provides some excellent tips for getting started. Notably, a lightweight review process is more efficient and practical than long, in-depth reviews with multiple developers. Even performing reviews on only 20-33% of the submissions provides benefits due to the "ego effect". While 20% may seem low, remember that we are aiming for achievable: reviewing 20% of the source code is definitely better than none.
I highly recommend implementing peer code reviews after setting up automated code-formatting. This helps constrain code review discussions by preventing them from devolving into style nit-picking. If you've set up static analysis, make sure the tools are used prior to code reviews.
For more information on code reviews:
- Best Practices for Peer Code Review
- Social Aspects of Peer Review
- A GitHub Pull Request Template for Your Projects
- Phil Koopman on Peer Reviews
The Keys to Success When Adopting a New Processes
When adopting new processes, it's important to focus on one at a time. Adopting new processes in stages ensures that you have time to correctly implement each new technique before moving on to the next one. By implementing too many changes at once, you are likely to overwhelm your team and evoke a mutiny.
If you're leading a team, it helps to find someone who is excited and can help you champion the idea. Empower that person so they can demonstrate the benefits of the new process to your team. Back them up when there is pushback. Change is always hard. Expect the resistance, but don't let it stop you.
The key to making new processes stick is to make them as automated as possible. There is never a case where it isn't worth the time it takes to automate a development process. Automation ensures that the process is easy to follow and always happens, rather than trusting individual contributors to remember to follow a process. Automation also makes the process less personal. The rules are clearly defined and are being enforced by a tool. Depersonalization helps us view the situation dispassionately, rather than as an attack on our abilities.
To recap, when you implement new processes:
- Adopt one new process at a time
- Empower a process champion on your team
If you liked this article, sign up for our monthly newsletter to receive interesting embedded tidbits, articles I've enjoyed reading, summaries of interesting new parts, companies looking for engineers, and other miscellanea.