Quick Tip: Avoid Enums If Possible

One of the “anti-patterns” I come across from time to time is over-use of enumerations for control flow. This article describes why I consider it an anti-pattern, and how to counteract it.

Let’s suppose we want to represent an arithmetic operation on two numbers – such as plus, minus, multiply, etc. We could represent that operation as an enum (although to be clear – I don’t think you should):

enum ArithmeticOperation
{
  Add,
  Subtract,
  Multiply,
  Divide
}

We could then define a function to apply this operation to two numbers (highlighting will be explained later):

int ApplyOperation(ArithmeticOperation op, int x, int y)
{
  switch(op)
  {
    case ArithmeticOperation.Add: return x + y;
    case ArithmeticOperation.Subtract: return x - y;
    case ArithmeticOperation.Multiply: return x * y;
    case ArithmeticOperation.Divide: return x / y;
    default: throw new InvalidOperationException();
  }
}

That’s… sort of fine – but what happens when we want to extend it?

Adding Operators: No Compile-Time Checking

Let’s consider what happens if we want to add another operator – say exponentiation. If we add the enum value but forget to implement the switch case value, everything compiles fine. It’s only once we come to run the code, and actually hit that code path, that we discover the mistake, giving a run-time exception.

Adding Methods: Bad Code Organisation

Next, imagine what happens if we need to add more methods that switch on this enum. We might need a method returning the operator symbol, and another returning the operator precedence.

string GetOperatorSymbol(ArithmeticOperation op)
{
  switch(op)
  {
    case ArithmeticOperation.Add: return "+";
    case ArithmeticOperation.Subtract: return "-";
    case ArithmeticOperation.Multiply: return "*";
    case ArithmeticOperation.Divide: return "/";
    default: throw new InvalidOperationException();
  }
}

int GetOperatorPrecedence(ArithmeticOperation op)
{
  switch(op)
  {
    case ArithmeticOperation.Add: return 10;
    case ArithmeticOperation.Subtract: return 10;
    case ArithmeticOperation.Multiply: return 20;
    case ArithmeticOperation.Divide: return 20;
    default: throw new InvalidOperationException();
  }
}

I’ve highighted the relevant code for the “add” operator. Notice how it’s spread all over the place. Switch statements might group by operation, but they split by concept – and that’s the exact opposite of what’s usually intended with OO coding.

A Better Way

I’d suggest a better way of approaching this problem is to use an abstract class.

abstract class ArithmeticOperation
{
  abstract int ApplyOperation(int x, int y);
  abstract string GetSymbol();
  abstract int GetPrecendence();
}

class AddArithmeticOperation : ArithmeticOperation
{
  override int ApplyOperation(int x, int y)
  {
    return x + y;
  }

  override string GetSymbol()
  {
    return "+";
  }

  override int GetPrecedence()
  {
    return 10;
  }
}

// ...and so forth for the other operators.

That way, all the code for each operator sits in the same place, and adding any new methods for an operator requires all operators to be implemented before the code will even compile.

When to Use Enums

So if enums should be avoided, why do most languages include them? Personally I’d be happy without them,  but I can think of one possible reason you might want to use an enum. If you’re defining a public interface designed for consumption by other coders, you could argue that simplifying the interface at the expense of the code inside is a valid trade-off.

3 Responses to “Quick Tip: Avoid Enums If Possible”

  1. By doing that you have lost the ability for clients to easily consume your API. Since they can’t add new methods to your class directly, they still have have to use switch-like constructs. Only they can’t use switch itself, they have to instead chain a series of if-else-if blocks.

    Consider, for example, the DayOfWeek enumeration. There is no way you are goign to predict what uses a developer may have for that enumeration.

    Long term, our static analysis tools are getting better. You could write an FxCop to check for missing switch cases, something that would be impossible with if-else-if blocks.

    That said, enumerations aren’t always the bees knees. For the example you gave, using classes does make sense as there isn’t a finite number of operators.

  2. > Notice how it’s spread all over the place. Switch statements might group by operation, but they split by concept – and that’s the exact opposite of what’s usually intended with OO coding.

    C# isn’t really an OO language, it lost that claim the day they added events. For real OO, look to Java and cringe.

  3. This is all dependent on what you want to be easy and what you want to be hard (with easy and hard referring to the amount of work needed to extend the system at a later date).

    Using the enumeration (a data structure), writing new functions based on it require no extra effort to refactor existing functions while adding a new operation requires an effort to update every function that relies on it to include the new case. Therefore: with data structures, new methods = easy, new components = hard.

    Using an abstract class (an object), all classes that derive from it must implement the abstract methods defined in the class. You can add components at will without modifying sub-classes. However, to add a new abstract method would require refactoring every existing subclass to implement the new abstract method. Therefore: with objects, new methods = hard, new components = easy.

Leave a Reply