Monday, March 10, 2014

Enums - what can go wrong?

In last two posts I showed you what enums in java can give us and what we can do with them. I hope that right now you will agree with me that enums are really powerful.

Today I want to present you a few things which you can treat as a warning sign whenever you will see something like this in your own code.
But you need to remember that it's just a sign, nothing more. It doesn't mean that you shouldn't solve your problems in presented ways, it would be enough to take a deep breath, look at code once again and then made decision if it should be changed or not.

On the end of my last post I asked a couple of questions about examples given by me. If there was something worrying in them? Were they good or not?
Today I will try to explain why I asked those questions and I will share with you my thoughts about problems that can occur when we are overusing enumerations.

Let's start :)

complete collection

Look at the code of first enum which was presented in my last post:
public enum TDDStep {
    RED, GREEN, REFACTOR;
}

This is example of really good enum. Why? Because as definition says: enumeration is a collection of items that is complete and as you remember, there are only three steps in TDD, not more, not less, which means that presented enum is a complete list of those steps.

Ok, that was easy one :) Let's move on.

it's starts growing

Next stop:
package com.smalaca.enumeration.designpatterns;

public enum Category implements HasDescription {

    CREATIONAL("Deals with object creation mechanisms, trying to create objects in a manner suitable to the situation."),
    STRUCTURAL("Eases the design by identifying a simple way to realize relationships between entities."),
    BEHAVIORAL("Identifies common communication patterns between objects and realize these patterns.");
 
    private final String description;

    private Category(final String description) {
        this.description = description;
    }

    public String describe() {
        return description;
    }

    public String getExample() {
  
        String example = "";
  
        switch(this) 
        {
            case CREATIONAL:
                example = "Builder";
                break;
  
            case STRUCTURAL:
                example = "Adapter";
                break;
  
            case BEHAVIORAL:
                example = "Strategy";
                break;  
        }
  
        return example;
    }
}

And it's still not bad :)

Is this collection complete? It depends, but we can assume with huge probability that yes. Of course you can find other categories (like on Wikipedia where Concurrency patterns are mentioned), however those three are the most popular and in many places you don't even find a sentence about something more then them.

And the warning sign in this enum is not about items in collection, but about size. Well, I always thought that if enums starts to grow it means that we need to keep an eye on it.
Of course at the current moment there is no problem, not a single issue, but if the new lines of code will appear, we should consider small refactoring :)

But why I'm writing such a things? What is a problem with enum with a few methods?
Well, till know I posted one definition of enumeration and this is rather mathematical. When we are talking about programming it would be good to mention also another definition:
Enumerated type - is a data type consisting of a set of named values [...]
As you can read the method aren't mentioned here. Enum is a set of named values, which means that definition of such a data type should be as simple as possible.

Another problem (which is a consequence of not following definition) is messing around with Single responsibility principle, but I will wrote more about this in next paragraph.

here comes the best

And this is my favourite one:
package com.smalaca.enumeration.designpatterns;

public enum Creational implements HasDescription {

    ABSTRACT_FACTORY {
        @Override
        public String describe() {
            return "Provide an interface for creating families of related or dependent objects without specifying their concrete classes.";
        }
    },
 
    BUILDER {
        @Override
        public String describe() {
            return "Separate the construction of a complex object from its representation allowing the same construction process to create various representations.";
        }
    },
 
    FACTORY_METHOD {
        @Override
        public String describe() {
            return "Define an interface for creating a single object, but let subclasses decide which class to instantiate.";
        }
    };
 
    abstract public String describe();
}

Example shows us structure which never should be called enum. Why? Because it's against definition. Collection of design patterns won't be complete ever, there is always something what waits for it's own name and description. And this lead us to simple conclusion - it should not be an enum, but regular class + another class which would be responsible for creating valid patterns.

Ok, I can close the topic right now, but... I won't. Let's imagine that enum is ok, somehow we know that collection is complete and won't change. Can you do that?
And what we have? Similar situation like with Category enum and as I wrote above there is a little problem with SRP in such cases and now it's time to write something more about this.

single responsibility? are you sure?

In enums presented above this is only a warning sign, but I can easily imagine that you saw in your career many bigger enums than those two. With more methods, more logic.
So let me clarify what the problems are.

Look at TDDStep enum. What is responsibility of it? To provide collection of steps and nothing more than this.
Now take a look at Category and Creational enums. What are their responsibilities. First is to provide collection of items, but there is also second - defining structure and behaviour of those elements.
And it's even not so scary, but what if abstract method in example would be about behavior? Then number of responsibilities is even higher, because for each item in collection we would have different behaviour.

What impact on testability, future improvements or maintenance it will have? The more responsibilities the bigger impact.

summary

Yeah, enums are powerful, but as you can see with great power needs to come great responsibility. If not, the problems will come.