design - ¿Las excepciones son realmente para errores excepcionales?
language-agnostic exception (15)
Tengo entendido que la sabiduría común dice que solo use excepciones para condiciones verdaderamente excepcionales (de hecho, he visto esa declaración aquí en SO varias veces).
Sin embargo, Krzysztof Cwalina dice:
Una de las ideas erróneas más grandes sobre las excepciones es que son para "condiciones excepcionales". La realidad es que son para comunicar las condiciones de error. Desde una perspectiva de diseño del marco, no existe una "condición excepcional". Si una condición es excepcional o no depende del contexto de uso, pero las bibliotecas reutilizables raramente saben cómo se usarán. Por ejemplo, OutOfMemoryException podría ser excepcional para una aplicación de entrada de datos simple; no es tan excepcional para las aplicaciones que hacen su propia gestión de memoria (por ejemplo, servidor SQL). En otras palabras, la condición excepcional de un hombre es la condición crónica de otro hombre.
Luego también dice que las excepciones deberían usarse para:
- Errores de uso
- Errores de programa
- Fallas del sistema
Teniendo en cuenta que Krzysztof Cwalina es el primer ministro para el equipo de CLR en MS, pregunto: ¿qué piensas de su declaración?
Aquí está la definición de excepción: una excepción es un evento, que ocurre durante la ejecución de un programa, que interrumpe el flujo normal de las instrucciones del programa.
Por lo tanto, para responder a su pregunta, no. Las excepciones son para eventos disruptivos , que pueden o no ser excepcionales . Me encanta esta definición, es simple y funciona siempre, si se aceptan excepciones como yo. Por ejemplo, un usuario envía un un / pw incorrecto, o tiene un argumento ilegal / entrada de usuario incorrecta. Lanzar una excepción aquí es la forma más directa de resolver estos problemas, que son perjudiciales, pero no excepcionales, ni siquiera imprevistos.
Probablemente deberían haber sido llamados interrupciones , pero ese barco ha navegado.
Ciertamente creo que las excepciones deben usarse solo si tienes una condición excepcional.
El problema está en la definición de "excepcional". Aquí esta el mio:
Una condición es excepcional si está fuera del comportamiento normal asumido de la parte del sistema que plantea la excepción.
Esto tiene algunas implicaciones:
- Excepcional depende de tus suposiciones . Si una función asume que se pasan parámetros válidos, entonces lanzar una IllegalArgumentException está bien. Sin embargo, si el contrato de una función dice que corregirá los errores de entrada en la entrada de alguna manera, entonces este uso es "normal" y no debería arrojar una excepción en un error de entrada.
- Excepcional depende de la estratificación del subsistema . Una función IO de red ciertamente podría generar una excepción si la red no está commentada, ya que supone una conexión válida. Sin embargo, se esperaría que un intermediario de mensajes basado en ESB manejara las conexiones descartadas, por lo que si usaba internamente una función IO de la red, tendría que detectar y manejar el error de manera apropiada. En caso de que no sea obvio, try / catch es efectivamente equivalente a un subsistema que dice "una condición que es excepcional para uno de mis componentes es realmente considerada normal por mí, así que debo manejarla".
Creo que cuanto más cerca del suelo está usted son las excepciones menos apropiadas como un medio de comunicación de error. En una abstracción más alta, como en Java o .net, una excepción puede constituir una forma elegante de transmitir mensajes de error a las personas que llaman. Sin embargo, este no es el caso en C. Esta es también una decisión de diseño de framework versus api.
Creo que hay un par de buenas razones por las cuales las excepciones deberían usarse para detectar problemas inesperados.
En primer lugar, crean un objeto para encapsular la excepción, que por definición debe hacer que sea mucho más costoso que procesar un enunciado if simple. Como ejemplo de Java, debe llamar a File.exists () en lugar de esperar y manejar rutinariamente una excepción FileNotFoundException.
En segundo lugar, las excepciones que quedan atrapadas fuera del método actual (o tal vez incluso de clase) hacen que el código sea mucho más difícil de leer que si el manejo estuviera incluido en el único método.
Habiendo dicho eso, personalmente me encantan las excepciones. Le eximen de la necesidad de manejar explícitamente todos esos errores tipo "may-happen-but-probably-never-will", que hacen que escriba repetidamente print-an-error-and-abort-on-non-zero-return- manejo de código de cada llamada de método.
Mi conclusión es ... si razonablemente puede esperar que suceda, entonces es parte de su aplicación y debe codificarla. Cualquier otra cosa es una excepción.
Dado que normalmente programo en Python, y en ese lenguaje las excepciones están en todas partes, para mí una excepción puede representar desde un error del sistema hasta una condición completamente legítima.
Por ejemplo, la forma "pitónica" de comprobar si una cadena contiene un número entero es probar int (theString) y ver si genera una excepción. ¿Es eso un "error excepcional"?
Nuevamente, en Python siempre se piensa que el bucle for actúa sobre un iterador, y un iterador debe lanzar una excepción ''StopIteration'' cuando finaliza su trabajo (el bucle for capta esa excepción). ¿Es eso "excepcional" de alguna manera?
De hecho, es difícil saber qué es exactamente lo que interpreta una "condición excepcional" que justifica el uso de una excepción en un programa.
Una instancia que es muy útil para usar al comunicar la causa de los errores . Como la cita de Krzysztof Cwalina menciona:
Una de las ideas erróneas más grandes sobre las excepciones es que son para "condiciones excepcionales". La realidad es que son para comunicar las condiciones de error.
Para dar un ejemplo concreto, digamos que tenemos un getHeader(File f)
que lee un encabezado de un archivo y devuelve un objeto FileHeader
.
Puede haber varios problemas que pueden surgir al tratar de leer datos de un disco. Quizás el archivo especificado no existe, el archivo contiene datos que no se pueden leer, errores inesperados de acceso al disco, falta de memoria, etc. Tener múltiples medios de falla significa que debe haber múltiples formas de informar qué salió mal.
Si no se usaron excepciones, pero existía la necesidad de comunicar el tipo de error que ocurrió, con la firma del método actual, lo mejor que podemos hacer es devolver un null
. Como obtener un null
no es muy informativo, la mejor comunicación que recibimos de ese resultado es que "ocurrió algún tipo de error, por lo que no pudimos continuar, lo siento". - No comunica la causa del error.
(O, como alternativa, podemos tener constantes de clase para objetos FileHeader que indiquen condiciones FileNotFound y similares, emulando códigos de error, pero que realmente apesta de tener un tipo booleano con TRUE
, FALSE
, FILE_NOT_FOUND
).
Si FileNotFound
una excepción FileNotFound
o DeviceNotReady
(hipotética), al menos sabemos cuál fue el origen del error, y si se DeviceNotReady
una aplicación de usuario final, podríamos manejar el error para resolver el problema.
El uso del mecanismo de excepción proporciona un medio de comunicación que no requiere un respaldo al uso de códigos de error para la notificación de condiciones que no están dentro del flujo normal de ejecución.
Sin embargo, eso no significa que todo deba ser manejado por excepciones. Como lo señala S.Lott :
No use excepciones para validar la entrada del usuario, por ejemplo. La gente comete errores todo el tiempo. Para eso son las declaraciones.
Eso es algo que no se puede enfatizar lo suficiente. Uno de los peligros de no saber cuándo utilizar exactamente las excepciones es la tendencia a ser feliz por la excepción; usando excepciones donde la validación de entrada sería suficiente.
Realmente no tiene sentido definir y lanzar una excepción InvalidUserInput
cuando todo lo que se requiere para manejar una situación así es notificar al usuario de lo que se espera como entrada.
Además, debe tenerse en cuenta que se espera que la entrada del usuario tenga una entrada defectuosa en algún momento. Es una medida defensiva para validar los datos de entrada antes de enviar las entradas del mundo exterior a las partes internas del programa.
Es un poco difícil decidir qué es excepcional y qué no.
De lo que se trata es de qué herramienta se necesita para hacer el trabajo.
Las excepciones son una herramienta muy poderosa. Antes de usarlos, pregúnteles si necesitan este poder y la complejidad que conlleva.
Las excepciones pueden parecer simples, porque sabes que cuando se golpea la línea con la excepción, todo se detiene. ¿Qué pasa desde aquí?
¿Se producirá una excepción no detectada?
¿Se detectará la excepción mediante el manejo global de errores?
¿Se manejará la excepción mediante un manejo de errores más anidado y detallado?
Debes saber todo en la pila para saber qué hará esa excepción. Esto viola el concepto de independencia. Ese método ahora depende del manejo de errores para hacer lo que usted espera.
Si tengo un método, no me importa lo que esté fuera de ese método. Solo me debería importar cuál es la entrada, cómo procesarla y cómo devolver la respuesta.
Cuando utilizas una excepción, básicamente dices: "No me importa lo que pase desde aquí", "algo salió mal" y "no quiero que empeore". Haz lo que sea necesario para mitigar el problema.
Ahora, si le importa cómo manejar el error, pensará un poco más y lo incorporará a la interfaz del método; por ejemplo, si está intentando encontrar algún objeto, posiblemente devuelva el valor predeterminado de ese objeto si no se puede encontrar uno. que lanzar una excepción como "Objeto no encontrado".
Cuando crea el manejo de errores en la interfaz de métodos, la firma de ese método no solo es más descriptiva de lo que puede hacer, sino que impone la responsabilidad de cómo manejar el error en la persona que llama del método. El método del que llama puede ser capaz de resolverlo o no, y de lo contrario informaría de nuevo en la cadena. Eventualmente llegarás al punto de entrada de la aplicación. Ahora sería apropiado emitir una excepción, ya que es mejor que comprenda cómo se manejarán las excepciones si trabaja con la interfaz pública de las aplicaciones.
Déjame darte un ejemplo de mi manejo de errores para un servicio web.
Nivel 1. Manejo global de errores en global.asax: esa es la red de seguridad para evitar excepciones no detectadas. Esto nunca debería ser intencionalmente alcanzado.
Nivel 2. Método de servicio web - Envuelto en un try / catch para garantizar que siempre cumpla con su interfaz json.
Nivel 3. Métodos de trabajo: estos obtienen datos, lo procesan y lo devuelven sin formato al método del servicio web.
En los métodos de trabajador no es correcto lanzar una excepción. Sí, tengo el manejo de errores del método de servicio web anidado, pero ese método se puede usar en otros lugares donde esto puede no existir.
En cambio, si se usa un método de trabajador para obtener un registro y no se puede encontrar el registro, simplemente devuelve nulo. El método del servicio web verifica la respuesta y cuando lo encuentra nulo, sabe que no puede continuar. El método de servicio web sabe que tiene un manejo de error para devolver json, por lo que lanzar una excepción simplemente devolverá los detalles en términos de lo sucedido. Desde la perspectiva de un cliente, es genial que se haya empaquetado en json que se pueda analizar fácilmente.
Ves que cada pieza solo sabe lo que tiene que hacer y lo hace. Cuando lanza una excepción en la mezcla, secuestra el flujo de aplicaciones. Esto no solo lleva a un código difícil de seguir, sino que la respuesta al abuso de excepciones es el try / catch. Ahora es más probable que abuse de otra herramienta muy poderosa.
Con demasiada frecuencia veo un try / catch atrapando todo en medio de una aplicación, porque el desarrollador se asustó de que el método que usan sea más complejo de lo que parece.
El dicho de que las excepciones se deben usar para circunstancias excepcionales se usa en "Effective Java Second Edition": uno de los mejores libros de Java.
El problema es que esto se saca de contexto. Cuando el autor afirma que las excepciones deben ser excepcionales, acaba de mostrar un ejemplo de uso de excepciones para finalizar un ciclo while, una mala excepción. Citar:
las excepciones son, como su nombre lo indica, para ser usadas solo por condiciones excepcionales; nunca deben usarse para el flujo de control ordinario.
Entonces todo depende de su definición de "condición de excepción". Tomado fuera de contexto, puede dar a entender que rara vez se debe utilizar.
Usar excepciones en lugar de devolver códigos de error es bueno, aunque usarlas para implementar una técnica "inteligente" o "más rápida" no es bueno. Eso es generalmente lo que se entiende por "condición excepcional".
Excepción comprobada: errores menores que no son errores y no deben detener la ejecución. ex. IO o análisis de archivos Excepción no verificada - programación de "error" que desobedece un contrato de método - ej. OutOfBoundsException. O un error que hace que la continuación de la ejecución sea una muy mala idea - ex IO o análisis de archivos de un archivo muy importante. Quizás un archivo de configuración.
Esto suena demasiado simplista, pero creo que tiene sentido simplemente usar excepciones donde sean apropiadas. En idiomas como Java y Python, las excepciones son muy comunes, especialmente en ciertas situaciones. Las excepciones son apropiadas para el tipo de error que desea crear a través de una ruta de código y obligar al desarrollador a atrapar explícitamente. En mi propia codificación, considero el momento adecuado para agregar una excepción cuando no se puede ignorar el error, o simplemente es más elegante lanzar una excepción en lugar de devolver un valor de error a una llamada de función, etc.
Algunos de los lugares más apropiados para excepciones que puedo pensar de forma directa:
- NotImplementedException : forma muy apropiada de designar que un método o función particular no está disponible, en lugar de simplemente regresar sin hacer nada.
- Excepciones de OutOfMemory : es difícil imaginar una forma mejor de manejar este tipo de error, ya que representa una falla de asignación de memoria en todo el sistema o en todo el sistema operativo. Esto es esencial para tratar, por supuesto!
- NullPointerException - El acceso a una variable nula es un error del programador, y la OMI es otro buen lugar para forzar la salida de un error a la superficie
- ArrayIndexException : en un lenguaje implacable como C, los desbordamientos de búfer son desastrosos. Los lenguajes más nítidos pueden devolver un valor nulo de algún tipo, o en algunas implementaciones, incluso envolver el conjunto. En mi opinión, lanzar una excepción es una respuesta mucho más elegante.
Esto de ninguna manera es una lista completa, pero con suerte ilustra el punto. Use excepciones donde sean elegantes y lógicas. Como siempre con la programación, la herramienta correcta para el trabajo correcto es un buen consejo. No tiene sentido volverse loco por la locura en vano, pero tampoco es prudente ignorar por completo una herramienta potente y elegante a tu disposición.
KCwalina tiene un punto. Será bueno identificar casos en los que el código fallará (hasta un límite)
Estoy de acuerdo con S.Lott en que a veces validar es mejor que lanzar Excepción. Una vez dicho esto, OutOfMemory no es lo que cabría esperar en su aplicación (a menos que esté asignando una memoria grande y necesita memoria para seguir adelante).
Creo que depende del dominio de la aplicación.
La declaración de Krzysztof Cwalina es un poco engañosa. La declaración original se refiere a "condiciones excepcionales", para mí es natural que yo sea el que defina lo que es excepcional o no. Sin embargo, creo que el mensaje pasó por OK, ya que creo que todos estamos hablando de excepciones de ''desarrollador''.
Las excepciones son excelentes para la comunicación, pero con un pequeño diseño de jerarquía también son excelentes para cierta separación de preocupaciones, especialmente entre capas (DAO, Business, etc.). Por supuesto, esto solo es útil si trata estas excepciones de manera diferente.
Un buen ejemplo de jerarquía es la jerarquía de excepciones de acceso a datos de primavera.
Me he estado preguntando sobre esto yo mismo. ¿Qué queremos decir con "excepcional"? Tal vez no haya una definición estricta, pero ¿hay alguna regla de oro que podamos usar para decidir qué es excepcional en un contexto dado?
Por ejemplo, ¿sería justo decir que una condición "excepcional" es aquella que viola el contrato de una función?
Para las personas que escriben marcos, quizás sea interesante.
Para el resto de nosotros, es confuso (y posiblemente inútil). Para las aplicaciones comunes, las excepciones deben reservarse como situaciones "excepcionales". Las excepciones interrumpen la presentación secuencial ordinaria de su programa.
Debe ser cauteloso acerca de romper el procesamiento secuencial ordinario de arriba hacia abajo de su programa. El manejo de excepciones es, intencionadamente, difícil de leer. Por lo tanto, reserve excepciones para cosas que están fuera de los escenarios estándar.
Ejemplo: No use excepciones para validar la entrada del usuario. Las personas cometen errores de entrada todo el tiempo. Eso no es excepcional, es por eso que escribimos software. Para eso son las declaraciones.
Cuando su aplicación obtiene una excepción de OutOfMemory, no tiene sentido atraparla. Eso es excepcional. La suposición de "ejecución secuencial" está fuera de la ventana. Su aplicación está condenada al fracaso, solo se cuelga y espera que su transacción RDBMS finalice antes de que falle.
Si practicas "dile, no preguntes", entonces una excepción es la forma en que un programa dice "no puedo hacer eso". Es "excepcional" en el sentido de que dices "do X" y no puede hacer X. Una simple situación de manejo de errores. En algunos idiomas es bastante común trabajar de esta manera, en Java y C ++ las personas tienen otras opiniones porque las excepciones se vuelven bastante costosas.
General: excepción solo significa "no puedo"
Pragmático: ... si puede permitirse trabajar de esa manera en su idioma.
Ciudadanía: ... y su equipo lo permite.
Yo pienso que él tiene razón. Eche un vistazo al análisis de números en java. Ni siquiera puedes verificar la cadena de entrada antes de analizar. Te obligan a analizar y recuperar NFE si algo salió mal. ¿El fallo de análisis es algo excepcional? Creo que no.