usuario - que tipos de excepciones existen en c#
Manejo de errores sin excepciones (6)
Creo que has tenido la impresión equivocada del mensaje deseado. Esta es una excelente cita que encontré ayer de la edición actual de la revista Visual Studio (Vol. 19, No 8).
O un miembro cumple su contrato o lanza un retiro. Período. No hay término medio. No hay códigos de retorno, no a veces funciona, a veces no funciona.
Las excepciones deben usarse con cuidado ya que son costosas de crear y lanzar, pero son, sin embargo, la forma en que .NET Framework de notificar a un cliente (me refiero a cualquier componente de llamada) de un error.
Mientras busco SO para los enfoques de manejo de errores relacionados con la validación de reglas de negocios, todo lo que encuentro son ejemplos de manejo estructurado de excepciones.
MSDN y muchos otros recursos de desarrollo de buena reputación tienen muy claro que no se deben usar excepciones para manejar los casos de error de rutina. Solo deben usarse para circunstancias excepcionales y errores inesperados que pueden ocurrir debido a un uso inadecuado por parte del programador (pero no del usuario). En muchos casos, los errores de los usuarios, como los campos que se dejan en blanco, son comunes, y todo lo que nuestro programa debería esperar, y por lo tanto no son excepcionales y no son candidatos para el uso de excepciones.
CITAR:
Recuerde que el uso del término excepción en la programación tiene que ver con el pensamiento de que una excepción debe representar una condición excepcional. Las condiciones excepcionales, por su propia naturaleza, no ocurren normalmente; por lo tanto, su código no debe lanzar excepciones como parte de sus operaciones diarias.
No lance excepciones para señalar eventos comunes. Considere el uso de métodos alternativos para comunicar a la persona que llama la ocurrencia de esos eventos y deje la excepción para cuando ocurra algo realmente fuera de lo común.
Por ejemplo, uso adecuado:
private void DoSomething(string requiredParameter)
{
if (requiredParameter == null) throw new ArgumentExpcetion("requiredParameter cannot be null");
// Remainder of method body...
}
Uso inapropiado:
// Renames item to a name supplied by the user. Name must begin with an "F".
public void RenameItem(string newName)
{
// Items must have names that begin with "F"
if (!newName.StartsWith("F")) throw new RenameException("New name must begin with /"F/"");
// Remainder of method body...
}
En el caso anterior, de acuerdo con las mejores prácticas, hubiera sido mejor pasar el error a la interfaz de usuario sin involucrar / requerir los mecanismos de manejo de excepciones de .NET.
Utilizando el mismo ejemplo anterior, supongamos que uno debe imponer un conjunto de reglas de denominación contra los elementos. ¿Qué enfoque sería mejor?
¿Tener el método devolver un resultado enumerado? RenameResult.Success, RenameResult.TooShort, RenameResult.TooLong, RenameResult.InvalidCharacters, etc.
¿Está utilizando un evento en una clase de controlador para informar a la clase de UI? La IU llama al método RenameItem del controlador, y luego maneja un evento AfterRename que el controlador genera y que ha cambiado el nombre del estado como parte de los argumentos del evento.
La clase de control hace referencia y llama directamente a un método de la clase de UI que maneja el error, por ejemplo, ReportError (texto de cadena).
Algo más... ?
Esencialmente, quiero saber cómo realizar una validación compleja en clases que pueden no ser la clase Form en sí, y pasar los errores a la clase Form para su visualización, pero no quiero involucrar el manejo de excepciones donde no se debe usar (¡Aunque parece mucho más fácil!)
Basándome en las respuestas a la pregunta, creo que tendré que plantear el problema en términos que sean más concretos:
UI = Interfaz de usuario, BLL = Capa de lógica de negocios (en este caso, solo una clase diferente)
- El usuario introduce el valor dentro de la interfaz de usuario.
- La UI reporta el valor a BLL.
- BLL realiza la validación rutinaria del valor.
- BLL descubre la violación de la regla.
- BLL devuelve la violación de la regla a la interfaz de usuario.
- La IU recibe el retorno de BLL e informa un error al usuario.
Dado que es rutinario que un usuario ingrese valores no válidos, no se deben usar excepciones. ¿Cuál es la manera correcta de hacer esto sin excepciones?
El ejemplo que da es de entradas de validación de interfaz de usuario.
Por lo tanto, un buen enfoque es separar la validación de la acción. WinForms tiene un sistema de validación integrado, pero en principio funciona de la siguiente manera:
ValidationResult v = ValidateName(string newName);
if (v == ValidationResult.NameOk)
SetName(newName);
else
ReportErrorAndAskUserToRetry(...);
Además, puede aplicar la validación en el método SetName para asegurarse de que se haya verificado la validez:
public void SetName(string newName)
{
if (ValidateName(newName) != ValidationResult.NameOk)
throw new InvalidOperationException("name has not been correctly validated");
name = newName;
}
(Tenga en cuenta que este puede no ser el mejor enfoque para el rendimiento, pero en el caso de aplicar una verificación de validación simple a una entrada de UI, es poco probable que la validación dos veces tenga algún significado. Alternativamente, la verificación anterior se podría realizar simplemente como una verificación de comprobación de debug-only para detectar cualquier intento por parte de los programadores de llamar al método sin validar primero la entrada. Una vez que sepa que todas las personas que llaman cumplen con su contrato, a menudo no es necesario realizar una verificación del tiempo de ejecución de liberación)
Para citar otra respuesta:
Either a member fulfills its contract or it throws an exception. Period.
Lo que esto se pierde es: ¿Cuál es el contrato? Es perfectamente razonable establecer en el "contrato" que un método devuelve un valor de estado. eg File.Exists () devuelve un código de estado, no una excepción, porque ese es su contrato.
Sin embargo, su ejemplo es diferente. En él, realmente haces dos acciones separadas: validación y almacenamiento. Si SetName puede devolver un código de estado o establecer el nombre, está tratando de hacer dos tareas en una, lo que significa que la persona que llama nunca sabe qué comportamiento exhibirá, y tiene que tener un manejo especial de casos para esos casos. Sin embargo, si divide SetName en pasos independientes de Validar y Almacenar, entonces el contrato para StoreName puede consistir en que pase entradas válidas (tal como se pasa por ValidateName), y arroja una excepción si este contrato no se cumple. Debido a que cada método hace una cosa y solo una cosa, el contrato es muy claro y es obvio cuando se debe lanzar una excepción.
En mi opinión, en caso de duda, lanzar excepciones a la validación de reglas de negocios. Sé que esto es algo contraintuitivo y puede que me enoje por esto, pero lo apoyo porque lo que es rutinario y lo que no depende de la situación, y es una decisión de negocios, no de programación.
Por ejemplo, si tiene clases de dominio de negocios que son utilizadas por una aplicación WPF y un servicio WCF, la entrada no válida de un campo puede ser rutinaria en una aplicación WPF, pero sería desastroso cuando los objetos del dominio se usen en una situación WCF donde está manejando solicitudes de servicio desde otra aplicación.
Pensé largo y duro, y se me ocurrió esta solución. Haga lo siguiente para las clases de dominio:
- Agregar una propiedad: ThrowsOnBusinessRule. El valor predeterminado debe ser verdadero para lanzar excepciones. Ajústalo a falso si no quieres tirarlo.
- Agregue una colección privada de diccionarios para almacenar excepciones con clave como propiedad de dominio que tiene una violación de las reglas de negocios. (Por supuesto, puedes exponerlo públicamente si quieres)
- Agregue un método: ThrowsBusinessRule (string propertyName, Exception e) para manejar la lógica anterior
- Si lo desea, puede implementar IDataErrorInfo y usar la colección de diccionarios. La implementación de IDataErrorInfo es trivial dada la configuración anterior.
Estoy de acuerdo con parte de la sugerencia de Henk.
Tradicionalmente, las operaciones de "pasar / fallar" se implementaron como funciones con un tipo de retorno entero o bool que especificaría el resultado de la llamada. Sin embargo, algunos se oponen a esto, afirmando que "Una función o método debe realizar una acción o devolver un valor, pero no ambos". En otras palabras, un miembro de la clase que devuelve un valor no debería ser un miembro de la clase que cambia el estado del objeto.
He encontrado la mejor solución para agregar un .HasErrors / .IsValid y una propiedad .Errors dentro de la clase que genera los errores. Las primeras dos propiedades permiten que la clase cliente pruebe si existen errores o no, y si es necesario, también puede leer la propiedad .Errors e informar uno o todos los errores contenidos. Luego, cada método debe conocer estas propiedades y administrar el estado de error de manera apropiada. Estas propiedades se pueden incluir en una interfaz IErrorReporting que pueden incorporar varias clases de fachada de capa de regla empresarial.
Las excepciones son solo eso: una forma de manejar escenarios excepcionales, escenarios que normalmente no deberían ocurrir en su aplicación. Ambos ejemplos proporcionados son ejemplos razonables de cómo usar las excepciones correctamente. En ambos casos, están identificando que se ha invocado una acción que no debe permitirse y es excepcional al flujo normal de la aplicación.
La mala interpretación es que el segundo error, el método de cambio de nombre, es el único mecanismo para detectar el error de cambio de nombre. Las excepciones nunca deben usarse como un mecanismo para pasar mensajes a una interfaz de usuario. En este caso, tendrá alguna lógica que compruebe que el nombre especificado para el cambio de nombre es válido en algún lugar de su validación de UI. Esta validación lo haría para que la excepción nunca sea parte del flujo normal.
Existen excepciones para evitar que ocurran "cosas malas", que actúan como la última línea de defensa de sus llamadas a la API para garantizar que se detengan los errores y que solo pueda tener lugar un comportamiento legal. Son errores a nivel de programador y solo deben indicar que se ha producido uno de los siguientes:
- Se ha producido algo catastrófico y sistémico, como quedarse sin memoria.
- Un programador ha programado algo incorrecto, ya sea una mala llamada a un método o una pieza de SQL ilegal.
No se supone que sean la única línea de defensa contra el error del usuario. Los usuarios requieren muchos más comentarios y cuidados de los que una excepción ofrecerá e intentarán de forma rutinaria hacer cosas que están fuera del flujo esperado de sus aplicaciones.
Supongo que está creando su propio motor de validación de reglas de negocio, ya que no ha mencionado el que está utilizando.
Yo usaría excepciones, pero no las tiraría. Obviamente, deberá estar acumulando el estado de la evaluación en algún lugar: para registrar el hecho de que una regla en particular falló, almacenaría una instancia de Excepción que describa la falla. Esto es porque:
- Las excepciones son serializables
- Las excepciones siempre tienen una propiedad de
Message
que es legible para el ser humano, y pueden tener propiedades adicionales para registrar los detalles de la excepción en forma legible por máquina. - Algunas de las fallas de las reglas de negocios pueden, de hecho, haber sido señaladas por excepciones, como
FormatException
, por ejemplo. Podría capturar esa excepción y agregarla a la lista.
De hecho, la revista MSDN de este mes tiene un artículo que menciona la nueva clase AggregateException
en .NET 4.0, que pretende ser una colección de excepciones que ocurrieron en un contexto particular.
Dado que está utilizando Windows Forms, debe usar los mecanismos integrados para la validación: el evento Validating
y el componente ErrorProvider
.