Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to handle coexistence of the "int enum" pattern with java enums as an API evolves?

Tags:

java

enums

api

Suppose you're maintaining an API that was originally released years ago (before java gained enum support) and it defines a class with enumeration values as ints:

public class VitaminType {
 public static final int RETINOL = 0;
 public static final int THIAMIN = 1;
 public static final int RIBOFLAVIN = 2;
}

Over the years the API has evolved and gained Java 5-specific features (generified interfaces, etc). Now you're about to add a new enumeration:

public enum NutrientType {
 AMINO_ACID, SATURATED_FAT, UNSATURATED_FAT, CARBOHYDRATE;
}

The 'old style' int-enum pattern has no type safety, no possibility of adding behaviour or data, etc, but it's published and in use. I'm concerned that mixing two styles of enumeration is inconsistent for users of the API.

I see three possible approaches:

  • Give up and define the new enum (NutrientType in my fictitious example) as a series of ints like the VitaminType class. You get consistency but you're not taking advantage of type safety and other modern features.

  • Decide to live with an inconsistency in a published API: keep VitaminType around as is, and add NutrientType as an enum. Methods that take a VitaminType are still declared as taking an int, methods that take a NutrientType are declared as taking such.

  • Deprecate the VitaminType class and introduce a new VitaminType2 enum. Define the new NutrientType as an enum.
    Congratulations, for the next 2-3 years until you can kill the deprecated type, you're going to deal with deprecated versions of every single method that took a VitaminType as an int and adding a new foo(VitaminType2 v) version of each. You also need to write tests for each deprecated foo(int v) method as well as its corresponding foo(VitaminType2 v) method, so you just multiplied your QA effort.

What is the best approach?

like image 680
jpdaigle Avatar asked Dec 04 '08 15:12

jpdaigle


4 Answers

How likely is it that the API consumers are going to confuse VitaminType with NutrientType? If it is unlikely, then maybe it is better to maintain API design consistency, especially if the user base is established and you want to minimize the delta of work/learning required by customers. If confusion is likely, then NutrientType should probably become an enum.

This needn't be a wholesale overnight change; for example, you could expose the old int values via the enum:

public enum Vitamin {

    RETINOL(0), THIAMIN(1), RIBOFLAVIN(2);

    private final int intValue;

    Vitamin(int n) {
        intValue = n;
    }

    public int getVitaminType() {
        return intValue;
    }

    public static Vitamin asVitamin(int intValue) {
        for (Vitamin vitamin : Vitamin.values()) {
            if (intValue == vitamin.getVitaminType()) {
                return vitamin;
            }
        }
        throw new IllegalArgumentException();
    }

}

/** Use foo.Vitamin instead */
@Deprecated
public class VitaminType {

    public static final int RETINOL = Vitamin.RETINOL.getVitaminType();
    public static final int THIAMIN = Vitamin.THIAMIN.getVitaminType();
    public static final int RIBOFLAVIN = Vitamin.RIBOFLAVIN.getVitaminType();

}

This allows you to update the API and gives you some control over when to deprecate the old type and scheduling the switch-over in any code that relies on the old type internally.

Some care is required to keep the literal values in sync with those that may have been in-lined with old consumer code.

like image 157
McDowell Avatar answered Nov 14 '22 23:11

McDowell


Personal opinion is that it's probably not worth the effort of trying to convert. For one thing, the "public static final int" idiom isn't going away any time soon, given that it's sprinkled liberally all over the JDK. For another, tracking down usages of the original ints is likely to be really unpleasant, given that your classes will compile away the reference so you're likely not to know you've broken anything until it's too late (by which I mean

class A
   {
       public static final int MY_CONSTANT=1
   }

   class B
   {
           ....
           i+=A.MY_CONSTANT; 
   }

gets compiled into

i+=1

So if you rewrite A you may not ever realize that B is broken until you recompile B later.

It's a pretty well known idiom, probably not so terrible to leave it in, certainly better than the alternative.

like image 20
Steve B. Avatar answered Nov 15 '22 00:11

Steve B.


There is a rumor that the creator of "make" realized that the syntax of Makefiles was bad, but felt that he couldn't change it because he already had 10 users.

Backwards compatibility at all costs, even if it hurts your customers, is a bad thing. SO can't really give you a definitive answer on what to do in your case, but be sure and consider the cost to your users over the long term.

Also think about ways you can refactor the core of your code will keeping the old integer based enums only at the outer layer.

like image 1
Darron Avatar answered Nov 15 '22 01:11

Darron


Wait for the next major revision, change everything to enum and provide a script (sed, perl, Java, Groovy, ...) to convert existing source code to use the new syntax.

Obviously this has two drawbacks:

  • No binary compatibility. How important this one is depends on the use cases, but can be acceptable in the case of a new major release
  • Users have to do some work. If the work is simple enough, then this too may be acceptable.

In the meantime, add new types as enums and keep old types as ints.

like image 1
Joachim Sauer Avatar answered Nov 14 '22 23:11

Joachim Sauer