The "Unset" Enum Item
A good bit of defensive programming is to include an "Unset" item within your enum declarations.
Unintentional Default Values - UNSAFE
Let's say you have an invoicing program that applies discounts and penalties to amounts owed.
- If paid early, the customer gets a 2% discount
- If paid on-time, the customer pays full price
- If paid late, the customer pays a 10% penalty
You might represent these three possibilities with an enumerated type. You could then write a function that took the full price the customer owed and the payment period in which they paid and produced an appropriate amount due.
But what happens if we forget to initialize the PaymentPeriod value–or if the code execution resets? In VBA, enums are little more than glorified Long integers. That means that the default value of a typical enum is the first item in the list. In this case, that would be pp_Discount.
Here's what could happen:
Sub UnsafeCalc()
Dim PmtPeriod As pp__PaymentPeriod
Debug.Print CalcAmtOwed_UNSAFE(100, PmtPeriod)
End Sub
When we run this UnsafeCalc procedure, which uses the above unsafe code, the value printed to the immediate window is 98.
In other words, the CalcAmtOwed function applied a 2% discount even though we never explicitly intended to do so. This is an example of a logic bug. Logic bugs are the worst kind of all, because they can go undetected for so long.
I can easily imagine an application where every few months some obscure edge case results in the PaymentPeriod variable being reinitialized and a customer receiving an unwarranted 2% discount.
An Unset Default Item: The SAFER Approach
In the code below, I've introduced an additional item in the pp__PaymentPeriod enum: pp_Unset
. The sole purpose of this item is to act as the placeholder for an uninitialized variable of this type.
Now, when we calculate the amount owed, we can throw an error if the PaymentPeriod variable has not yet been initialized. It's still a runtime error–which we'd like to avoid–but runtime errors are better than logic errors.
If we run the SafeCalc
routine in an immediate window, we get the following runtime error:
This is good, because the only thing worse than a bug in your software is a bug in your software that you never discover.
Alternative approach
Another option would be to override the default start value of the enum by assigning a positive number to the first item. This achieves our objective of preventing an uninitialized pp__PaymentPeriod
variable being assigned a default value of pp_Discount
.
Enum pp__PaymentPeriod
pp_Discount = 1 'Override the default start value of 0
pp_Face 'VBA implicitly assigns pp_Face a value of 2
pp_Penalty 'VBA implicitly assigns pp_Penalty a value of 3
End Enum
I don't like this as much because it's less explicit than having a pp_Unset item to hold the value of 0. It's potentially misleading, too. As a code reader, I would assume that the number 1 had some special meaning with regards to pp_Discount. In fact, there's nothing special about the number 1 in this code other than it is not zero. I much prefer the explicit pp_Unset enum item approach.
Side note
If you're curious about my unorthodox enum naming convention, I wrote an article about it.
Referenced articles
Image by Capri23auto from Pixabay
UPDATE [2021-07-22]: Updated the first code sample to remove the = 1
from the top enum item (h/t Philipp Stiefel).