30 January 2025 · 3 min
- 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.
- 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 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 a 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
Daniel Lemire, "Programmer time and the pitfalls of wasteful work," in Daniel Lemire's blog, January 30, 2025, https://lemire.me/blog/2025/01/30/programmer-time-and-the-pitfalls-of-false-positives/.
[BibTeX]
So naming things is hard and renaming them is wasteful?
Re, Patching code to silence false positives…
As you say, it may not be worth the time it takes. And if contorted code is required, that puts a burden on future maintainers.
I suggest, however, that adding comments explaining why the warnings do not indicate an actual flaw can save time for your inheritors.
“uint64_t g(uint64_t x) {
return -x;
}`
Why would one write this function?
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.
Hi
I’m curious in what application would you select the least significant bit, versus how often would -x for unsigned be an error?
Can you point at a single case in the real world where this warning identified a bug that could not be otherwise identified?
Note that there, to my knowledge, it only exists in Visual Studio and it is highly optional (level 4 warnings).
Can we use x & 1 to select only the least significant bit, and zero out all others?
Consider the number 12. In binary, that is 0b1100. Suppose you want to set all bits, except the least significant set bit, to zero. The answer is 0b1100 or 4.