Mejores prácticas para usar y conservar enumeraciones

77

He visto varias preguntas / discusiones aquí sobre la mejor manera de manejar y persistir valores similares a enumeraciones (por ejemplo, datos persistentes adecuados para enumeraciones , Cómo conservar una enumeración usando NHibernate ), y me gustaría preguntar cuál es el consenso general .

En particular:

  • ¿Cómo deben manejarse estos valores en el código?
  • ¿Cómo deben conservarse en una base de datos (como texto / como un número)?
  • ¿Cuáles son las ventajas y desventajas de las diferentes soluciones?

Nota: moví las explicaciones incluidas originalmente en esta pregunta a una respuesta.

sleske
fuente

Respuestas:

19

Estoy de acuerdo con mucho de lo que dices. Sin embargo, me gustaría agregar una cosa sobre la persistencia de las enumeraciones: no creo que la generación de las enumeraciones en el momento de la compilación a partir de los valores de la base de datos sea aceptable, pero también creo que la verificación del tiempo de ejecución no es una buena solución . Definiría un tercer medio: tener una prueba unitaria que verificará los valores de la enumeración con la base de datos. Esto evita la divergencia "casual" y evita la sobrecarga de comparar las enumeraciones con la base de datos cada vez que se ejecuta el código.

Paul Sonier
fuente
14

El artículo inicial me parece bien. Aún así, según los comentarios, parece que algunos comentarios sobre las enumeraciones de Java podrían aclarar algunas cosas.

El tipo de enumeración en Java es una clase por definición, pero muchos programadores tienden a olvidar esto, porque prefieren relacionarlo con "una lista de valores permitidos" como en algunos otros lenguajes. Es más que eso.

Por lo tanto, para evitar esas declaraciones de cambio, podría ser razonable colocar código y métodos adicionales en la clase enum. Casi nunca es necesario crear una "clase real similar a una enumeración" separada.

Considere también el punto de la documentación: ¿desea documentar el significado real de su enumeración en la base de datos? ¿En el código fuente que refleja los valores (su tipo de enumeración) o en alguna documentación externa? Yo personalmente prefiero el código fuente.

Si desea presentar valores de enumeración como números enteros en la base de datos debido a la velocidad o cualquier motivo, esa asignación también debe residir en la enumeración de Java. Obtendrá la asignación de nombre de cadena de forma predeterminada, y me he contentado con eso. Hay un número ordinal asociado con cada valor de enumeración, pero usarlo directamente como un mapeo entre el código y la base de datos no es muy brillante, porque ese número ordinal cambiará si alguien reordena los valores en el código fuente. O agrega valores de enumeración adicionales entre los valores existentes. O elimina algún valor.

(Por supuesto, si alguien cambia el nombre de la enumeración en el código fuente, el mapeo de cadenas predeterminado también se estropea, pero es menos probable que suceda accidentalmente. y verifique las restricciones en la base de datos como ya se sugirió aquí).

lokori
fuente
1
Hay dos escenarios para apoyar: alguien que reordena las enumeraciones en mi archivo O alguien que hace una refactorización (para aclarar las opciones de nombres iniciales deficientes) y rompe los datos persistentes. Creo que esto último es más importante y ordinal es el camino a seguir para la persistencia de datos.
Justin
7

Intenté resumir mi comprensión. Siéntase libre de editar esto si tiene alguna corrección. Así que aquí va:

En el código

