Programmer time is precious. This realization should shape our approach to software development, focusing our efforts on tasks that genuinely contribute to the improvement of our code and the software ecosystem.
- 1. Hunting for bugs. I like to add tests, and then even more tests. The time spent building tests should proportionate to the time spent building the software. Fuzzing is also fantastically useful. I love using sanitizers.
- Fixing bugs. Bugs disrupt user experience, compromise functionality, and can even introduce security vulnerabilities. Addressing bugs is critical to build trust in the software.?
- ?Documentation matters. Underdocumented code is mysterious and may trigger unnecessary surprises. Lack of documentation may also harm relationships with users.
- Adding new features. Innovation and growth in software come from introducing new features. ?Features should be user visible: ‘internal’ features are often wasteful.
- Improving Performance. Performance enhancement is all about making the software run faster, use fewer resources, or handle larger workloads more efficiently. This can significantly impact user satisfaction, particularly in applications where speed is paramount. Improving performance is not just about identifying bottlenecks… it is an ongoing journey. You need a good design and multiple rounds of optimizations. You can often continue to improve the performance for years and years.
However, there are areas where I believe our time is not well spent:
- Patching code to silence false positives from disabled-by-default static analyzers. The level 4 warnings under Visual Studio when compiling C++ code is a good example, but so are the obscure GCC and clang warnings. Static analyzers are tools that can scan code for potential issues without executing the program. ?However, when these tools are overly strict or misconfigured, they might report numerous false positives; issues that aren’t actually problems. ?Spending time patching code merely to quiet these false alarms is, in my view, wasteful. It diverts attention from more impactful work. This is not to say that static analysis is not beneficial; when used correctly, it can save considerable time and resources. But the effort required to address non-issues can quickly become counterproductive.
- Aimless refactoring is also often wasteful. Renaming classes, moving code around just so that it looks ‘nice’. I am not against the occasional cleaning round… but it is should not be time consuming. Refactoring for its own sake may become an excuse for not fixing bugs or for not improving the performance. It is easy work, but often not impactful.
Let us take concrete examples. Consider the following function:
uint8_t f(uint64_t x) {
uint8_t y = x % 256;
return y;
}
When compiled with LLVM/clang with the -Wconversion flag, you get the following warning:
warning: implicit conversion loses integer precision: 'uint64_t' (aka 'unsigned long') to 'uint8_t' (aka 'unsigned char') [-Wimplicit-int-conversion]
The warning is bogus, a waste of time. There is no loss of precision (whatever precision means).
Another favorite of mine is this function:
uint64_t g(uint64_t x) {
return -x;
}
Negating an unsigned integer has many applications: e.g., if you want to select only the least significant bit, and zero all others, you can use the expression (-x) AND x. Under Visual Studio, if crank up the warnings, you get
warning C4146: unary minus operator applied to unsigned type, result still unsigned
While we strive for perfection in our code, we must also be strategic about where we invest our most precious resource: programmer time. Let us prioritize what truly matters in the grand scheme of software development.