The Elusive Enum Trap
Enums are both powerful and ubiquitous in typed programming languages.
They enable you to construct easy-to-follow decision paths, based on their set of pre-defined values. Plus, if you have your linter/compiler configured correctly, you can catch non-exhaustive paths pre-runtime.
In programming 101, we learn the importance of good variable names. However, none suffer so greatly and routinely to this rule than enums — happily making it through code-reviews, and causing headaches down the line.
For this post, I’ll use TypeScript to help portray the idea, but the problem is language-independent.
Do not use “Type” in an enum’s name
An enum’s name should make it immediately clear what we would expect its values to be, and to not be.
Consider a booking system for private tutoring.
You wish to identify what type of lesson a booking is for (i.e. Piano, Singing, etc.) so that you can determine its price. With an urge to merge, not many would shoot-down the following in code-review:
The Urge to Merge — now that’s a book in the making.
“The disasters of rushing your engineers.”
So what’s the problem here? Excluding using a map (which would be my preference in real code, but keeping things simple for this post*)
Time passes, requirements change, and now we wish to support group lessons.
Having been away from the code for a while, you’ve read the spec change, and preemptively you’re thinking: “I’ll just add a ‘lesson type’ to the booking”.
Hint: my initial problem defintion highlights the issue. “what type of lesson” means nothing without me having to provide examples. I have to check the values to know the domain. Big no no.
Best case: you spot your pre-existing lessonType
and realise, as it's something already in production and written to the DB, you’ll just have to leave that there and create a new enum and field on the booking interface. Perhaps TutoringMethod
.
Worst case: your spritely intern picks up the ticket, and sinks time into adding Group
and Private
into the pre-existing enum, having not spotted that these should be distinct concepts.
This example is overly simplified, of course. In the real world, the lines between two different concepts might be much more subtle and harder to differentiate for somebody fresh to a project’s domain.
Had the initial engineer taken the time to consider other possible enum names, they might have settled on Instrument
which would have allowed the requirement change to be more easily implemented. Perhaps:
Take the time to name well
- Identify clearly what the domain of your values is.
- Think about what the domain is not.
It took me 5 frustrating minutes (and I mean that seriously) to settle on TutoringMethod
. I tossed up LessonStyle
and LessonTeachingApproach
, but Style and Approach are really just synonyms of Type.
I felt like I wasn’t really devving while weighing it up, but over time it will save the next engineers/ops those minutes to understand it.
And don’t be scared of long names. Personally, I would always opt for very long variable names for the sake of absolute clarity. AnimalWithShortBrownFurAndFourLegsThatCanWearHats.BEAR
Footnotes
* Enums and Maps to me are like peas in a pod. I believe most switch statements should be replaced with a private map and public accessor. For example:
- + nobody outside the file can mess with the map
- + the lookup is O(1) at runtime**
- + fewer lines of code
- + Record<V, N> is statically type-checked to ensure it holds an entry for every V
Taking this up a notch with TutoringMethod:
Replace the returned numbers with calculator functions, and you’ve got the beginnings of a lovely, injectable and easily unit-testable pricing calculator.
** Switch statements can also be O(1) in both compiled and interpreted languages, depending on your compiler/transpiler configuration.