c# - utiliza - ¿Cuál es el método preferido para manejar valores de enumeración inesperados?
que es un enum en java (12)
En Java, la forma estándar es lanzar un AssertionError
, por dos razones:
- Esto asegura que incluso si las
asserts
están deshabilitadas, se produce un error. - Está afirmando que no hay otros valores de enumeración, por lo que
AssertionError
documenta sus suposiciones mejor queNotImplementedException
(que Java no tiene de todos modos).
Supongamos que tenemos un método que acepta un valor de una enumeración. Después de que este método comprueba que el valor es válido, switch
los valores posibles. Entonces, la pregunta es: ¿cuál es el método preferido para manejar valores inesperados después de que se haya validado el rango de valores?
Por ejemplo:
enum Mood { Happy, Sad }
public void PrintMood(Mood mood)
{
if (!Enum.IsDefined(typeof(Mood), mood))
{
throw new ArgumentOutOfRangeException("mood");
}
switch (mood)
{
case Happy: Console.WriteLine("I am happy"); break;
case Sad: Console.WriteLine("I am sad"); break;
default: // what should we do here?
}
¿Cuál es el método preferido para manejar el caso default
?
- Deja un comentario como
// can never happen
-
Debug.Fail()
(oDebug.Assert(false)
) -
throw new NotImplementedException()
(o cualquier otra excepción) - De alguna otra manera no he pensado
Es responsabilidad de la función de llamada proporcionar una entrada válida e implícitamente cualquier cosa que no esté en la enumeración no es válida (el programador Pragmático parece implicar esto). Dicho esto, esto implica que cada vez que cambie su enumeración, debe cambiar TODO el código que lo acepta como entrada (y ALGUNO código que lo produce como salida). Pero eso es probablemente cierto de todos modos. Si tiene una enumeración que cambia con frecuencia, probablemente debería usar algo que no sea una enumeración, considerando que las enumeraciones son normalmente entidades de tiempo de compilación.
Esta es una de esas preguntas que demuestra por qué el desarrollo basado en pruebas es tan importante.
En este caso, me gustaría una NotSupportedException porque, literalmente, el valor no estaba manejado y, por lo tanto, no era compatible. Una excepción NotImplementedException da más la idea de: "Esto aún no está terminado";)
El código de llamada debe poder manejar una situación como esta y se pueden crear pruebas de unidad para probar fácilmente este tipo de situaciones.
La respuesta correcta del programa sería morir de tal manera que el desarrollador pueda detectar fácilmente el problema. mmyers y JaredPar dieron buenas maneras de hacerlo.
Por que morir ¡Eso parece tan extremo!
La razón es que si no está manejando un valor de enumeración correctamente y simplemente no cumple, está poniendo su programa en un estado inesperado. Una vez que estás en un estado inesperado, quién sabe qué está pasando. Esto puede provocar datos erróneos, errores que son más difíciles de rastrear o incluso vulnerabilidades de seguridad.
Además, si el programa muere, existe una mayor probabilidad de que lo atrapen en el control de calidad y, por lo tanto, ni siquiera se salga por la puerta.
Llámelo una opinión o una preferencia, pero la idea detrás de la enumeración es que representa la lista completa de valores posibles. Si se pasa un "valor inesperado" en el código, entonces la enumeración (o propósito detrás de la enumeración) no está actualizada. Mi preferencia personal es que cada enumeración lleve una asignación predeterminada de Undefined. Dado que la enumeración es una lista definida, nunca debe estar desactualizada con su código de consumo.
En cuanto a qué hacer si su función está obteniendo un valor inesperado o No definido en mi caso, una respuesta genérica no parece posible. Para mí, depende del contexto de la razón para evaluar el valor de enumeración: ¿es una situación en la que la ejecución del código debería detenerse, o se puede usar un valor predeterminado?
Mi opinión es que, dado que se trata de un error del programador, deberías hacer una afirmación o lanzar una RuntimException (Java, o lo que sea equivalente para otros idiomas). Tengo mi propia UnhandledEnumException que se extiende desde RuntimeException que uso para esto.
Para C #, algo que vale la pena saber es que Enum.IsDefined()
es peligroso . No puedes confiar en eso como eres. Obtener algo que no sea de los valores esperados es un buen caso para lanzar una excepción y morir en voz alta.
En Java, es diferente porque las enumeraciones son clases no enteras, por lo que realmente no se pueden obtener valores inesperados (a menos que la enumeración se actualice y la instrucción de cambio no lo sea), que es una de las principales razones por las que prefiero las enumeraciones de Java. También tienes que atender a los valores nulos. Pero obtener un caso que no sea nulo que no reconozca también es un buen caso para lanzar una excepción.
Para casi todas las declaraciones de cambio en mi base de código, tengo el siguiente caso predeterminado
switch( value ) {
...
default:
Contract.InvalidEnumValue(value);
}
El método emitirá una excepción que detalla el valor de la enumeración en el momento en que se detectó un error.
public static void InvalidEnumValue<T>(T value) where T: struct
{
ThrowIfFalse(typeof(T).IsEnum, "Expected an enum type");
Violation("Invalid Enum value of Type {0} : {1}", new object[] { typeof(T).Name, value });
}
Podría tener una traza para el valor predeterminado que indique el valor de la enumeración pasada. Lanzar excepciones está bien, pero en una aplicación grande habrá varios lugares donde a su código no le interesan otros valores de la enumeración.
Por lo tanto, a menos que esté seguro de que el código intenta manejar todos los valores posibles de la enumeración, tendrá que regresar más tarde y eliminar la excepción.
Prefiero throw new NotImplementedException("Unhandled Mood: " + mood)
. El punto es que la enumeración puede cambiar en el futuro, y este método puede no actualizarse en consecuencia. Lanzar una excepción parece ser el método más seguro.
No me gusta el método Debug.Fail()
, porque el método puede ser parte de una biblioteca y los nuevos valores pueden no probarse en el modo de depuración. Otras aplicaciones que usan esa biblioteca pueden enfrentar un comportamiento extraño en tiempo de ejecución en ese caso, mientras que en el caso de lanzar una excepción, el error se conocerá de inmediato.
Nota: NotImplementedException
existe en commons.lang
.
Supongo que la mayoría de las respuestas anteriores son válidas, pero no estoy seguro de que alguna sea correcta.
La respuesta correcta es que rara vez cambias un idioma OO, indica que estás haciendo tu OO mal. En este caso, es una indicación perfecta de que su clase Enum tiene problemas.
Solo debe llamar a Console.WriteLine (mood.moodMessage ()) y definir moodMessage para cada uno de los estados.
Si se agrega un nuevo estado: todo su código debería adaptarse automáticamente, nada debería fallar, lanzar una excepción o necesitar cambios.
Editar: respuesta al comentario.
En su ejemplo, para ser "Good OO", la funcionalidad del modo de archivo estaría controlada por el objeto FileMode. Podría contener un objeto delegado con operaciones "abrir, leer, escribir ..." que son diferentes para cada Modo de archivo, por lo que se podría implementar Archivo.open ("nombre", ArchivoModo.Crear) como (lamento la falta de familiaridad con la API):
open(String name, FileMode mode) {
// May throw an exception if, for instance, mode is Open and file doesn''t exist
// May also create the file depending on Mode
FileHandle fh = mode.getHandle(name);
... code to actually open fh here...
// Let Truncate and append do their special handling
mode.setPosition(fh);
}
Esto es mucho más limpio que intentar hacerlo con interruptores ... (por cierto, los métodos serían tanto privados como de paquetes y posiblemente delegados a clases de "Modo")
Cuando OO se hace bien, cada método parece unas pocas líneas de código realmente comprensible y simple, DEMASIADO. Siempre tienes la sensación de que hay un gran "Núcleo de queso" desordenado que mantiene todos los pequeños objetos de nacho, pero nunca puedes encontrarlo, son nachos hasta el fondo ...
Usualmente trato de definir un valor indefinido (0):
enum Mood { Undefined = 0, Happy, Sad }
De esa manera siempre puedo decir:
switch (mood)
{
case Happy: Console.WriteLine("I am happy"); break;
case Sad: Console.WriteLine("I am sad"); break;
case Undefined: // handle undefined case
default: // at this point it is obvious that there is an unknown problem
// -> throw -> die :-)
}
Esto es al menos como normalmente hago esto.