Tengo excepciones creadas para cada condición que mi aplicación no espera. UserNameNotValidException
, PasswordNotCorrectException
Etc.
Sin embargo, me dijeron que no debía crear excepciones para esas condiciones. En mi UML esas SON excepciones al flujo principal, entonces ¿por qué no debería ser una excepción?
¿Alguna orientación o mejores prácticas para crear excepciones?
exception
language-agnostic
Kwan Cheng
fuente
fuente
IsCredentialsValid(username,password)
no debe arrojar una excepción si el nombre de usuario o la contraseña no son válidos, pero devuelve falso. Pero supongamos que un método que lee datos de la base de datos podría arrojar legítimamente tal excepción, si falla la autenticación. En resumen: debe lanzar una excepción si un método no puede realizar la tarea que se supone que debe hacer.Respuestas:
Mi pauta personal es: se produce una excepción cuando se descubre que una suposición fundamental del bloque de código actual es falsa.
Ejemplo 1: digamos que tengo una función que se supone que examina una clase arbitraria y devuelve verdadero si esa clase hereda de la Lista <>. Esta función hace la pregunta, "¿Es este objeto un descendiente de la Lista?" Esta función nunca debería arrojar una excepción, porque no hay áreas grises en su operación: cada clase hereda o no de la Lista <>, por lo que la respuesta es siempre "sí" o "no".
Ejemplo 2: digamos que tengo otra función que examina una Lista <> y devuelve verdadero si su longitud es mayor que 50, y falso si la longitud es menor. Esta función hace la pregunta, "¿Esta lista tiene más de 50 elementos?" Pero esta pregunta supone: supone que el objeto que se le da es una lista. Si le doy un NULL, entonces esa suposición es falsa. En ese caso, si la función devuelve ya sea verdadera o falsa, entonces es romper sus propias reglas. La función no puede devolver nada y afirmar que respondió la pregunta correctamente. Por lo tanto, no regresa, arroja una excepción.
Esto es comparable a la falacia lógica de la "pregunta cargada" . Cada función hace una pregunta. Si la entrada que se da hace que esa pregunta sea una falacia, entonces arroje una excepción. Esta línea es más difícil de dibujar con funciones que devuelven vacío, pero la conclusión es: si se violan los supuestos de la función sobre sus entradas, debería arrojar una excepción en lugar de regresar normalmente.
El otro lado de esta ecuación es: si encuentra que sus funciones arrojan excepciones con frecuencia, entonces probablemente necesite refinar sus suposiciones.
fuente
Porque son cosas que sucederán normalmente. Las excepciones no son mecanismos de control de flujo. Los usuarios suelen tener contraseñas incorrectas, no es un caso excepcional. Las excepciones deben ser algo realmente raro,
UserHasDiedAtKeyboard
escriba situaciones.fuente
Mis pequeñas pautas están fuertemente influenciadas por el gran libro "Código completo":
fuente
NO es una excepción si el nombre de usuario no es válido o la contraseña no es correcta. Esas son cosas que debe esperar en el flujo normal de operación. Las excepciones son cosas que no son parte de la operación normal del programa y son bastante raras.
EDITAR: No me gusta usar excepciones porque no se puede saber si un método arroja una excepción simplemente mirando la llamada. Es por eso que las excepciones solo deben usarse si no puede manejar la situación de manera decente (piense "sin memoria" o "la computadora está en llamas").
fuente
Una regla general es usar excepciones en el caso de algo que normalmente no podría predecir. Ejemplos son la conectividad de la base de datos, el archivo faltante en el disco, etc. Para los escenarios que puede predecir, es decir, los usuarios que intentan iniciar sesión con una contraseña incorrecta, deberían usar funciones que devuelvan booleanos y sepan cómo manejar la situación con gracia. No desea finalizar abruptamente la ejecución lanzando una excepción solo porque alguien escribió mal su contraseña.
fuente
Otros proponen que no se usen excepciones porque el inicio de sesión incorrecto se espera en un flujo normal si el usuario escribe incorrectamente. No estoy de acuerdo y no entiendo el razonamiento. Compárelo con abrir un archivo ... si el archivo no existe o no está disponible por alguna razón, el marco generará una excepción. Usar la lógica anterior fue un error de Microsoft. Deberían haber devuelto un código de error. Lo mismo para el análisis, webrequests, etc., etc.
No considero que un inicio de sesión incorrecto forme parte de un flujo normal, es excepcional. Normalmente, el usuario escribe la contraseña correcta y el archivo existe. Los casos excepcionales son excepcionales y está perfectamente bien usar excepciones para ellos. Complicar su código al propagar valores de retorno a través de n niveles en la pila es un desperdicio de energía y dará como resultado un código desordenado. Haz lo más simple que pueda funcionar. No optimice prematuramente mediante el uso de códigos de error, raramente ocurren cosas excepcionales por definición, y las excepciones no cuestan nada a menos que las arroje.
fuente
login
método de tipo sería que una contraseña puede ser incorrecta, de hecho puede usarse para determinar esto, y no querría tener una excepción en este caso; mientras que en unfile open
escenario tipo, que tiene un resultado particular deseado, si el sistema no puede entregar el resultado debido a parámetros de entrada incorrectos o algún factor externo, ese es un uso perfectamente lógico de una excepción.Las excepciones son un efecto algo costoso, si, por ejemplo, tiene un usuario que proporciona una contraseña no válida, generalmente es una mejor idea devolver un indicador de falla o algún otro indicador de que no es válido.
Esto se debe a la forma en que se manejan las excepciones, la entrada incorrecta verdadera y los elementos de parada críticos únicos deben ser excepciones, pero no información de inicio de sesión fallida.
fuente
Creo que solo deberías lanzar una excepción cuando no hay nada que puedas hacer para salir de tu estado actual. Por ejemplo, si está asignando memoria y no hay ninguna para asignar. En los casos que menciona, puede recuperarse claramente de esos estados y puede devolver un código de error a su interlocutor en consecuencia.
Verá muchos consejos, incluso en las respuestas a esta pregunta, de que debe lanzar excepciones solo en circunstancias "excepcionales". Eso parece superficialmente razonable, pero es un consejo erróneo, porque reemplaza una pregunta ("cuándo debo lanzar una excepción") con otra pregunta subjetiva ("qué es excepcional"). En cambio, siga los consejos de Herb Sutter (para C ++, disponible en el artículo del Dr. Dobbs Cuándo y cómo usar excepciones , y también en su libro con Andrei Alexandrescu, C ++ Coding Standards ): arroje una excepción si, y solo si
¿Por qué es esto mejor? ¿No reemplaza la pregunta con varias preguntas sobre condiciones previas, condiciones posteriores e invariantes? Esto es mejor por varias razones relacionadas.
throw
es un detalle de implementación. Nos obliga a tener en cuenta que debemos considerar el diseño y su implementación por separado, y nuestro trabajo al implementar un método es producir algo que satisfaga las restricciones de diseño.catch
cláusulas.fuente
Yo diría que no hay reglas estrictas sobre cuándo usar excepciones. Sin embargo, hay buenas razones para usarlas o no:
Razones para usar excepciones:
Razones para no usar excepciones:
En general, estaría más inclinado a usar excepciones en Java que en C ++ o C #, porque considero que una excepción, declarada o no, es fundamentalmente parte de la interfaz formal de una función, ya que cambiar su garantía de excepción puede romper el código de llamada La mayor ventaja de usarlos en Java IMO es que sabe que su interlocutor DEBE manejar la excepción, y esto mejora las posibilidades de un comportamiento correcto.
Debido a esto, en cualquier idioma, siempre derivaría todas las excepciones en una capa de código o API de una clase común, por lo que el código de llamada siempre puede garantizar la captura de todas las excepciones. También consideraría que es malo lanzar clases de excepción que son específicas de la implementación, al escribir una API o biblioteca (es decir, ajustar las excepciones de las capas inferiores para que la excepción que recibe la persona que llama sea comprensible en el contexto de su interfaz).
Tenga en cuenta que Java hace la distinción entre excepciones generales y de tiempo de ejecución en que este último no necesita ser declarado. Solo usaría las clases de excepción de tiempo de ejecución cuando sepa que el error es el resultado de un error en el programa.
fuente
Las clases de excepción son como las clases "normales". Se crea una nueva clase cuando "es" un tipo diferente de objeto, con diferentes campos y diferentes operaciones.
Como regla general, debe intentar equilibrar el número de excepciones y la granularidad de las excepciones. Si su método arroja más de 4-5 excepciones diferentes, probablemente pueda fusionar algunas de ellas en excepciones más "generales" (por ejemplo, en su caso "AuthenticationFailedException"), y usar el mensaje de excepción para detallar qué salió mal. A menos que su código maneje cada uno de ellos de manera diferente, no necesita crear muchas clases de excepción. Y si lo hace, puede que simplemente devuelva una enumeración con el error que ocurrió. Es un poco más limpio de esta manera.
fuente
Si el código se ejecuta dentro de un bucle que probablemente causará una excepción una y otra vez, entonces lanzar excepciones no es algo bueno, porque son bastante lentas para la gran N. Pero no hay nada de malo en lanzar excepciones personalizadas si el rendimiento no es un problema. Solo asegúrese de tener una excepción base que todos hereden, llamada BaseException o algo así. BaseException hereda System.Exception, pero todas sus excepciones heredan BaseException. Incluso puede tener un árbol de tipos de Excepción para agrupar tipos similares, pero esto puede ser excesivo o no.
Entonces, la respuesta corta es que si no causa una penalización de rendimiento significativa (que no debería a menos que esté lanzando muchas excepciones), entonces continúe.
fuente
int.MaxValue
tiempos de bucle y generando la excepción 'dividir por cero'. La versión IF / ELSE, en la que estaba comprobando si el dividendo no es cero antes de la operación de división , se completó en 6082 ms y 15407722, mientras que la versión TRY / CATCH, en la que estaba generando la excepción y capturando la excepción , se completó en 28174385 ms y 71371326155 ticks: la friolera de 4632 veces más que la versión if / else.Estoy de acuerdo con japollock allá arriba: presente una aceptación cuando no esté seguro del resultado de una operación. Llamadas a API, acceso a sistemas de archivos, llamadas a bases de datos, etc. Cada vez que se mueva más allá de los "límites" de sus lenguajes de programación.
Me gustaría agregar, siéntase libre de lanzar una excepción estándar. A menos que vaya a hacer algo "diferente" (ignorar, enviar un correo electrónico, iniciar sesión, mostrar esa imagen de ballena de Twitter, etc.), no se moleste con excepciones personalizadas.
fuente
La regla general para lanzar excepciones es bastante simple. lo hace cuando su código ha entrado en un estado NO RECUPERABLE NO VÁLIDO. Si los datos se ven comprometidos o no puede cancelar el procesamiento que se produjo hasta el momento, debe finalizarlos. de hecho, ¿qué más puedes hacer? su lógica de procesamiento finalmente fallará en otro lugar. Si puede recuperarse de alguna manera, hágalo y no arroje una excepción.
en su caso particular, si se vio obligado a hacer algo tonto como aceptar el retiro de dinero y solo luego verificar el usuario / contraseña, debe finalizar el proceso lanzando una excepción para notificar que algo malo ha sucedido y evitar daños mayores.
fuente
En general, desea lanzar una excepción por cualquier cosa que pueda suceder en su aplicación que sea "Excepcional"
En su ejemplo, parece que ambas excepciones las está llamando a través de una contraseña / validación de nombre de usuario. En ese caso, se puede argumentar que no es realmente excepcional que alguien escriba mal un nombre de usuario / contraseña.
Son "excepciones" al flujo principal de su UML pero son más "ramas" en el procesamiento.
Si intentó acceder a su archivo o base de datos passwd y no pudo, ese sería un caso excepcional y garantizaría lanzar una excepción.
fuente
En primer lugar, si los usuarios de su API no están interesados en fallas específicas y específicas, entonces tener excepciones específicas para ellos no tiene ningún valor.
Como a menudo no es posible saber qué puede ser útil para sus usuarios, un mejor enfoque es tener las excepciones específicas, pero asegurarse de que hereden de una clase común (por ejemplo, std :: exception o sus derivados en C ++). Eso le permite a su cliente detectar excepciones específicas si así lo desea, o la excepción más general si no le importa.
fuente
Las excepciones están destinadas a eventos que son comportamientos anormales, errores, fallas, etc. El comportamiento funcional, el error del usuario, etc., deben ser manejados por la lógica del programa. Dado que una cuenta o contraseña incorrecta es una parte esperada del flujo lógico en una rutina de inicio de sesión, debería ser capaz de manejar esas situaciones sin excepciones.
fuente
Tengo problemas filosóficos con el uso de excepciones. Básicamente, usted espera que ocurra un escenario específico, pero en lugar de manejarlo explícitamente, está empujando el problema para que se maneje "en otro lugar". Y dónde está ese "otro lugar" puede ser una incógnita.
fuente
Yo diría que, en general, todo fundamentalismo lleva al infierno.
Ciertamente, no querrás terminar con un flujo impulsado por excepciones, pero evitar las excepciones por completo también es una mala idea. Tienes que encontrar un equilibrio entre ambos enfoques. Lo que no haría es crear un tipo de excepción para cada situación excepcional. Eso no es productivo.
Lo que generalmente prefiero es crear dos tipos básicos de excepciones que se utilizan en todo el sistema: LogicalException y TechnicalException . Estos pueden distinguirse aún más por subtipos si es necesario, pero generalmente no es necesario.
La excepción técnica denota la excepción realmente inesperada, como que el servidor de la base de datos está inactivo, la conexión al servicio web arrojó la IOException, etc.
Por otro lado, las excepciones lógicas se utilizan para propagar la situación errónea menos grave a las capas superiores (generalmente, algún resultado de validación).
Tenga en cuenta que incluso la excepción lógica no está destinada a ser utilizada regularmente para controlar el flujo del programa, sino para resaltar la situación en la que el flujo realmente debería terminar. Cuando se usa en Java, ambos tipos de excepción son subclases de RuntimeException y el manejo de errores está altamente orientado a aspectos.
Por lo tanto, en el ejemplo de inicio de sesión, sería aconsejable crear algo como AuthenticationException y distinguir las situaciones concretas mediante valores de enumeración como UsernameNotExisting , PasswordMismatch , etc. Entonces no terminará teniendo una gran jerarquía de excepciones y puede mantener los bloques de captura en un nivel mantenible . También puede emplear fácilmente algún mecanismo genérico de manejo de excepciones, ya que tiene las excepciones categorizadas y sabe muy bien qué propagar al usuario y cómo.
Nuestro uso típico es lanzar la excepción lógica durante la llamada al servicio web cuando la entrada del usuario no es válida. La excepción se ordena al detalle SOAPFault y luego se desarma a la excepción nuevamente en el cliente, lo que resulta en mostrar el error de validación en un determinado campo de entrada de página web, ya que la excepción tiene una asignación adecuada a ese campo.
Ciertamente, esta no es la única situación: no es necesario acceder al servicio web para generar la excepción. Usted es libre de hacerlo en cualquier situación excepcional (como en el caso de que necesite fallar rápidamente), todo es a su discreción.
fuente
para mí Se debe lanzar una excepción cuando falla una regla técnica o comercial requerida. por ejemplo, si una entidad de automóvil está asociada con un conjunto de 4 llantas ... si una llanta o más son nulas ... una excepción debe activarse "NotEnoughTiresException", porque puede detectarse en un nivel diferente del sistema y tener un impacto significativo significado a través de la tala. además si solo tratamos de controlar el flujo del nulo y evitar la instanciación del automóvil. Es posible que nunca encontremos la fuente del problema, porque no se supone que la llanta sea nula en primer lugar.
fuente
La razón principal para evitar lanzar una excepción es que hay muchos gastos generales involucrados en lanzar una excepción.
Una cosa que establece el artículo a continuación es que una excepción es para condiciones y errores excepcionales.
Un nombre de usuario incorrecto no es necesariamente un error del programa, sino un error del usuario ...
Aquí hay un punto de partida decente para excepciones dentro de .NET: http://msdn.microsoft.com/en-us/library/ms229030(VS.80).aspx
fuente
Lanzar excepciones hace que la pila se desenrolle, lo que tiene algunos impactos en el rendimiento (admitido, los entornos administrados modernos han mejorado en eso). Sería una mala idea seguir lanzando y atrapando repetidamente excepciones en una situación anidada.
Probablemente más importante que eso, las excepciones son para condiciones excepcionales. No deben usarse para el flujo de control ordinario, ya que esto dañará la legibilidad de su código.
fuente
Tengo tres tipos de condiciones que atrapo.
La entrada incorrecta o faltante no debería ser una excepción. Utilice tanto el js del lado del cliente como la expresión regular del lado del servidor para detectar, establecer atributos y reenviar a la misma página con mensajes.
La AppException. Esta suele ser una excepción que detecta y agrega en su código. En otras palabras, estos son los que espera (el archivo no existe). Regístrese, configure el mensaje y reenvíe a la página de error general. Esta página generalmente tiene un poco de información sobre lo que sucedió.
La excepción inesperada. Estos son los que no conoces. Regístrese con detalles y reenvíelos a una página de error general.
Espero que esto ayude
fuente
La seguridad se combina con su ejemplo: no debe decirle a un atacante que existe un nombre de usuario, pero la contraseña es incorrecta. Esa es información adicional que no necesita compartir. Simplemente diga "el nombre de usuario o la contraseña son incorrectos".
fuente
La respuesta simple es, siempre que una operación sea imposible (debido a cualquiera de las aplicaciones O porque violaría la lógica de negocios). Si se invoca un método y es imposible hacer para lo que fue escrito, lanzar una excepción. Un buen ejemplo es que los constructores siempre arrojan ArgumentExceptions si no se puede crear una instancia utilizando los parámetros proporcionados. Otro ejemplo es InvalidOperationException, que se produce cuando no se puede realizar una operación debido al estado de otro miembro o miembros de la clase.
En su caso, si se invoca un método como Iniciar sesión (nombre de usuario, contraseña), si el nombre de usuario no es válido, es correcto lanzar una UserNameNotValidException o PasswordNotCorrectException si la contraseña es incorrecta. El usuario no puede iniciar sesión con los parámetros proporcionados (es decir, es imposible porque violaría la autenticación), por lo que debe lanzar una excepción. Aunque podría tener sus dos Excepciones heredadas de ArgumentException.
Dicho esto, si NO desea lanzar una Excepción porque un error de inicio de sesión puede ser muy común, una estrategia es crear un método que devuelva tipos que representen errores diferentes. Aquí hay un ejemplo:
A la mayoría de los desarrolladores se les enseña a evitar Excepciones debido a la sobrecarga causada por lanzarlos. Es genial ser consciente de los recursos, pero generalmente no a expensas del diseño de su aplicación. Esa es probablemente la razón por la que le dijeron que no lanzara sus dos Excepciones. Si usar Excepciones o no, generalmente se reduce a la frecuencia con que ocurrirá la Excepción. Si es un resultado bastante común o bastante esperado, es cuando la mayoría de los desarrolladores evitarán las Excepciones y, en su lugar, crearán otro método para indicar la falla, debido al supuesto consumo de recursos.
Aquí hay un ejemplo de cómo evitar usar Excepciones en un escenario como el que acabamos de describir, usando el patrón Try ():
fuente
En mi opinión, la pregunta fundamental debería ser si uno esperaría que la persona que llama deseara continuar con el flujo normal del programa si ocurre una condición. Si no lo sabe, tenga métodos separados doSomething y trySomething, donde el primero devuelve un error y el segundo no, o tenga una rutina que acepte un parámetro para indicar si se debe lanzar una excepción si falla). Considere una clase para enviar comandos a un sistema remoto e informar respuestas. Ciertos comandos (por ejemplo, reiniciar) harán que el sistema remoto envíe una respuesta pero luego no responda durante un cierto período de tiempo. Por lo tanto, es útil poder enviar un comando "ping" y averiguar si el sistema remoto responde en un período de tiempo razonable sin tener que lanzar una excepción si no lo hace. t (la persona que llama probablemente esperaría que los primeros intentos de "ping" fallaran, pero uno eventualmente funcionaría). Por otro lado, si uno tiene una secuencia de comandos como:
uno desearía que el fracaso de cualquier operación anule la secuencia completa. Si bien se puede verificar cada operación para asegurarse de que tuvo éxito, es más útil que la rutina exchange_command () arroje una excepción si falla un comando.
En realidad, en el escenario anterior puede ser útil tener un parámetro para seleccionar varios modos de manejo de fallas: nunca arroje excepciones, arroje excepciones solo por errores de comunicación o arroje excepciones en los casos en que un comando no devuelva un "éxito" " indicación.
fuente
"PasswordNotCorrectException" no es un buen ejemplo para usar excepciones. Es de esperar que los usuarios tengan sus contraseñas incorrectas, por lo que no es una excepción en mi humilde opinión. Probablemente incluso se recupere de él, mostrando un buen mensaje de error, por lo que es solo una verificación de validez.
Las excepciones no controladas detendrán la ejecución eventualmente, lo cual es bueno. Si devuelve códigos falsos, nulos o de error, tendrá que lidiar con el estado del programa usted mismo. Si olvida verificar las condiciones en algún lugar, su programa puede seguir ejecutándose con datos incorrectos, y puede tener dificultades para averiguar qué sucedió y dónde .
Por supuesto, podría causar el mismo problema con las declaraciones catch vacías, pero al menos detectarlas es más fácil y no requiere que comprenda la lógica.
Entonces, como regla general:
Úselos donde no quiera o simplemente no pueda recuperarse de un error.
fuente
Puede usar algunas excepciones genéricas para esas condiciones. Por ejemplo, ArgumentException está destinado a usarse cuando algo sale mal con los parámetros de un método (con la excepción de ArgumentNullException). En general, no necesitará excepciones como LessThanZeroException, NotPrimeNumberException, etc. Piense en el usuario de su método. El número de condiciones que ella querrá manejar específicamente es igual al número del tipo de excepciones que su método necesita lanzar. De esta manera, puede determinar qué tan detalladas serán las excepciones.
Por cierto, siempre trate de proporcionar algunas formas para que los usuarios de sus bibliotecas eviten excepciones. TryParse es un buen ejemplo, existe para que no tenga que usar int.Parse y detectar una excepción. En su caso, es posible que desee proporcionar algunos métodos para verificar si el nombre de usuario es válido o la contraseña es correcta para que sus usuarios (o usted) no tengan que manejar muchas excepciones. Con suerte, esto dará como resultado un código más legible y un mejor rendimiento.
fuente
En última instancia, la decisión se reduce a si es más útil lidiar con errores de nivel de aplicación como este mediante el manejo de excepciones, o mediante su propio mecanismo de inicio, como devolver códigos de estado. No creo que haya una regla estricta sobre cuál es mejor, pero consideraría:
fuente
Hay dos clases principales de excepción:
1) Excepción del sistema (por ejemplo, conexión de base de datos perdida) o 2) Excepción del usuario. (por ejemplo, validación de entrada del usuario, 'la contraseña es incorrecta')
Me pareció útil crear mi propia clase de excepción de usuario y cuando quiero arrojar un error de usuario quiero que me manejen de manera diferente (es decir, se muestre un error de recursos al usuario), entonces todo lo que necesito hacer en mi controlador de errores principal es verificar el tipo de objeto :
fuente
Algunas cosas útiles para pensar al decidir si una excepción es apropiada:
qué nivel de código desea que se ejecute después de que se produzca el candidato de excepción, es decir, cuántas capas de la pila de llamadas deberían desenrollarse. En general, desea manejar una excepción lo más cerca posible de donde ocurre. Para la validación de nombre de usuario / contraseña, normalmente manejaría las fallas en el mismo bloque de código, en lugar de dejar que aparezca una excepción. Entonces, una excepción probablemente no sea apropiada. (OTOH, después de tres intentos fallidos de inicio de sesión, el flujo de control puede cambiar a otra parte, y una excepción puede ser apropiada aquí).
¿Es este evento algo que le gustaría ver en un registro de errores? No todas las excepciones se escriben en un registro de errores, pero es útil preguntar si esta entrada en un registro de errores sería útil, es decir, intentaría hacer algo al respecto o sería la basura que ignora.
fuente