Skip to content

How Enums Spread Disease — And How To Cure It

Poorly handled enums can infect code with fragility and tight coupling like a digital Typhoid Mary.

Say you're writing software that optimizes traffic flow patterns, and you need to model different vehicle types. So you code up something like this:

vehicle_type.h

enum VehicleType {
    eVTCar,
    eVTMotorcycle,
    eVTTruck,
    eVTSemi,
};

Then you press your enum into service:

route.cpp

if (vehicle.vt == eVTSemi || vehicle.vt == eVTTruck) {
    // These vehicle types sometimes have unusual weight, so we 
    // have to test whether they can use old bridges...
    if (vehicle.getWeight() > bridge.getMaxWeight()) {

Quickly your enum becomes handy in lots of other places as well:

if (vehicle.vt == eVTMotorcycle) {
    // vehicle is particularly sensitive to slippery roads

And...

switch (vehicle.vt) {
case eVTTruck:
case eVTSemi:
    // can't use high-occupancy/fuel-efficient lane
case eVTMotorcycle:
    // can always use high-occupancy/fuel-efficient lane
default:
    // can only use lane during off-peak hours
}

Diagnosis

The infection from your enum is already coursing through the bloodstream at this point. Do you recognize the warning signs?

  • Knowledge about the semantics of each member of the enum are spread throughout the code.
  • The members of the enum are incomplete. How will we account for cranes and bulldozers and tractors and vans?
  • Semantics are unsatisfying. We're saying cars are never gas guzzlers or gas savers; what about massive old steel-framed jalopies and tiny new hybrids?
A vehicle that challenges our tidy enum.

The infection amplifies when we want to represent the enum in a config file or a UI. Now we need to convert to and from strings, and we use the classic shadow array of string literals, indexed by enum:

// THIS ARRAY ***MUST*** BE KEPT IN SYNC WITH THE ENUM DECLARED
// AT THE TOP OF vehicle_type.h!!!
char const * VEHICLE_TYPE_NAMES[] = { 
    "car", 
    "motorcycle", 
    "truck", 
    "semi"
};

char const * getVehicleTypeName(VehicleType vt) {
    return VEHICLE_TYPE_NAMES[vt];
}

Lest you think this ugliness is unique to C/C++, the Java or C# equivalent isn't all that pretty, either:

@override
String toString() {
    // eVTxyz  — > xyz
    return super.toString().toLowerCase().substring(3);
}

static VehicleType fromString(String vt) {
    if (vt.equals("truck")) return eVTTruck;
    if (vt.equals("semi")) return eVTSemi;
    ...
}

You might be rolling your eyes at the clumsy conversions. Yes, we could do error checking to make getVehicleTypeName() safer. Yes, we could use reflection in some languages to automate these conversions.

That misses the point.

We're still propagating knowledge indiscriminately. If the UI is involved, chances are there's a view, or an html