Photo by James Harrison on Unsplash

The Elusive Enum Trap

Theo Gregory
4 min readMar 8, 2021

--

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:

Made with carbon.now.sh

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:

Made with carbon.now.sh

Take the time to name well

  1. Identify clearly what the domain of your values is.
  2. 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:

Made with carbon.now.sh
  • + 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:

Made with carbon.now.sh

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.

--

--

Theo Gregory

dubious dev, fintech friend, sloppy skier | he/him