2010-03-10 5 views
31

Nous avons une API REST dans laquelle les clients peuvent fournir des paramètres représentant les valeurs définies sur le serveur dans les Enums Java.Meilleure pratique pour rechercher Java Enum

Donc, nous pouvons fournir une erreur descriptive, nous ajoutons cette méthode lookup à chaque Enum. On dirait que nous ne faisons que copier du code (mauvais). Y a-t-il une meilleure pratique?

public enum MyEnum { 
    A, B, C, D; 

    public static MyEnum lookup(String id) { 
     try { 
      return MyEnum.valueOf(id); 
     } catch (IllegalArgumentException e) { 
      throw new RuntimeException("Invalid value for my enum blah blah: " + id); 
     } 
    } 
} 

Mise à jour: Le message d'erreur par défaut fourni par valueOf(..) serait No enum const class a.b.c.MyEnum.BadValue. Je voudrais fournir une erreur plus descriptive de l'API.

+2

Eh bien, il n'y a rien de mal avec le IllegalArgumentException, qui est déjà une exception RuntimeException. Que voulez-vous améliorer ici? – Riduidel

+0

Ajoutez un message plus descriptif que celui fourni lors de l'utilisation de 'valueOf' (et' IllegalArgumentException') –

Répondre

26

Vous pouvez probablement implémenter la méthode statique générique lookup.

Comme si

public class LookupUtil { 
    public static <E extends Enum<E>> E lookup(Class<E> e, String id) { 
     try {   
     E result = Enum.valueOf(e, id); 
     } catch (IllegalArgumentException e) { 
     // log error or something here 

     throw new RuntimeException(
      "Invalid value for enum " + e.getSimpleName() + ": " + id); 
     } 

     return result; 
    } 
} 

Ensuite, vous pouvez

public enum MyEnum { 
    static public MyEnum lookup(String id) { 
     return LookupUtil.lookup(MyEnum.class, id); 
    } 
} 

ou appeler explicitement la méthode de recherche de classe utilitaire. Pourquoi faut-il écrire ce code de 5 lignes?

+0

Vous ne pouvez pas créer une énumération de base pour étendre les énumérations enfant, car "Toutes les énumérations étendent implicitement java.lang.Enum. ne supporte pas l'héritage multiple, une énumération ne peut pas étendre autre chose ", donc il n'y a nulle part où mettre ce code, sauf si vous voulez le mettre dans une sorte de classe utilitaire statique et l'appeler avec le paramètre de classe enum. –

+0

Quel avantage votre méthode utilitaire abandonne-t-elle en invoquant Enum.valueOf? – sleske

+0

Publication et pré-actions et traitement des erreurs. Si je comprends bien, c'est la gestion des erreurs qui est copiée et collée entre différents types d'énumération. –

3

public class EnumTest { 
public enum MyEnum { 
    A, B, C, D; 
} 

@Test 
public void test() throws Exception { 
    MyEnum.valueOf("A"); //gives you A 
    //this throws ILlegalargument without having to do any lookup 
    MyEnum.valueOf("RADD"); 
} 
} 
+0

L'idée/exigence est de relancer l'exception spéciale –

+0

C'est vrai. Voir mise à jour pour un peu plus de description sur l'erreur "par défaut" par rapport à une erreur plus descriptive. –

+0

Dans ce cas, la réponse de @ "Mykola Golubyev" aurait dû résoudre votre problème qui est une classe d'utilitaire de recherche statique et vous pouvez lancer n'importe quel message/exception que vous voulez dans le bloc catch? –

3

Si vous voulez que la recherche soit insensible à la casse, vous pouvez faire une boucle à travers les valeurs qui en fait un peu plus convivial:

public enum MyEnum { 
    A, B, C, D; 

     public static MyEnum lookup(String id) { 
     boolean found = false; 
     for(MyEnum enum: values()){ 
      if(enum.toString().equalsIgnoreCase(id)) found = true; 
     } 
     if(!found) throw new RuntimeException("Invalid value for my enum: " +id); 
     } 
} 
+0

Bonne suggestion, pas vraiment ce que je cherchais, mais une bonne idée. Notez dans votre 'lookup' que vous ne renvoyez aucune valeur de la méthode. –

+0

Bon point. Devrait être: if (enum.toString(). EqualsIgnoreCase (id)) return enum; Vous pourriez alors éliminer le booléen et juste jeter l'exception si vous avez passé la boucle. – Adam

11

On dirait que vous avez une mauvaise pratique ici, mais pas là où vous pensez.

Attraper un IllegalArgumentException pour relancer un autre RuntimeException avec un message plus clair pourrait sembler une bonne idée mais ce n'est pas le cas. Parce que cela signifie que vous vous souciez des messages dans vos exceptions.

Si vous vous souciez des messages dans vos exceptions, cela signifie que votre utilisateur voit d'une manière ou d'une autre vos exceptions. C'est mauvais. Si vous souhaitez fournir un message d'erreur explicite à votre utilisateur, vous devez vérifier la validité de la valeur enum lors de l'analyse de l'entrée utilisateur et envoyer le message d'erreur approprié dans la réponse si l'entrée de l'utilisateur est incorrecte.

Quelque chose comme:

// This code uses pure fantasy, you are warned! 
class MyApi 
{ 
    // Return the 24-hour from a 12-hour and AM/PM 

