Real-World Refactoring: Switch Twitch Itch
After my previous post about turning a switch statement to a class hierarchy, I feel today would be a great day to discuss turning enums into a classes.
While enums definitely serve their purpose in the land of coding (whether it be C# or Java), they can be used improperly in certain places.
In a previous post about refactoring a switch statement to a class hierarchy, we used a strategy pattern to transform a switch statement into something a little more usable.
Today, we focus on another code smell: switch statements using enums. We'll go over a different approach of converting a large number of enums in a "switch statement from hell."
I've seen developers write code where I've seen enumerated types that go on for two pages (meaning one full screen of code and then clicking PgDn to see page 2) that consisted of either an if..then or a switch statement. This is definitely a code smell and needs refactored.
My big reason why I don't care for enums: You can't extend them. What if you wanted to attach a dollar amount or additional information? You can't.
Enums are fixed data types. A number and a word. They don't and shouldn't change. If we are adding another item to an enum and then adding an additional switch statement for that new enum value, we have failed in our design.
When you see code like that and it is setting a variable or state of an object, it makes more sense to take those enums and refactor the code into a more suitable (and open) design.
NOTE: If you are wondering where I found out about this technique, it's in the book called Refactoring To Patterns and I constantly reference it on my site. It's one of my top 10 books every .NET developer should own. This particular technique is the Replace Type Code with Class on page 286.
The Basics
Enum types represent a fixed set of constants. If you know that these constants will never, ever, EVER change, by all means, use them. For example, if we need a list of planets, we can create them like so:
enum Planets { Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune }
We know that they aren't going to change anytime soon (not that I'm aware of and they won't change in my lifetime).
But in programming, do we ever have a fixed number of constants in our code? NO! Because we have business rules that constantly change...
...All. The. Time.
I continue to see enums in code. Not only do I see enums, but I see them used with switch statements. It's getting to the point where I can't "unsee" an enum anymore.
Does this look familiar?
public enum Planets { Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune } public class PlanetDecision { private long _distanceFromSun; public void SelectPlanets(Planets selected) { switch (selected) { case Planets.Mercury: _distanceFromSun = 35980000; break; case Planets.Venus: _distanceFromSun = 67240000; break; case Planets.Earth: _distanceFromSun = 92960000; break; case Planets.Mars: _distanceFromSun = 141600000; break; case Planets.Jupiter: _distanceFromSun = 438800000; break; case Planets.Saturn: _distanceFromSun = 890700000; break; case Planets.Uranus: _distanceFromSun = 1787000000; break; case Planets.Neptune: _distanceFromSun = 2795000000; break; } } }
I see this.
Then I start to develop a twitch.
How can we refactor this?
By converting this type code (or enum) into a class. I promise it won't hurt...much.
Starting Out With Some Class
While this may seem completely fundamental, we need a planet class with an interface.
public interface IPlanet { int Id { get; } string Title { get; } long DistanceFromSun { get; } } public class Planet : IPlanet { public Planet(int id, string title, long distanceFromSun) { Id = id; Title = title; DistanceFromSun = distanceFromSun; } public int Id { get; private set; } public string Title { get; private set; } public long DistanceFromSun { get; private set; } }
If you're thinking, "Why create all of these classes when an enum is so much simpler?" then you aren't thinking openly enough about your design.
With the SOLID design principles, the 'O' stands for Open/Closed Principle. This states that software entities you write should be open for extension, but closed for modification.
In the case of an enum, there isn't a way to extend it.
What if you need another statistic or property for the planets? Do you create another switch or if..then statement for your new attribute...say color?
You can, however, extend a class. We even have an interface to add another attribute to our planets if we so desire.
Let's create a class to keep track of our planets.
public class Planets : List<IPlanet> { public Planets() { Clear(); Add(new Planet(1, "Mercury", 35980000)); Add(new Planet(2, "Venus", 67240000)); Add(new Planet(3, "Earth", 92960000)); Add(new Planet(4, "Mars", 141600000)); Add(new Planet(5, "Jupiter", 438800000)); Add(new Planet(6, "Saturn", 890700000)); Add(new Planet(7, "Uranus", 1787000000)); Add(new Planet(8, "Neptune", 2795000000)); } public IPlanet Mercury { get { return this.Find(e => e.Title == "Mercury"); } } public IPlanet Venus { get { return this.Find(e => e.Title == "Venus"); } } public IPlanet Earth { get { return this.Find(e => e.Title == "Earth"); } } public IPlanet Mars { get { return this.Find(e => e.Title == "Mars"); } } public IPlanet Jupiter { get { return this.Find(e => e.Title == "Jupiter"); } } public IPlanet Saturn { get { return this.Find(e => e.Title == "Saturn"); } } public IPlanet Uranus { get { return this.Find(e => e.Title == "Uranus"); } } public IPlanet Neptune { get { return this.Find(e => e.Title == "Neptune"); } } }
Now that we have our list of planets, we can revisit our PlanetDecision class and can eliminate the switch statement altogether.
public class PlanetDecision { IPlanet SelectedPlanet { get; set; } public void SetSelectedPlanet(IPlanet planet) { SelectedPlanet = planet; } }
When a user picks from a list of planets and wants to find out more about that planet, we set the SelectedPlanet property using a Bridge Design Pattern.
Once we have our SelectedPlanet, we can display all sorts of information based upon their selection.
Conclusion
When I see enums used all over the place, it screams code smell. The local variable they are checking against could easily be converted over to a class field. Instead, they return the number after the switch statement or if..then statement is executed.
Like I said, enums do have their place, but when it comes to state, it may be easier to create a class making it more flexible and easier to use.
Am I wrong in thinking this way? Do you use enums with switch statements or if..then statements?
Post your comments below.