Est-il acceptable d'aller à l'encontre des noms en majuscules pour les énumérations afin de simplifier leur représentation de chaîne?

14

Plusieurs fois, j'ai vu des gens utiliser des noms en majuscules ou même des minuscules pour les constantes enum, par exemple:

enum Color {
  red,
  yellow,
  green;
}

Cela rend le travail avec leur forme de chaîne simple et facile, si vous voulez le faire throw new IllegalStateException("Light should not be " + color + "."), par exemple.

Cela semble plus acceptable si l'énumération l'est private, mais je ne l'aime toujours pas. Je sais que je peux créer un constructeur enum avec un champ String, puis remplacer toString pour retourner ce nom, comme ceci:

enum Color {
  RED("red"),
  YELLOW("yellow"),
  GREEN("green");

  private final String name;

  private Color(String name) { 
    this.name = name 
  }

  @Override public String toString() {
    return name; 
  }
}

Mais regardez combien de temps cela dure. C'est ennuyeux de continuer à faire si vous avez un tas de petites énumérations que vous voulez garder simples. Est-il correct d'utiliser simplement des formats de cas non conventionnels ici?

décrypteur
la source
4
C'est une convention. Avez-vous des raisons de rompre la convention? C'est à toi de voir.
9
Je me demande simplement ce qui ne va pas avec un message d'exception qui dit Light should not be RED.. Après tout, ce message est destiné au développeur, l'utilisateur ne devrait jamais le voir.
Brandin
1
@Brandin si la couleur est plus un détail d'implémentation, alors personne ne devrait la voir. Bien sûr, alors je suppose que cela laisse la question de savoir pourquoi nous avons besoin d'un joli formulaire toString.
codebreaker
8
À la fin de la journée, c'est à vous de décider. Si je déboguais votre code et voyais un message d'exception, Light should not be YELLOW.je suppose que c'est une constante quelconque, la méthode toString () est juste à des fins de débogage, donc je ne vois pas pourquoi vous devez changer à quoi il ressemble dans ce message.
Brandin
1
Je pense que la réponse est plus basée sur votre idée de «d'accord». Le monde ne va probablement pas s'enflammer si vous faites cela et vous pouvez même réussir à écrire un programme. Est-ce utile? meh trop subjectif. Si vous obtenez un avantage, cela en vaut-il la peine pour vous et cela affectera-t-il les autres? Telles sont les questions auxquelles je me soucierais.
Garet Claborn

Réponses:

14

La réponse courte est, bien sûr, de savoir si vous voulez rompre avec les conventions de dénomination pour ce qui sont essentiellement des constantes ... Citant le JLS :

Noms constants

Les noms des constantes dans les types d'interface doivent être, et les variables finales des types de classe peuvent être conventionnellement une séquence d'un ou plusieurs mots, acronymes ou abréviations, tous en majuscules, avec des composants séparés par des caractères de soulignement "_". Les noms constants doivent être descriptifs et ne pas être abrégés inutilement. Classiquement, ils peuvent être n'importe quelle partie appropriée du discours.

La réponse longue, en ce qui concerne l'utilisation de toString(), est que c'est certainement la méthode à remplacer si vous voulez une représentation plus lisible des enumvaleurs. Citant de Object.toString()(soulignement le mien):

Renvoie une représentation sous forme de chaîne de l'objet. En général, la toStringméthode renvoie une chaîne qui "représente textuellement" cet objet . Le résultat devrait être une représentation concise mais informative, facile à lire pour une personne . Il est recommandé que toutes les sous-classes remplacent cette méthode.

Maintenant, je ne sais pas pourquoi certaines des réponses ont dérivé pour parler de la conversion en enumsva-et-vient avec des Stringvaleurs, mais je vais juste donner mon avis ici également. Une telle sérialisation des enumvaleurs peut facilement être prise en charge en utilisant les méthodes name()ou ordinal(). Les deux le sont finalet vous pouvez donc être sûr des valeurs renvoyées tant que les noms ou le positionnement des valeurs ne changent pas . Pour moi, c'est un marqueur assez clair.

Ce que je comprends de ce qui précède est le suivant: aujourd'hui, vous voudrez peut-être décrire YELLOWsimplement comme «jaune». Demain, vous voudrez peut-être le décrire comme "Pantone Minion Yellow" . Ces descriptions devraient être renvoyées après avoir appelé toString(), et je ne m'attendrais pas à ce que l'une name()ou l' autre ordinal()change. Si je le fais, c'est quelque chose que je dois résoudre au sein de ma base de code ou de mon équipe, et devient une question plus importante qu'un simple enumstyle de nommage .