    void getHour24(Request request, Response response) 
    { 
     // validate user input 
     int nTime12 = 1; 
     try 
     { 
      nTime12 = Integer.parseInt(request.getParam("hour12")); 
      if(nTime12 <= 0 || nTime12 > 12) 
      { 
       throw new NumberFormatException(); 
      } 
     } 
     catch(NumberFormatException e) 
     { 
      response.setCode(400); // Bad request 
      response.setContent("time12 must be an integer between 1 and 12"); 
      return; 
     } 

     AMPM pm = null; 
     try 
     { 
      pm = AMPM.lookup(request.getParam("pm")); 
     } 
     catch(IllegalArgumentException e) 
     { 
      response.setCode(400); // Bad request 
      response.setContent("pm must be one of " + AMPM.values()); 
      return; 
     } 

     response.setCode(200); 
     switch(pm) 
     { 
      case AM: 
       response.setContent(nTime12); 
       break; 
      case PM: 
       response.setContent(nTime12 + 12); 
       break; 
     } 
     return; 
    } 
} 
+0

Je pense en substance que nous faisons ce que vous suggérez. Nous lançons l'exception, puis une classe supérieure sur la pile l'attrape, puis renvoie juste le message d'exception au client via l'API REST (dans une réponse d'erreur définie) –

+5

Ce que j'ai suggéré est que vous ne devez pas intercepter les exceptions génériques message, mais renvoie le message destiné à expliquer pourquoi l'exception a été interceptée. Avoir une réponse d'erreur générique basée sur vos exceptions est une fuite de l'architecture interne au client et est une mauvaise pratique. Le message d'erreur de votre API ne doit pas dépendre de l'architecture de rapport d'erreurs de l'implémentation (dans ce cas, les exceptions Java). –

2

Le message d'erreur dans IllegalArgumentException est déjà assez descriptif.

Votre méthode crée une exception générique à partir d'une exception spécifique avec le même message simplement reformulé. Un développeur préfère le type d'exception spécifique et peut gérer le cas de manière appropriée au lieu d'essayer de gérer RuntimeException.

Si l'intention est de rendre le message plus convivial, alors les références aux valeurs d'énumération ne leur sont d'aucune façon utiles. Laissez le code de l'interface utilisateur déterminer ce qui doit être affiché à l'utilisateur, et le développeur de l'interface utilisateur serait mieux avec l'exception IllegalArgumentException.

+0

Le message d'erreur "par défaut" serait: "No enum const class a.b.c.MyEnum.BadValue". Je préférerais retourner un message d'erreur plus pertinent de l'API REST. Le développeur qui crée la partie frontale ne sait pas vraiment quoi faire de l'erreur par défaut si ce n'est de simplement l'afficher - ce que je voudrais éviter. –

+2

Je ne vois pas comment votre exemple de message est meilleur. Aucun d'entre eux n'est approprié pour un utilisateur, l'exception doit donc être gérée et un message approprié doit être affiché. Ceci est différent de placer le message dans l'exception, ce qui devrait être pour le développeur. – Robin

3

mise à jour: Comme GreenTurtle fait remarquer correctement, ce qui suit est erroné


Je voudrais juste écrire

boolean result = Arrays.asList(FooEnum.values()).contains("Foo"); 

Ceci est peut-être moins performant que d'attraper une exception d'exécution, mais fait pour le code beaucoup plus propre . Attraper de telles exceptions est toujours une mauvaise idée, car il est sujet à un mauvais diagnostic. Que se passe-t-il lorsque la récupération de la valeur comparée provoque elle-même une exception IllegalArgumentException? Ceci serait alors traité comme une valeur non correspondante pour l'énumérateur.

+6

Ce code ne retournera jamais true sauf si vous implémentez dans votre classe FooEnum une méthode equals capable de gérer les objets String. Cela se produit car la ArrayList générée contient des objets de type FooEnum et retournera toujours false si contains est demandé avec un objet String. – GreenTurtle

+0

@ GreenTurtle Oui, vous avez évidemment raison, j'ai oublié que le nombre Enum # ne fonctionne qu'avec la comparaison d'objets. Pour enums, vous ne devriez même pas être en mesure d'implémenter vos propres égaux. J'essayerais quand même d'éviter d'attraper des exceptions d'exécution génériques et de simplement les parcourir, en faisant une comparaison de chaînes. – makrom

0

Nous faisons tous nos énumérations comme cela quand il s'agit de Rest/Json etc Il a l'avantage que l'erreur est lisible par l'homme et vous donne également la liste des valeurs acceptées. Nous utilisons une méthode personnalisée MyEnum.fromString au lieu de MyEnum.valueOf, espérons que cela aide.

public enum MyEnum { 

    A, B, C, D; 

    private static final Map<String, MyEnum> NAME_MAP = Stream.of(values()) 
      .collect(Collectors.toMap(MyEnum::toString, Function.identity())); 

    public static MyEnum fromString(final String name) { 
     MyEnum myEnum = NAME_MAP.get(name); 
     if (null == myEnum) { 
      throw new IllegalArgumentException(String.format("'%s' has no corresponding value. Accepted values: %s", name, Arrays.asList(values()))); 
     } 
     return myEnum; 
    } 
} 

donc par exemple si vous appelez

MyEnum value = MyEnum.fromString("X"); 

vous obtiendrez un IllegalArgumentException avec le message suivant:

'X' n'a pas de valeur correspondante. Valeurs acceptées: [A, B, C, D]

Vous pouvez modifier l'exception IllegalArgumentException en valeur personnalisée.

Questions connexes