When to and When Not to Use Enums in Java

Are enums a code smell?

daleef rahman
Better Programming

--

Photo by Blake on Unsplash

Many people consider Enums as a code smell and an anti-pattern in OOPs. Certain books have also cited enums as a code smell, such as the following.

“WARNING As a rule of thumb, enums are code smells and should be refactored to polymorphic classes. ” Dependency Injection in .Net by Mark Seemann

In most cases, enums smell because it's frequently abused, but that doesn't mean that you have to avoid them. Enums can be a powerful tool in your arsenal if used properly. In this article, I would like to give a brief review of scenarios when enums should be used and when they shouldn't.

A quick refresher of enums

Enums are generally used to represent a group of constants. They are created using the enum keyword and the constants are separated with a comma.

Enums are often used in switch statements to check for corresponding values:

So Enums are just lists of strings, right?

Enums are often thought of as string constants: named values that can be assigned and recognized later. But this is not the complete story, and if you stop here, you have just skimmed the surface.

Enums are actually implemented as classes, and enum values are their instances.

Class representation of the Enum “Houses”

This means that enums can have properties and methods like any other class.

Example taken from https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

All enum classes inherit the Java standard class java.lang.Enum from which they inherit some potentially useful methods.

The inherited methods you should know about are name(), ordinal(), and static method values().

The name() method returns the name exactly as defined in the enum value. The ordinal() method returns a numeric value that reflects the order in which the enums were declared, starting with zero. For example,

The method values() is more often useful. It returns an array of all enum values and can be used to iterate over them. Here’s an example.

When and why to use enums:

Although enums are much more than just a list of strings, the most common use case of enums is to represent a list of constants. But why do we need enums for that? Why can’t we use a simple array of strings?

Let’s take an example. Suppose you are building a game with the following set of input commands.

There will be code in your program that reacts to these commands and then calls the right method to act on them. In the following code snippet, I assume that the String variable commandWord holds the word that was typed in:

What’s wrong with this solution? There are really two fundamental problems that immediately stand out: type safety and globalisation.

Type safety

The letter P was capital in “Pass” inside the switch case, and the compiler could not detect that.

Now let’s rewrite the same using ENUMS:

If you mistype a case label or a value in an assignment here, the compiler will detect this and notify you.

Globalisation

If you decide to translate the program into a different language (let’s say the move command is now mouvement), this will cause errors. If you just change the command word in the array, the program will compile, but the functionality will break.

When is enum a code smell?

Enums force you to use multiple switch case

One primary reason ENUMs are considered code smells is that they tempt you to sprinkle switch statements throughout your code. And what is the issue with that?

Consider this scenario

Example from Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin)

There are some obvious problems here. It violates multiple OOP best practices:

  • When new employee types are added, the function will grow and readability will go for a toss.
  • It violates the Single Responsibility Principle (SRP) because there is more than one reason for it to change.
  • It violates the Open-Closed Principle (OCP) because it must change whenever new types are added.
  • Similar to this function, there will be an unlimited number of other functions that will have the same structure. isPayday(Employee e, Date date) or deliverPay(Employee e, Money pay) or a host of others. Adding an extra value to an enum means finding every use of the type in your code and potentially needing to add a new branch for the new value.

Unfortunately, we can’t always avoid switch statements, but we can make sure that each switch statement is buried in a low-level class.

Use the “ONE SWITCH” rule state by Robert C. Martin in Clean code: There may be no more than one switch statement for a given type of selection. The cases in that switch statement must create polymorphic objects that take the place of other such switch statements in the rest of the system.

Example from Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin)

Here, adding a new type simply means creating a new class that implements the interface — the changes are concentrated in a single place, and easier to make.

Note: This technique cannot be applied if you already have a class hierarchy. You can’t create a dual hierarchy via inheritance in object-oriented programming. But you can use another technique which is mentioned here: https://refactoring.guru/replace-type-code-with-state-strategy

Enums leads to suboptimal modeling

Similar to the previous issue, this is also not a standalone problem with enums. Unfortunately, enums can lead to poor data model design decisions sometimes.

Consider the previous example.

Suppose we want to store information about salary. “SALESMAN” has a commission-based salary, so we want to store the property commisionPercentage. The simplest way is to add a new property to the class.

However, this property is not relevant to “Engineer” as they don’t have a commission-based salary. So now you have an irrelevant property for “Engineer”, which is not a very clean way to represent the data.

A better way to design this is by using subclasses.

This might be obvious, but as with most obvious things — it’s not, so I thought it would be worth mentioning.

That’s it! Basically, if you think you know all possible values of “something” at compile time and are representable by a simple value, you can represent that as an enum type. But when you use those enums in multiple switch cases, that should put you on notice.

Again, some of these points might feel very obvious to you, or maybe they happen to be helpful.

--

--