En conclusion, si tout ce que vous avez l'intention de faire est d'enregistrer une représentation plus lisible de vos enumvaleurs, je vous suggérerai toujours de vous en tenir aux conventions, puis de passer outre toString(). Si vous avez également l'intention de les sérialiser dans un fichier de données ou vers d'autres destinations non Java, vous avez toujours les méthodes name()et ordinal()pour vous sauvegarder, il n'est donc pas nécessaire de s'inquiéter de la substitution toString().

hjk
la source
5

Ne modifiez pas ENUMc'est juste une mauvaise odeur de code. Soit une énumération une énumération et une chaîne une chaîne.

Utilisez plutôt un convertisseur CamelCase pour les valeurs de chaîne.

throw new IllegalStateException("Light should not be " + CamelCase(color) + ".");

Il existe de nombreuses bibliothèques open source qui résolvent déjà ce problème pour Java.

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/CaseFormat.html

Reactgular
la source
Ne serait-ce pas non déterministe dans certains cas marginaux? (pas le comportement d'un convertisseur spécifique, mais le principe général)
Panzercrisis
-1 L'ajout d'une bibliothèque tierce qui peut ne pas être celle que vous souhaitez pour le formatage de chaîne de base peut être excessif.
user949300
1
@ user949300 copiez le code, écrivez le vôtre ou quoi que ce soit. Vous manquez le point.
Reactgular
Pouvez-vous élaborer sur "le point"? "Que une énumération soit une énumération et une chaîne une chaîne" sonne bien, mais qu'est-ce que cela signifie vraiment?
user949300
1
Enum offre une sécurité de type. Il ne peut pas passer "pomme de terre" comme couleur. Et, peut-être, il inclut d'autres données dans l'énumération, telles que la valeur RVB, ne l'affiche tout simplement pas dans le code. Il existe de nombreuses bonnes raisons d'utiliser une énumération au lieu d'une chaîne.
user949300
3

En envoyant des énumérations entre mon code Java et une base de données ou une application cliente, je finis souvent par lire et écrire les valeurs d'énumération sous forme de chaînes. toString()est appelé implicitement lors de la concaténation de chaînes. Remplacer toString () sur certaines énumérations signifiait que parfois je pouvais juste

"<input type='checkbox' value='" + MY_CONST1 + "'>"

et parfois je devais me rappeler d'appeler

"<input type='checkbox' value='" + MY_CONST1.name() + "'>"

ce qui a conduit à des erreurs, donc je ne fais plus ça. En fait, je ne remplace aucune méthode sur Enum, car si vous les jetez dans suffisamment de code client, vous finirez par briser les attentes de quelqu'un.

Créez votre propre nouveau nom de méthode, comme public String text()ou toEnglish()ou quoi que ce soit.

Voici une petite fonction d'aide qui pourrait vous éviter de taper si vous avez beaucoup d'énumérations comme ci-dessus:

public static String ucFirstLowerRest(String s) {
    if ( (s == null) || (s.length() < 1) ) {
        return s;
    } else if (s.length() == 1) {
        return s.toUpperCase();
    } else {
        return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
    }
}

Il est toujours facile d'appeler .toUpperCase () ou .toLowerCase () mais le retour en casse mixte peut être délicat. Considérez la couleur, "bleu de France". La France est toujours en majuscule, donc vous voudrez peut-être ajouter une méthode textLower () à votre énumération si vous rencontrez cela. Lorsque vous utilisez ce texte au début d'une phrase, par rapport au milieu d'une phrase, par rapport à un titre, vous pouvez voir comment une seule toString()méthode va échouer. Et cela ne touche même pas les caractères illégaux dans les identificateurs Java, ou qui sont difficiles à taper car ils ne sont pas représentés sur les claviers standard, ou les caractères qui n'ont pas de casse (Kanji, etc.).

enum Color {
  BLEU_DE_FRANCE {
    @Override public String textTc() { return "Bleu De France"; }
    @Override public String textLc() { return "bleu de France"; }
  }
  CAFE_NOIR {
    @Override public String textTc() { return "Café Noir"; }
  }
  RED,
  YELLOW,
  GREEN;

  // The text in title case
  private final String textTc;

  private Color() { 
    textTc = ucFirstLowerRest(this.toString());
  }

  // Title case
  public String textTc() { return textTc; }

  // For the middle of a sentence
  public String textLc() { return textTc().toLowerCase(); }

  // For the start of a sentence
  public String textUcFirst() {
    String lc = textLc();
    return lc.substring(0, 1).toUpperCase() + lc.substring(1);
  }
}

Il n'est pas si difficile de les utiliser correctement:

IllegalStateException(color1.textUcFirst() + " clashes horribly with " +
                      color2.textLc() + "!")

J'espère que cela démontre également pourquoi l'utilisation de valeurs d'énumération à casse mixte vous décevra également. Une dernière raison de conserver les majuscules avec des constantes enum soulignées est que cela suit le principe du moindre étonnement. Les gens s'y attendent, donc si vous faites quelque chose de différent, vous devrez toujours vous expliquer ou traiter avec des gens qui abusent de votre code.

GlenPeterson
la source
3
La suggestion de ne jamais passer outre toString()à une énumération n'a aucun sens pour moi. Si vous voulez le comportement par défaut garanti des noms d'énumération, utilisez name(). Je ne trouve pas du tout "tentant" de compter sur toString()pour fournir la bonne clé valueOf(String). Je suppose que si vous voulez protéger vos énumérations, c'est une chose, mais je ne pense pas que ce soit une raison suffisante pour recommander que vous ne deviez jamais passer outre toString().
codebreaker
1
De plus, j'ai trouvé cela, qui recommande (comme on m'a laissé croire) que remplacer toString sur les énumérations est en fait une bonne chose: stackoverflow.com/questions/13291076/…
codebreaker
@codebreaker toString () est appelé implicitement lors de la concaténation de chaînes. Remplacer toString () sur certaines énumérations signifiait que parfois je pouvais juste <input type='checkbox' value=" + MY_CONST1 + ">, et parfois je devais me rappeler d'appeler <input type='checkbox' value=" + MY_CONST1.name() + ">, ce qui entraînait des erreurs.
GlenPeterson
D'accord, c'est un bon point, mais cela n'a vraiment d'importance que lorsque vous "sérialisez" des énumérations avec leur nom (ce qui n'est pas ce dont je dois m'inquiéter avec mon exemple).
codebreaker
0

