2008-12-04 16 views
5

Supposons que vous maintenir une API qui a été initialement publié il y a quelques années (avant java gagné enum support) et définit une classe avec des valeurs d'énumération comme ints:Quelle est la meilleure façon de gérer la coexistence du modèle "int enum" avec java enums au fur et à mesure de l'évolution d'une API?

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

Au fil des années, l'API a évolué et gagné Java Les fonctionnalités spécifiques à 5 (interfaces générées, etc.). Maintenant, vous êtes sur le point d'ajouter une nouvelle énumération:

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

Le « style ancien » modèle int-ENUM n'a pas de sécurité de type, aucune possibilité d'ajouter des comportements ou des données, etc, mais il est publié et utilisé . Je suis préoccupé par le fait que le mélange de deux styles d'énumération est incohérent pour les utilisateurs de l'API.

Je vois trois approches possibles:

  • Donnez et définir la nouvelle ENUM (NutrientType dans mon exemple fictif) comme une série de ints comme la classe VitaminType. Vous obtenez la cohérence, mais vous ne profitez pas de la sécurité de type et d'autres fonctionnalités modernes.

  • décident de vivre avec une incohérence dans une API publiée: garder VitaminType autour comme il est, et ajouter NutrientType comme enum. Les méthodes qui prennent un VitaminType sont toujours déclarées comme prenant un int, les méthodes qui prennent un NutrientType sont déclarées comme prenant un tel. Déposez la classe VitaminType et présentez une nouvelle énumération VitaminType2. Définissez le nouveau NutrientType sous la forme d'une énumération. Félicitations, pour les 2-3 prochaines années, jusqu'à ce que vous puissiez tuer le type obsolète, vous allez devoir gérer les versions obsolètes de chaque méthode qui a pris un VitaminType en tant qu'int et en ajoutant une nouvelle version de foo(VitaminType2 v). Vous devez également écrire des tests pour chaque méthode obsolète foo(int v) ainsi que sa méthode foo(VitaminType2 v) correspondante, de sorte que vous venez de multiplier votre effort d'assurance qualité.

Quelle est la meilleure approche?

Répondre

3

L'opinion personnelle est que cela ne vaut probablement pas la peine d'essayer de convertir. D'une part, l'idiome «public static final int» ne disparaîtra pas de sitôt, étant donné qu'il est répandu généreusement dans tout le JDK.D'autre part, le suivi des utilisations des ints originaux est susceptible d'être vraiment désagréable, étant donné que vos classes compileront la référence de sorte que vous ne saurez probablement pas que vous avez cassé quoi que ce soit jusqu'à ce qu'il soit trop tard (par lequel je veux dire

class A 
    { 
     public static final int MY_CONSTANT=1 
    } 

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

se compile en

i+=1 

donc, si vous réécris vous ne jamais se rendre compte que B est cassé jusqu'à ce que vous recompiler B plus tard.

Il est un langage assez bien connu, probablement pas terrible de le laisser, certa inly mieux que l'alternative.

1

Il y a une rumeur que le créateur de "make" a réalisé que la syntaxe de Makefiles était mauvaise, mais a estimé qu'il ne pouvait pas le changer parce qu'il avait déjà 10 utilisateurs.

Rétrocompatibilité à tout prix, même si cela fait mal à vos clients, est une mauvaise chose. SO ne peut pas vraiment vous donner une réponse définitive sur ce qu'il faut faire dans votre cas, mais assurez-vous et considérez le coût pour vos utilisateurs à long terme.

Pensez aussi à la façon dont vous pouvez refactoriser le cœur de votre code en conservant les anciennes énumérations basées sur des entiers uniquement sur la couche externe.

0

Le mieux serait si vous pouviez simplement corriger les versions publiées, si possible. À mon avis, la cohérence serait la meilleure solution, vous devrez donc refactoring. Personnellement, je n'aime pas les choses obsolètes, parce qu'elles se mettent en route. Vous pourriez être en mesure d'attendre jusqu'à ce qu'une version plus grande sorte et d'utiliser ces scripts jusque-là, et de tout refactoriser dans un grand projet. Si ce n'est pas possible, vous pourriez vous considérer comme coincé avec les ints, à moins que vous ne créiez des sortes de wrappers ou quelque chose du genre.

Si rien ne vous aide mais que vous modifiez toujours le code, vous finissez par perdre la cohérence ou vivre avec les versions obsolètes. En tout cas, généralement au moins à un moment donné, les gens en ont marre des vieilles choses si elles ont perdu leur cohérence et créent de nouvelles à partir de zéro ... Donc vous auriez le refactoring dans le futur, peu importe quoi.

Le client peut abandonner le projet et acheter un autre produit en cas de problème. Habituellement, ce n'est pas le problème du client que vous pouvez vous permettre de refactoring ou non, ils achètent juste ce qui est approprié et utilisable pour eux. Donc, à la fin, c'est un problème délicat et il faut faire attention.

1

Attendez la prochaine révision majeure, changez tout en enum et fournissez un script (sed, perl, Java, Groovy, ...) pour convertir le code source existant afin d'utiliser la nouvelle syntaxe.

Évidemment, cela a deux inconvénients:

  • Pas de compatibilité binaire. L'importance de celui-ci dépend des cas d'utilisation, mais peut être acceptable dans le cas d'une nouvelle version majeure.
  • Les utilisateurs doivent effectuer un certain travail. Si le travail est assez simple, cela peut aussi être acceptable.

En attendant, ajoutez de nouveaux types comme enums et conservez les anciens types comme ints.

6

Quelle est la probabilité que les consommateurs d'API vont confondre VitaminType avec NutrientType? Si cela est peu probable, il est peut-être préférable de maintenir la cohérence de la conception de l'API, en particulier si la base d'utilisateurs est établie et que vous voulez minimiser le delta de travail/apprentissage requis par les clients. Si la confusion est probable, alors NutrientType devrait probablement devenir enum.

Cela ne doit pas être un changement de nuit en gros; par exemple, vous pouvez exposer les anciennes valeurs int via le 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(); 

} 

Cela vous permet de mettre à jour l'API et vous donne un certain contrôle sur quand désapprouver l'ancien type et la planification de la commutation dans tout code repose sur l'ancien type interne.

Il faut prendre soin de garder les valeurs littérales en phase avec celles qui ont pu être alignées avec l'ancien code consommateur.