¿Está bien ir en contra de los nombres en mayúsculas para las enumeraciones para simplificar su representación de cadena?

14

Varias veces he visto a personas usar el título de mayúsculas o incluso todos los nombres en minúsculas para constantes de enumeración, por ejemplo:

enum Color {
  red,
  yellow,
  green;
}

Esto hace que trabajar con su forma de cadena sea simple y fácil, si lo desea throw new IllegalStateException("Light should not be " + color + "."), por ejemplo.

Esto parece más aceptable si la enumeración es private, pero todavía no me gusta. Sé que puedo hacer un constructor enum con un campo String y luego anular toString para devolver ese nombre, así:

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; 
  }
}

Pero mira cuánto tiempo más es eso. Es molesto seguir haciéndolo si tienes un montón de enumeraciones pequeñas que quieres que sean simples. ¿Está bien usar formatos de caso no convencionales aquí?

descifrador de códigos
fuente
44
Es una convención. ¿Tienes razones para romper la convención? Eso depende de usted.
99
Solo me pregunto qué pasa con un mensaje de excepción que dice Light should not be RED.. Después de todo, este mensaje es para el desarrollador, el usuario nunca debería verlo.
Brandin
1
@Brandin si el color es más un detalle de implementación, entonces nadie debería verlo. Por supuesto, entonces supongo que eso deja la pregunta de por qué necesitamos un buen formulario toString.
Codebreaker
8
Al final del día, depende de usted. Si estaba depurando su código y vi un mensaje de excepción Light should not be YELLOW., supongo que es una constante de algún tipo, el método toString () es solo para fines de depuración, por lo que no veo por qué necesita cambiar la apariencia de ese mensaje.
Brandin
1
Creo que la respuesta se basa más en su idea de 'está bien'. Lo más probable es que el mundo no estalle en llamas si haces esto e incluso puedes escribir un programa con éxito. ¿Es útil? meh demasiado subjetivo Si obtiene algún beneficio, ¿vale la pena para usted y afectará a otros? Esas son las preguntas que me interesarían.
Garet Claborn

Respuestas:

14

La respuesta corta es, por supuesto, si desea romper con las convenciones de nomenclatura para lo que son esencialmente constantes ... Citando del JLS :

Nombres constantes

Los nombres de las constantes en los tipos de interfaz deben ser, y las variables finales de los tipos de clase pueden ser convencionalmente una secuencia de una o más palabras, siglas o abreviaturas, todas en mayúscula, con componentes separados por caracteres de subrayado "_". Los nombres constantes deben ser descriptivos y no abreviados innecesariamente. Convencionalmente, pueden ser cualquier parte apropiada del discurso.

La respuesta larga, con respecto al uso de toString(), es que definitivamente es el método para anular si desea una representación más legible de los enumvalores. Citando de Object.toString()(énfasis mío):

Devuelve una representación de cadena del objeto. En general, el toStringmétodo devuelve una cadena que "representa textualmente" este objeto . El resultado debe ser una representación concisa pero informativa que sea fácil de leer para una persona . Se recomienda que todas las subclases anulen este método.

Ahora, no estoy seguro de por qué algunas de las respuestas derivaron a hablar de convertir de aquí enumspara allá con Stringvalores, pero también daré mi opinión aquí. Dicha serialización de enumvalores se puede solucionar fácilmente utilizando los métodos name()o ordinal(). Ambos son finaly, por lo tanto, puede estar seguro de los valores devueltos siempre que los nombres o la posición de los valores no cambien . Para mí, ese es un marcador lo suficientemente claro.

Lo que deduzco de lo anterior es: hoy, es posible que desee describir YELLOWcomo simplemente "amarillo". Mañana, es posible que desee describirlo como "Pantone Minion Yellow" . Estas descripciones deben ser devueltos a llamar toString(), y yo no espero de name()o ordinal()al cambio. Si lo hago, eso es algo que necesito resolver dentro de mi base de código o mi equipo, y se convierte en una pregunta más importante que solo un enumestilo de nomenclatura .

En conclusión, si todo lo que pretende hacer es registrar una representación más legible de sus enumvalores, aún le sugiero que se adhiera a las convenciones y luego anule toString(). Si también tiene la intención de serializarlos en un archivo de datos o en otros destinos que no sean Java, todavía tiene los métodos name()y ordinal()para respaldarlo, por lo que no hay necesidad de preocuparse por anularlos toString().

hjk
fuente
5

No modifique ENUMeso es solo un mal olor del código. Deje que una enumeración sea una enumeración y una cadena sea una cadena.

En su lugar, use un convertidor CamelCase para valores de cadena.

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

Hay muchas bibliotecas de código abierto que ya resuelven este problema para Java.

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

Reactgular
fuente
¿No sería no determinista en ciertos casos extremos? (no el comportamiento de un convertidor específico, sino el principio general)
Panzercrisis
-1 Agregar una biblioteca de terceros que puede no ser lo que desea para el formato básico de String puede ser exagerado.
user949300
1
@ user949300 copie el código, escriba el suyo o lo que sea. Te estás perdiendo el punto.
Reactgular
¿Puedes dar más detalles sobre "el punto"? "Que una enumeración sea una enumeración y una cadena sea una cadena" suena bien, pero ¿qué significa realmente?
user949300
1
Enum proporciona seguridad de tipo. No puede pasar "papa" como color. Y, tal vez, incluye otros datos en la enumeración, como el valor thr RGB, simplemente no lo muestra en el código. Hay muchas buenas razones para usar una enumeración en lugar de una cadena.
user949300
3