S'il y a la moindre chance que ces représentations de chaînes puissent être stockées dans des endroits comme des bases de données ou des fichiers texte, alors les avoir liées aux constantes d'énumération réelles deviendra très problématique si vous avez besoin de refactoriser votre énumération.

Et si un jour vous décidez de renommer Color.Whiteà Color.TitaniumWhitetout , il y a des fichiers de données là - bas contenant « Blanc »?

De plus, une fois que vous aurez commencé à convertir des constantes enum en chaînes, l'étape suivante sera de générer des chaînes pour que l'utilisateur puisse les voir, et le problème ici est, comme vous le savez sûrement déjà, que la syntaxe de java ne fonctionne pas. autoriser les espaces dans les identifiants. (Vous n'en aurez jamais Color.Titanium White.) Donc, comme vous aurez probablement besoin d'un mécanisme approprié pour générer des noms d'énumération à afficher à l'utilisateur, il est préférable d'éviter de compliquer inutilement votre énumération.

Cela étant dit, vous pouvez bien sûr aller de l'avant et faire ce que vous pensiez faire, puis refaçonner votre énumération plus tard, pour passer des noms au constructeur, quand (et si) vous rencontrez des problèmes.

Vous pouvez raser quelques lignes de votre énumération avec constructeur en déclarant le namechamp public finalet en perdant le getter. (Quel est cet amour que les programmeurs Java ont envers les getters?)

Mike Nakis
la source
Une statique finale publique est compilée dans du code qui l'utilise comme constante réelle plutôt que comme référence à la classe dont elle provient. Cela peut provoquer un certain nombre d'autres erreurs amusantes lors de la recompilation partielle du code (ou du remplacement d'un bocal). Si vous proposez public final static int white = 1;ou public final static String white = "white";(à part la rupture de la convention à nouveau), cela perd la sécurité de type fournie par l'énumération.
Les champs @MichaelT Enum ne sont pas statiques. Une instance est créée pour chaque constante d'énumération et les membres de l'énumération sont des variables membres de cette instance. Un public finalchamp d'instance qui est initialisé à partir du constructeur se comporte exactement comme un champ non final, sauf que vous ne pouvez pas lui affecter au moment de la compilation . Vous pouvez le modifier via la réflexion et tout le code qui s'y réfère commencera immédiatement à voir la nouvelle valeur.
Mike Nakis
0

On peut dire que le fait de suivre la convention de dénomination UPPERCASE viole DRY . (Et YAGNI ). Par exemple, dans votre exemple de code # 2: "RED" et "red" sont répétés.

Alors, suivez-vous la convention officielle ou suivez-vous DRY? Ton appel.

Dans mon code, je suivrai DRY. Cependant, je mettrai un commentaire sur la classe Enum, en disant "pas tous en majuscules parce que (explication ici)"

user949300
la source