En el código, las enumeraciones deben manejarse usando el tipo de enumeración nativo del lenguaje (al menos en Java y C #), o usando algo como el "patrón de enumeración de tipo seguro" . Se desaconseja el uso de constantes simples (Integer o similar), ya que se pierde la seguridad de los tipos (y se dificulta la comprensión de qué valores son entradas legales para, por ejemplo, un método).

La elección entre estos dos depende de cuánta funcionalidad adicional se adjuntará a la enumeración:

  • Si desea poner un montón de funcionalidad en la enumeración (lo cual es bueno, porque evita cambiar () en todo el tiempo), una clase suele ser más apropiada.
  • Por otro lado, para valores simples de tipo enumeración, la enumeración del lenguaje suele ser más clara.

En particular, al menos en Java una enumeración no puede heredar de otra clase, por lo que si tiene varias enumeraciones con un comportamiento similar que le gustaría poner en una superclase, no puede usar las enumeraciones de Java.

Enumeraciones persistentes

Para conservar las enumeraciones, a cada valor de enumeración se le debe asignar un ID único. Puede ser un número entero o una cadena corta. Se prefiere una cadena corta, ya que puede ser nemotécnica (facilita que los DBA, etc. comprendan los datos sin procesar en la base de datos).

  • En el software, cada enumeración debería tener funciones de mapeo para convertir entre la enumeración (para usar dentro del software) y el valor de ID (para persistir). Algunos marcos (por ejemplo, (N) Hibernate) tienen un soporte limitado para hacer esto automáticamente. De lo contrario, debe ponerlo en el tipo / clase de enumeración.
  • La base de datos debería (idealmente) contener una tabla para cada enumeración que enumere los valores legales. Una columna sería el ID (ver arriba), que es el PK. Las columnas adicionales pueden tener sentido para, por ejemplo, una descripción. Todas las columnas de la tabla que contendrán valores de esa enumeración pueden usar esta "tabla de enumeración" como FK. Esto garantiza que los valores de enumeración incorrectos nunca puedan persistir y permite que la base de datos "se sostenga por sí misma".

Un problema con este enfoque es que la lista de valores legales de enumeración existe en dos lugares (código y base de datos). Esto es difícil de evitar y, por lo tanto, a menudo se considera aceptable, pero hay dos alternativas:

  • Solo mantenga la lista de valores en la base de datos, genere el tipo de enumeración en el momento de la compilación. Elegante, pero significa que se requiere una conexión de base de datos para que se ejecute una compilación, lo que parece problemático.
  • Defina la lista de valores en el código para que tenga autoridad. Verifique los valores en la base de datos en tiempo de ejecución (generalmente al inicio), reclame / anule si no coincide.
sleske
fuente
6

En el manejo de código para C #, se ha perdido la definición de eliminar el valor 0. Casi sin falta siempre declaro mi primer valor como:

public enum SomeEnum
{
    None = 0,
}

Para que sirva como valor nulo. Debido a que el tipo de respaldo es un número entero y un número entero predeterminado es 0, por lo que es muy útil en muchos lugares para saber si una enumeración se ha establecido programáticamente o no.

Quisquilloso
fuente
6
Estoy en desacuerdo. Esto solo tendría sentido si a veces deja variables sin inicializar, lo que consideraría una práctica muy mala. A menudo he visto esta idea de tener un valor "ninguno", pero creo que solo oculta el problema real (la variable no inicializada).
sleske
3
¿Cómo esconde el problema? Lo hace explícito como un int que acepta valores NULL. Dejo los valores sin inicializar en el código porque sé a qué los configurará el CLR de forma predeterminada. Todavía están inicializados, es solo implícito.
Quibbleome
Bueno, probablemente sea una cuestión de estilo. Creo firmemente en la inicialización completa de todas las variables en la declaración (o como máximo en un if-else directamente después de la declaración). De lo contrario, es posible que olvide inicializarlos, especialmente si el flujo de código es complicado. Consulte también c2.com/cgi/wiki?SingleStepConstructor .
sleske
5

Java o C # siempre deben usar enumeraciones en el código. Descargo de responsabilidad: Mi experiencia es C #.

Si el valor se va a conservar en una base de datos, los valores integrales de cada miembro de enumeración deben definirse explícitamente para que un cambio posterior en el código no altere accidentalmente los valores de enumeración traducidos y, por lo tanto, el comportamiento de la aplicación.

Los valores siempre deben conservarse en una base de datos como valores integrales, para proteger contra la refactorización del nombre de enumeración. Mantenga la documentación de cada enumeración en un wiki y agregue un comentario al campo de la base de datos que apunte a la página del wiki que documenta el tipo. También agregue documentación XML al tipo de enumeración que contiene un enlace a la entrada wiki para que esté disponible a través de Intellisense.

Si usa una herramienta para generar código CRUD, debería ser capaz de definir un tipo de enumeración para usar en una columna, de modo que los objetos de código generados siempre usen miembros enumerados.

Si es necesario aplicar lógica personalizada para un miembro de enumeración, tiene algunas opciones:

  • Si tiene una enumeración MyEnum, cree una clase estática MyEnumInfo que ofrezca métodos de utilidad para descubrir información adicional sobre el miembro enum, mediante declaraciones de conmutación o cualquier medio necesario. Agregar "Información" al final del nombre de enumeración en el nombre de la clase asegura que estarán uno al lado del otro en IntelliSense.
  • Decore los miembros de la enumeración con atributos para especificar parámetros adicionales. Por ejemplo, hemos desarrollado un control EnumDropDown que crea un menú desplegable ASP.NET lleno de valores de enumeración, y un EnumDisplayAttribute especifica el texto de visualización bien formateado que se utilizará para cada miembro.

No he intentado esto, pero con SQL Server 2005 o posterior, teóricamente podría registrar el código C # con la base de datos que contendría información de enumeración y la capacidad de convertir valores en enumeraciones para usar en vistas u otras construcciones, haciendo un método para traducir el datos de una manera más fácil de usar para los administradores de bases de datos.

David Boike
fuente
+1 asignar valores explícitamente es la única forma de evitar la "corrupción" cuando se cambia la enumeración
ashes999
3

En mi humilde opinión, en cuanto a la parte del código:

Usted debe siempre utilizar el tipo 'enumeración' para sus enumeraciones, básicamente se obtiene una gran cantidad de regalos si lo hace: Tipo de seguridad, la encapsulación y la evitación del interruptor, el apoyo de algunas colecciones como EnumSety EnumMapy claridad del código.

en cuanto a la parte de persistencia, siempre puede persistir la representación de cadena de la enumeración y volver a cargarla utilizando el método enum.valueOf (String).

MahdeTo
fuente
De acuerdo en principio, sin embargo, al menos en Java, la "enumeración" está limitada porque no puede tener una superclase (como se señaló anteriormente), por lo que a veces una clase de "enumeración segura" es probablemente mejor.
sleske
3

Es menos preferible almacenar el valor de texto de una enumeración en una base de datos que almacenar un número entero, debido al espacio adicional requerido y la búsqueda más lenta. Es valioso porque tiene más significado que un número, sin embargo, la base de datos es para almacenamiento y la capa de presentación es para que las cosas se vean bien.

cjk
fuente
1
No se garantiza que el valor int de la enumeración sea el mismo a lo largo del tiempo.
Miguel Ping
Además, si usa una cadena corta, el rendimiento debería ser el mismo. Un char (2) toma 2 bytes, un int generalmente también toma 2 o 4.
sleske
6
@Miguel Ping: La idea es asignar explícitamente un ID (int o char) a cada enumeración. Usar el int generado internamente de la enumeración es realmente muy peligroso.
sleske
1
si se desea un significado más allá de los valores enteros en la base de datos, lo que usaría es una tabla que mapee los valores enteros con cadenas legibles por humanos. además, sí, los valores enteros no deben cambiar más tarde. aunque no debería ser un problema; la única relevancia que deben tener los valores enteros de enumeración subyacentes es que son diferentes de los otros miembros de la enumeración. (es decir, no deberían tener ninguna razón para cambiar). si los valores enteros tienen un significado más allá de la identificación única, probablemente debería usarse una estructura de datos diferente.
Dave Cousineau
3

Bueno, desde mi experiencia, usar enumeraciones para cualquier otra cosa que no sea para pasar opciones (como indicadores) a una llamada inmediata a un método, da como resultado switch-ing en algún momento.

  • Si va a usar la enumeración en todo su código, entonces podría terminar con un código que no es tan fácil de mantener (la switchdeclaración infame )
  • Extender las enumeraciones es un fastidio. Agrega un nuevo elemento de enumeración y termina revisando todo su código para verificar todas las condiciones.
  • Con .NET 3.5, puede agregar métodos de extensión a las enumeraciones para que se comporten un poco más como clases. Sin embargo, agregar funcionalidad real de esta manera no es tan fácil ya que todavía no es una clase (terminaría usando switch-es en sus métodos de extensión si no en otro lugar.

Entonces, para una entidad tipo enumeración con un poco más de funcionalidad, debe tomarse un tiempo y crearla como una clase, con varias cosas en mente:

  • Para que su clase se comporte como una enumeración, puede forzar a cada clase derivada a crear una instancia como Singleton o anular Equals para permitir la comparación de valores de diferentes instancias.
  • Si su clase es similar a una enumeración, debería significar que no debería contener un estado serializable; la deserialización debería ser posible solo a partir de su tipo (una especie de "ID", como dijo).
  • La lógica de persistencia debe limitarse a la clase base solamente, de lo contrario extender su "enumeración" sería una pesadilla. En caso de que optara por el patrón Singleton, necesitaría garantizar la deserialización adecuada en instancias singleton.
Groo
fuente
3

Cada vez que te encuentres usando "números mágicos" en el código, cambia a enumeraciones. Además del ahorro de tiempo (dado que la magia desaparecerá cuando aparezcan los errores ...), salvará sus ojos y su memoria (las enumeraciones significativas hacen que el código sea más legible y autodocumentado), ya que adivinen qué, lo más probable es que usted sea la persona a mantener y desarrollar tu propio código

Yordan Georgiev
fuente
1

Sé que este es un foro antiguo, ¿y si la base de datos pudiera tener otras cosas que se integran directamente? Por ejemplo, cuando la base de datos resultante es el ÚNICO propósito del código. Luego, estará definiendo las enumeraciones en cada integración. Mejor entonces tenerlos en la base de datos. De lo contrario, estoy de acuerdo con la publicación original.

Gredoso
fuente