It is time to tackle one of the key subjects of defensive coding practices—the use of constants.
Why so many programmers ignore this important language feature, I am not sure, particularly in Java, C, and C++. Python makes it a little harder to use because the language lacks intrinsic support for constants, but with a simple-work around, Python can also create pseudo-constant values.
For argument’s sake, I will refer to constants as
const in a C++ context in this post. In java, look for the keyword
final. The concepts themselves will apply to virtually any language, however.
Don’t confuse #define with a constant
The most important thing to remember is that a compiler define is not the same as a constant. Compiler defines should only be used to control the compilation flow, to identify different configurations or platforms, or to enable and disable certain features during the build process. You should consider it purely a compile-time tool and should never use it for runtime evaluations in actual code!
Many programmers use a compiler define to declare runtime constants instead of using the proper constant language features. It is not only bad practice—it is an abomination!
#define MAX_THREADS 15 // Do not do this!
A line of code like this will ALWAYS fail my code review. It is a clear case where you should use a constant declaration instead. It is an immutable value the code uses during runtime for evaluation. The correct way for this declaration would look something like this…
const size_t MaxThreads = 15; // This is good
constexpr size_t MaxThreads = 15; // This is even better
The reason behind this rigorous separation is simple. It creates safer code. A compiler define is like a wildcard. It will replace anything that matches the macro with the respective definition. This can become the source of errors and unexpected behavior because other headers or libraries may have already established a similarly named define, creating a conflict that may or may not be visible to you. The best way to avoid these often hard-to-find and even-harder-to-debug issues is to minimize the use of such wildcard compiler defines.
The benefits of constants in the real world
const instead offers you numerous advantages. Foremost,
const has scope. It means you can limit its visibility to a file, a namespace, a class, a function, or a single code block. Collisions like the one described above can easily be avoided this way. In terms of defensive programming, that is a huge bonus that I cannot overstate.
Another upside of
const usage is that the compiler can better optimize your code. If the compiler sees that a value never changes, it can work with that knowledge. It will make accommodations in the register usage or code building that it would not do with a variable value. The result is often smaller, faster, and more robust code.
const is also type-checked, it can further strengthen your code. It helps prevent bugs in that it disallows the commingling of data types. Compiler defines do not offer that benefit because, at their core, these defines are straightforward string replacements without knowledge of any underlying datatypes.
Furthermore, as if you would need any more convincing, a
const value has a namespace and will therefore show up in your debugger. It is accessible through its name, the same way a variable is. Compiler defines, in contrast, are generally not available to the debugger.
Constant naming is important
Many coding standards enforce the use of all-capital names for compiler defines, which is fine if they are used properly and with appropriate scarcity. However, when names in all-caps litter your code, they quickly become a readability issue. They act like a roadblock in the flow.
If you consider a constant a non-changing variable, this naming convention also goes out the window. I typically recommend that the naming of constants should follow your coding guides’ variable naming standards instead. Your code readability will improve dramatically!
So, when in doubt, always use a
const before reaching for a compiler define and use
#define only where the definition needs to be available at compile-time!
Constants in code
As a defensive programmer, you should also develop the habit of making any variable you declare in your code a constant by default. If—and only if—its value needs to be modified should you remove the
const declaration from it and turn it into an assignable variable.
The reason for this is the same as described above. Doing this will allow the compiler to optimize and analyze your code better. In return, the compiler will provide you with full type-checking. It will also help you uncover situations where you may accidentally try to modify a constant value. Creating that kind of compiler-supported red flag will force you to stop and make sure the assignment is, in fact, deliberate and valid. At that point, you can easily remove the
const keyword. The key here is to err on the side of safety.
The constant argument
Constant declarations are also valuable in conjunction with function arguments. As a general rule, declare all incoming function arguments constant by default. Naturally, outbound arguments that act as return values cannot be constant, so declare them as variables. In my code, I also like to order my parameters with the incoming ones preceding the outgoing ones. Moreover, I tend to avoid outgoing arguments altogether, to begin with. There are often better ways of data transport than filling a provided datatype in a potentially unrelated function.
There are good reasons to make incoming argument constant. Consider this example.
void function( int inValue )
… do something with inValue
… assign a new inValue
… do something else with inValue
As you add more code to this function, it is easy to overlook that
inValue has been modified along the way. At some point, you may access the variable, expecting it to hold the original input value. You may have overlooked an innocuous instruction hidden in a wall of code that modified the value. As a result, you will spend time hunting for the problem—usually in the debugger. If only there were a way to prevent this from happening…
This scenario has latent bug potential written all over it! It is what we strive to avoid. As defensive programmers, we need a way to prevent this.
Let’s fix this problem
If all incoming values are ALWAYS constant, you never have to worry about it and can always rely on these incoming variables to hold their original value, which is an important safety guidepost.
If you do need to modify an incoming variable, because you want to use it as a counter, for example, simply assign it to a different variable. You are then free to make changes to that variable as you see fit, but you will always be able to find the original incoming value in the original, incoming variable. It is a free guardrail!
“How is it free?”, you may ask. “We just created an extra variable which may create stack- or register pressure.” Not really. Compilers are smart if you let them.
If you assign the incoming variable to another variable and never use the constant ever again, the compiler sees that and will simply reuse the register allocated for the constant to hold the variable. The overhead is zero!
If, instead, you use the original value again in the code, no harm is done. Either way, you would have had to store the original value somewhere, resulting in two variables.
It is a win-win situation. There is no penalty in any scenario, and, once again, it gives you all the benefits that come with the use of constants.
Constants in function declarations
Take equal care when crafting function declarations. If the function can be declared
const because it does not modify any class members, do so. Restrict potentially harmful or incorrect access to your objects, and you end up with safer code in the long run.
bool isValid( void ) const;
If you have functions that provide access to class members directly, as a reference or a pointer, make sure to also declare them as
const by default. It will prevent external code from potentially wreaking havoc on your object’s members.
If you need a legitimate access interface to modify members, you can always declare an additional non-const accessor function. I should point out, though, that you should avoid non-const accessors as much as possible. Instead, consider providing class functions that safely perform and properly assert the respective object member modifications. Remind yourself that one of the tenets of object-oriented programming is to keep functionality encapsulated within the class.
Thanks for stopping by. Preparing content such as the one you have just read takes time and effort to prepare. If you enjoyed it and are using a Brave browser, please feel free to leave a small tip as a sign of your support by clicking on the small BAT icon at the top of your browser window. Your tip is much appreciated and it encourages me to continue providing more content such as this.