Al enviar enumeraciones entre mi código Java y una base de datos o aplicación cliente, a menudo termino leyendo y escribiendo los valores de enumeración como cadenas. toString()se llama implícitamente al concatenar cadenas. Anular toString () en algunas enumeraciones significaba que a veces solo podía

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

y a veces tenía que recordar llamar

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

lo que condujo a errores, así que ya no hago eso. En realidad, no anulo ningún método en Enum porque si los aplica a un código de cliente suficiente, eventualmente romperá las expectativas de alguien.

Haga su propio nombre de método nuevo, como public String text()o toEnglish()o lo que sea.

Aquí hay una pequeña función auxiliar que podría ahorrarle algo de escritura si tiene muchas enumeraciones como la anterior:

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();
    }
}

Siempre es fácil llamar a .toUpperCase () o .toLowerCase (), pero recuperar mayúsculas y minúsculas puede ser complicado. Considere el color, "bleu de France". Francia siempre está en mayúscula, por lo que es posible que desee agregar un método textLower () a su enumeración si se encuentra con eso. Cuando usa este texto al comienzo de una oración, frente a la mitad de una oración, frente a un título, puede ver cómo un solo toString()método se quedará corto. Y eso ni siquiera toca caracteres que son ilegales en los identificadores de Java, o que son difíciles de escribir porque no están representados en teclados estándar, o caracteres que no tienen mayúsculas (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);
  }
}

No es tan difícil usarlos correctamente:

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

Esperemos que eso también demuestre por qué el uso de valores de enumeración de mayúsculas y minúsculas también lo decepcionará. Una última razón para mantener todo en mayúsculas con constantes enum de subrayado es que hacerlo sigue el Principio de Menos Asombro. La gente lo espera, así que si haces algo diferente, siempre tendrás que explicarte a ti mismo o tratar con personas que usen mal tu código.

GlenPeterson
fuente
3
La sugerencia de nunca anular toString()una enumeración no tiene sentido para mí. Si desea el comportamiento predeterminado garantizado de los nombres de enumeración, use name(). No me parece "tentador" en absoluto confiar toString()para proporcionar la clave correcta valueOf(String). Supongo que si quieres probar tus enumeraciones a prueba de idiotas, eso es una cosa, pero no creo que sea una razón suficiente para recomendarte que nunca debas anular toString().
Codebreaker
1
Además, encontré esto, que recomienda (como me hicieron creer) que anular toString en las enumeraciones es, de hecho, algo bueno: stackoverflow.com/questions/13291076/…
codebreaker
@codebreaker toString () se llama implícitamente al concatenar cadenas. Anular toString () en algunas enumeraciones significaba que a veces podía simplemente <input type='checkbox' value=" + MY_CONST1 + ">, y a veces tenía que acordarme de llamar <input type='checkbox' value=" + MY_CONST1.name() + ">, lo que provocaba errores.
GlenPeterson
Bien, ese es un buen punto, pero solo importa cuando estás "serializando" enumeraciones con su nombre (que no es de lo que necesito preocuparme con mi ejemplo).
Codebreaker
0

Si existe la más mínima posibilidad de que estas representaciones de cadenas se almacenen en lugares como bases de datos o archivos de texto, entonces vincularlas con las constantes de enumeración reales será muy problemático si alguna vez necesita refactorizar su enumeración.

¿Qué pasa si un día se decide cambiar el nombre Color.Whitea Color.TitaniumWhitebien hay datos que contienen archivos por ahí "blanca"?

Además, una vez que comience a convertir constantes de enumeración en cadenas, el siguiente paso más adelante será generar cadenas para que el usuario las vea, y el problema aquí es, como estoy seguro de que ya sabe, que la sintaxis de Java no permitir espacios dentro de los identificadores. (Nunca lo habrá hecho Color.Titanium White). Entonces, dado que probablemente necesitará un mecanismo adecuado para generar nombres de enumeración para mostrar al usuario, es mejor evitar complicar innecesariamente su enumeración.

Dicho esto, por supuesto, puede seguir adelante y hacer lo que ha estado pensando en hacer, y luego refactorizar su enumeración más tarde, para pasar nombres al constructor, cuando (y si) se encuentra con problemas.

Puede eliminar un par de líneas de su enum-with-constructor declarando el namecampo public finaly perdiendo el captador. (¿Qué es este amor que los programadores de Java tienen hacia los getters?)

Mike Nakis
fuente
Una estática final pública se compila en un código que la usa como la constante real en lugar de una referencia a la clase de la que proviene. Esto puede causar una serie de otros errores divertidos al realizar una compilación parcial de código (o reemplazar un jar). Si está sugiriendo public final static int white = 1;o public final static String white = "white";(aparte de romper la convención nuevamente), esto pierde el tipo de seguridad que proporciona la enumeración.
@MichaelT Los campos de enumeración no son estáticos. Se crea una instancia para cada constante enum, y los miembros de la enumeración son variables miembro de esa instancia. Un public finalcampo de instancia que se inicializa desde el constructor se comporta exactamente como un campo no final, excepto que no se le puede asignar en tiempo de compilación . Puede modificarlo mediante reflexión y todo el código que se refiere a él comenzará a ver el nuevo valor de inmediato.
Mike Nakis
0

Podría decirse que seguir la convención de nombres MAYÚSCULAS viola DRY . (Y YAGNI ). por ejemplo, en su ejemplo de código # 2: "ROJO" y "rojo" se repiten.

Entonces, ¿sigues la convención oficial o sigues a DRY? Tu llamada.

En mi código, seguiré DRY. Sin embargo, pondré un comentario sobre la clase Enum, diciendo "no todo en mayúsculas porque (explicación aquí)"

user949300
fuente