Siempre he creído que si un método puede arrojar una excepción, entonces es imprudente no proteger esta llamada con un bloque de prueba significativo.
Acabo de publicar ' SIEMPRE debe ajustar las llamadas que pueden intentar, atrapar bloques. "a esta pregunta y me dijeron que era un" consejo notablemente malo ". Me gustaría entender por qué.
Respuestas:
Un método solo debería detectar una excepción cuando puede manejarlo de alguna manera sensata.
De lo contrario, pásalo, con la esperanza de que un método más arriba en la pila de llamadas pueda tener sentido.
Como otros han señalado, es una buena práctica tener un controlador de excepciones no controlado (con registro) en el nivel más alto de la pila de llamadas para garantizar que se registren los errores fatales.
fuente
try
bloques. Hay una buena discusión en "C ++ más eficaz" de Scott Meyers.try
bloques son gratuitos en cualquier compilador de C moderno, esa información está fechada por Nick. Tampoco estoy de acuerdo con tener un controlador de excepciones de nivel superior porque pierdes información de la localidad (el lugar real donde falló la instrucción).terminate
) . Es más un mecanismo de seguridad. Además,try/catch
son más o menos gratuitos cuando no hay ninguna excepción. Cuando hay una que se propaga, consume tiempo cada vez que se arroja y se atrapa, por lo que una cadena detry/catch
ese solo lanzamiento no es gratuita.Como Mitch y otros declararon, no debe atrapar una excepción que no planea manejar de alguna manera. Debe considerar cómo la aplicación manejará sistemáticamente las excepciones cuando la esté diseñando. Esto generalmente lleva a tener capas de manejo de errores basadas en las abstracciones; por ejemplo, maneja todos los errores relacionados con SQL en su código de acceso a datos para que la parte de la aplicación que interactúa con objetos de dominio no esté expuesta al hecho de que es un DB debajo del capó en alguna parte.
Hay algunos olores de código relacionados que definitivamente desea evitar, además del olor de "atrapar todo en todas partes" .
"catch, log, rethrow" : si desea un registro basado en el alcance, escriba una clase que emita una declaración de registro en su destructor cuando la pila se desenrolla debido a una excepción (ala
std::uncaught_exception()
). Todo lo que necesita hacer es declarar una instancia de registro en el ámbito que le interesa y, voila, tiene un registro y no es innecesariotry
/catch
lógico."atrapar, lanzar traducido" : esto generalmente apunta a un problema de abstracción. A menos que esté implementando una solución federada en la que esté traduciendo varias excepciones específicas a una más genérica, probablemente tenga una capa innecesaria de abstracción ... y no diga que "podría necesitarla mañana" .
"atrapar, limpiar, volver a lanzar" : esta es una de mis manías. Si ve mucho de esto, debe aplicar las técnicas de Adquisición de recursos es Inicialización y colocar la parte de limpieza en el destructor de una instancia de objeto de conserje .
Considero que el código que está lleno de
try
/catch
blocks es un buen objetivo para la revisión y refactorización de código. Indica que el manejo de excepciones no se comprende bien o que el código se ha convertido en un amœba y que necesita una refactorización grave.fuente
Logger
clase similar a lalog4j.Logger
que incluye la ID del hilo en cada línea de registro y emití una advertencia en el destructor cuando una excepción estaba activa.Debido a que la siguiente pregunta es "He detectado una excepción, ¿qué hago después?" ¿Qué harás? Si no hace nada, se trata de un error de ocultación y el programa podría "simplemente no funcionar" sin ninguna posibilidad de encontrar lo que sucedió. Debes entender qué harás exactamente una vez que hayas detectado la excepción y solo si sabes.
fuente
No necesita cubrir cada bloque con try-catches porque un try-catch todavía puede detectar excepciones no controladas lanzadas en funciones más abajo en la pila de llamadas. Entonces, en lugar de que todas las funciones tengan un try-catch, puede tener uno en la lógica de nivel superior de su aplicación. Por ejemplo, puede haber una
SaveDocument()
rutina de nivel superior, que llama a muchos métodos que llaman a otros métodos, etc. Estos submétodos no necesitan sus propios intentos de captura, porque si arrojan, aún se captura porSaveDocument()
la captura.Esto es bueno por tres razones: es útil porque tiene un solo lugar para informar un error: los
SaveDocument()
bloques de captura. No es necesario repetir esto en todos los submétodos, y de todos modos es lo que desea: un solo lugar para darle al usuario un diagnóstico útil sobre algo que salió mal.Dos, el guardado se cancela cada vez que se lanza una excepción. Con cada sub-método de prueba atractivo, si se produce una excepción, se obtiene en el bloque de captura de ese método, las hojas de ejecución de la función, y se lleva a cabo a través
SaveDocument()
. Si algo ya salió mal, es probable que desee detenerse allí.Tres, todos sus submétodos pueden asumir que cada llamada tiene éxito . Si falla una llamada, la ejecución saltará al bloque catch y el código posterior nunca se ejecutará. Esto puede hacer que su código sea mucho más limpio. Por ejemplo, aquí hay códigos de error:
Así es como se podría escribir con excepciones:
Ahora está mucho más claro lo que está sucediendo.
Tenga en cuenta que el código seguro de excepción puede ser más complicado de escribir de otras maneras: no desea perder ninguna memoria si se produce una excepción. Asegúrese de conocer RAII , contenedores STL, punteros inteligentes y otros objetos que liberan sus recursos en destructores, ya que los objetos siempre se destruyen antes de las excepciones.
fuente
try
:catch
bloques que intentan marcar cada permutación ligeramente diferente de algún error con un mensaje ligeramente diferente, cuando en realidad todos deberían terminar de la misma manera: ¡falla de transacción o programa y salir! Si ocurre una falla digna de una excepción, apuesto a que la mayoría de los usuarios solo quieren recuperar lo que pueden o, al menos, quedarse solos sin tener que lidiar con 10 niveles de mensajes al respecto.Herb Sutter escribió sobre este problema aquí . Seguro que vale la pena leerlo.
Un adelanto:
fuente
Como se indicó en otras respuestas, solo debe detectar una excepción si puede hacer algún tipo de manejo de errores sensible para ello.
Por ejemplo, en la pregunta que generó su pregunta, el interlocutor pregunta si es seguro ignorar las excepciones
lexical_cast
de un número entero a una cadena. Tal elenco nunca debería fallar. Si falló, algo salió terriblemente mal en el programa. ¿Qué podrías hacer para recuperarte en esa situación? Probablemente sea mejor dejar que el programa muera, ya que se encuentra en un estado en el que no se puede confiar. Por lo tanto, no manejar la excepción puede ser lo más seguro.fuente
Si siempre maneja las excepciones inmediatamente en la llamada de un método que puede arrojar una excepción, entonces las excepciones se vuelven inútiles, y será mejor que use códigos de error.
Todo el punto de excepciones es que no necesitan ser manejados en todos los métodos en la cadena de llamadas.
fuente
El mejor consejo que he escuchado es que solo debería detectar excepciones en los puntos en los que puede hacer algo sensiblemente sobre la condición excepcional, y que "atrapar, registrar y liberar" no es una buena estrategia (si en ocasiones es inevitable en las bibliotecas).
fuente
Estoy de acuerdo con la dirección básica de su pregunta para manejar tantas excepciones como sea posible en el nivel más bajo.
Algunas de las respuestas existentes son como "No necesita manejar la excepción. Alguien más lo hará en la pila". Según mi experiencia, es una mala excusa para no pensar en el manejo de excepciones en el código actualmente desarrollado, lo que hace que el manejo de excepciones sea el problema de otra persona o posterior.
Ese problema crece dramáticamente en el desarrollo distribuido, donde es posible que necesite llamar a un método implementado por un compañero de trabajo. Y luego debe inspeccionar una cadena anidada de llamadas a métodos para averiguar por qué él / ella le está lanzando alguna excepción, que podría haberse manejado mucho más fácilmente en el método anidado más profundo.
fuente
El consejo que me dio una vez mi profesor de ciencias de la computación fue: "Use los bloques Try and Catch solo cuando no sea posible manejar el error usando medios estándar".
Como ejemplo, nos dijo que si un programa se topaba con un problema grave en un lugar donde no es posible hacer algo como:
Entonces deberías usar try, catch blocks. Si bien puede usar excepciones para manejar esto, generalmente no se recomienda porque las excepciones son costosas en cuanto al rendimiento.
fuente
Si desea probar el resultado de cada función, use códigos de retorno.
El propósito de Excepciones es para que pueda probar los resultados MENOS a menudo. La idea es separar las condiciones excepcionales (inusuales, más raras) de su código más común. Esto mantiene el código ordinario más limpio y simple, pero aún así puede manejar esas condiciones excepcionales.
En un código bien diseñado, podrían lanzar funciones más profundas y podrían capturarse funciones más altas. Pero la clave es que muchas funciones "intermedias" estarán libres de la carga de manejar condiciones excepcionales. Solo tienen que estar "a salvo de excepciones", lo que no significa que deben atraparlos.
fuente
Me gustaría agregar a esta discusión que, desde C ++ 11 , tiene mucho sentido, siempre y cuando cada
catch
bloque searethrow
la excepción hasta el punto en que pueda / deba manejarse. De esta forma, se puede generar una traza inversa . Por lo tanto, creo que las opiniones anteriores están en parte desactualizadas.Uso
std::nested_exception
ystd::throw_with_nested
En StackOverflow se describe aquí y aquí cómo lograr esto.
Como puede hacer esto con cualquier clase de excepción derivada, ¡puede agregar mucha información a dicha traza inversa! También puede echar un vistazo a mi MWE en GitHub , donde una traza inversa se vería así:
fuente
Me dieron la "oportunidad" de rescatar varios proyectos y los ejecutivos reemplazaron a todo el equipo de desarrollo porque la aplicación tenía demasiados errores y los usuarios estaban cansados de los problemas y los problemas. Todas estas bases de código tenían un manejo centralizado de errores en el nivel de la aplicación como se describe en la respuesta más votada. Si esa respuesta es la mejor práctica, ¿por qué no funcionó y permitió que el equipo de desarrollo anterior resolviera problemas? ¿Quizás a veces no funciona? Las respuestas anteriores no mencionan cuánto tiempo pasan los desarrolladores arreglando problemas individuales. Si el tiempo para resolver los problemas es la métrica clave, la mejor práctica es instrumentar el código con bloques try..catch.
¿Cómo solucionó mi equipo los problemas sin cambiar significativamente la interfaz de usuario? Simple, cada método se instrumentó con try..catch bloqueado y todo se registró en el punto de falla con el nombre del método, los valores de los parámetros del método concatenados en una cadena que se pasa junto con el mensaje de error, el mensaje de error, el nombre de la aplicación, la fecha, y versión Con esta información, los desarrolladores pueden ejecutar análisis sobre los errores para identificar la excepción que ocurre más. O el espacio de nombres con el mayor número de errores. También puede validar que un error que ocurre en un módulo se maneja adecuadamente y no es causado por múltiples razones.
Otro beneficio profesional de esto es que los desarrolladores pueden establecer un punto de interrupción en el método de registro de errores y con un punto de interrupción y un solo clic en el botón de depuración "salir", están en el método que falló con acceso completo al acceso real objetos en el punto de falla, convenientemente disponibles en la ventana inmediata. Hace que sea muy fácil depurar y permite arrastrar la ejecución al inicio del método para duplicar el problema y encontrar la línea exacta. ¿El manejo centralizado de excepciones permite a un desarrollador replicar una excepción en 30 segundos? No.
La afirmación "Un método solo debería detectar una excepción cuando puede manejarlo de una manera sensata". Esto implica que los desarrolladores pueden predecir o encontrarán todos los errores que puedan ocurrir antes del lanzamiento. Si esto fuera cierto en un nivel superior, el controlador de excepciones de la aplicación no sería necesario y no habría mercado para Elastic Search y logstash.
¡Este enfoque también permite a los desarrolladores encontrar y solucionar problemas intermitentes en la producción! ¿Desea depurar sin un depurador en producción? ¿O prefieres recibir llamadas y recibir correos electrónicos de usuarios molestos? Esto le permite solucionar problemas antes de que nadie más lo sepa y sin tener que enviar un correo electrónico, mensajería instantánea o Slack con soporte, ya que todo lo necesario para solucionar el problema está ahí. El 95% de los temas nunca necesitan ser reproducidos.
Para funcionar correctamente, debe combinarse con el registro centralizado que puede capturar el espacio de nombres / módulo, el nombre de clase, el método, las entradas y el mensaje de error y almacenarlos en una base de datos para que pueda agregarse y resaltar qué método falla más para que pueda ser arreglado primero.
A veces, los desarrolladores eligen lanzar excepciones desde un bloque catch, pero este enfoque es 100 veces más lento que el código normal que no arroja. Se prefiere la captura y liberación con registro.
Esta técnica se utilizó para estabilizar rápidamente una aplicación que fallaba cada hora para la mayoría de los usuarios en una compañía Fortune 500 desarrollada por 12 desarrolladores durante 2 años. Con esto, se identificaron, arreglaron, probaron y desplegaron 3000 excepciones diferentes en 4 meses. Esto promedia una solución cada 15 minutos en promedio durante 4 meses.
Estoy de acuerdo en que no es divertido escribir todo lo necesario para instrumentar el código y prefiero no mirar el código repetitivo, pero a la larga vale la pena agregar 4 líneas de código a cada método.
fuente
Además del consejo anterior, personalmente uso algunos try + catch + throw; por la siguiente razón:
fuente
Me siento obligado a agregar otra respuesta, aunque la respuesta de Mike Wheat resume bastante bien los puntos principales. Lo pienso así. Cuando tienes métodos que hacen varias cosas, multiplicas la complejidad, no la agregas.
En otras palabras, un método que está envuelto en un intento de captura tiene dos resultados posibles. Tiene el resultado sin excepción y el resultado con excepción. Cuando se trata con muchos métodos, esto explota exponencialmente más allá de la comprensión.
Exponencialmente, porque si cada método se ramifica de dos maneras diferentes, cada vez que llame a otro método, estará cuadrando el número anterior de resultados potenciales. Para cuando haya llamado a cinco métodos, tiene hasta 256 resultados posibles como mínimo. Compare esto con no hacer una prueba / captura en cada método y solo tiene un camino a seguir.
Así es básicamente como lo veo. Puede sentirse tentado a argumentar que cualquier tipo de ramificación hace lo mismo, pero try / catches es un caso especial porque el estado de la aplicación básicamente se vuelve indefinido.
En resumen, try / catch hace que el código sea mucho más difícil de comprender.
fuente
No tiene necesidad de ocultar cada parte de su código dentro
try-catch
. El uso principal deltry-catch
bloque es el manejo de errores y errores / excepciones en su programa. Algún uso detry-catch
-try-catch
bloqueo.fuente
try
/catch
está completamente separado / ortogonal de eso. Si desea deshacerse de los objetos en un ámbito más pequeño, puede abrir uno nuevo{ Block likeThis; /* <- that object is destroyed here -> */ }
; no es necesario que lo envuelvatry
/ acatch
menos que realmente necesitecatch
algo, por supuesto.