views:

258

answers:

5

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?

+1  A: 

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.

Darron
+2  A: 

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.

Steve B.
A: 

The best would be if you could just fix the published versions, if possible. In my opinion consistency would be the best solution, so you would need to do some refactoring. I personally don't like deprecated things, because they get into way. You might be able to wait until a bigger version release and use those ints until then, and refactor everything in a big project. If that is not possible, you might consider yourself stuck with the ints, unless you create some kinds of wrappers or something.

If nothing helps but you still evolve the code, you end up losing consistency or living with the deprecated versions. In any case, usually at least at some point of time people become fed up with old stuff if it has lost it's consistency and create new from scratch... So you would have the refactoring in the future no matter what.

The customer might scrap the project and buy an other product, if something goes wrong. Usually it is not the customer's problem can you afford refactoring or not, they just buy what is appropriate and usable to them. So in the end it is a tricky problem and care needs to be taken.

Silvercode
+1  A: 

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.

Joachim Sauer
+3  A: 

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.

McDowell