Nous avons une API REST dans laquelle les clients peuvent fournir des paramètres représentant les valeurs définies sur le serveur dans Java Enums.
Pour que nous puissions 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);
}
}
}
Update : 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.
Vous pouvez probablement implémenter la méthode statique générique lookup
.
Ainsi
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 appelez explicitement la méthode de recherche de classe d'utilitaire.
On dirait que vous avez une mauvaise pratique ici mais pas où vous pensez.
Saisir une IllegalArgumentException
pour en redemander une autre RuntimeException
avec un message plus clair peut 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 alors que votre utilisateur voit 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 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;
}
}
Pourquoi devons-nous écrire ce code à 5 lignes?
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");
}
}
Si vous souhaitez que la recherche soit insensible à la casse, vous pouvez parcourir les valeurs pour la rendre un peu plus conviviale:
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);
}
}
update: Comme GreenTurtle l'a bien remarqué, les erreurs suivantes sont incorrectes
Je voudrais juste écrire
boolean result = Arrays.asList(FooEnum.values()).contains("Foo");
C'est peut-être moins performant que de capturer une exception d'exécution, mais cela rend le code beaucoup plus propre. Catching de telles exceptions est toujours une mauvaise idée, car elle est sujette à des diagnostics erronés ..__ Que se passe-t-il lorsque la récupération de la valeur comparée elle-même provoque une exception IllegalArgumentException? Ceci serait alors traité comme une valeur non correspondante pour l'énumérateur.
Nous faisons tous nos énumérations comme celle-ci quand il s’agit de Rest/Json, etc. ..__ L’avantage est 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 vous aidera.
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 une exception IllegalArgumentException avec le message suivant:
'X' n'a pas de valeur correspondante. Valeurs acceptées: [A, B, C, D]
vous pouvez remplacer l'exception IllegalArgumentException par une exception personnalisée.
Le message d'erreur dans IllegalArgumentException est déjà suffisamment 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 l'exception RuntimeException.
Si l'intention est de rendre le message plus convivial, les références aux valeurs d'énums ne leur sont de toute façon pas pertinentes. 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.
Guava fournit également une telle fonction qui retournera une Optional
si une énumération est introuvable
Enums.getIfPresent(MyEnum.class, id).toJavaUtil()
.orElseThrow(()-> new RuntimeException("Invalid enum blah blah blah.....")))