¿Por qué no debería envolver cada bloque en "try"-"catch"?
Siempre he creído que si un método puede generar una excepción, entonces es imprudente no proteger esta llamada con un bloque try significativo.
Acabo de publicar ' SIEMPRE debes cerrar las llamadas que pueden generar bloques de intento y captura'. ' a esta pregunta y me dijeron que era 'un consejo notablemente malo'; me gustaría entender por qué.
Un método sólo debería detectar una excepción cuando pueda manejarla de alguna manera sensata.
De lo contrario, páselo, con la esperanza de que un método más arriba en la pila de llamadas pueda entenderlo.
Como han señalado otros, 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.
Como dijeron Mitch y otros , no debería detectar una excepción que no planee 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, usted 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 los objetos del dominio no esté expuesta al hecho de que hay Hay un DB debajo del capó en alguna parte.
Hay algunos olores de código relacionados que definitivamente desea evitar además del olor a "captar todo en todas partes" .
"capturar, registrar, volver a lanzar" : si desea un registro basado en un ámbito, escriba una clase que emita una declaración de registro en su destructor cuando la pila se esté desarrollando 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, listo, tiene registro y no hay lógicatry
/ innecesariacatch
."capturar, lanzar traducido" : esto suele indicar un problema de abstracción. A menos que esté implementando una solución federada en la que esté traduciendo varias excepciones específicas en una más genérica, probablemente tenga una capa innecesaria de abstracción... y no diga "podría necesitarla mañana" .
"atrapar, limpiar, volver a lanzar" : esta es una de mis cosas que me molestan. Si ve mucho de esto, entonces debe aplicar técnicas de Adquisición de Recursos e Inicialización y colocar la parte de limpieza en el destructor de una instancia de objeto conserje .
Considero que el código lleno de bloques try
/ catch
es un buen objetivo para la revisión y refactorización del código. Indica que no se comprende bien el manejo de excepciones o que el código se ha convertido en una ameba y necesita urgentemente una refactorización.
Porque la siguiente pregunta es "He detectado una excepción, ¿qué hago a continuación?" ¿Qué vas a hacer? Si no hace nada, se trata de un error oculto y el programa podría "simplemente no funcionar" sin ninguna posibilidad de encontrar lo que sucedió. Debe comprender qué hará exactamente una vez que haya detectado la excepción y solo detectarla si lo sabe.
No es necesario cubrir cada bloque con try-catch porque un try-catch aún puede detectar excepciones no controladas lanzadas en funciones más abajo en la pila de llamadas. Entonces, en lugar de que cada función tenga un try-catch, puede tener uno en la lógica de nivel superior de su aplicación. Por ejemplo, podría 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 try-catches, porque si los lanzan, todavía serán capturados por ' SaveDocument()
s catch.
Esto es bueno por tres razones: es útil porque tiene un solo lugar para informar un error: los SaveDocument()
bloques catch. No es necesario repetir esto en todos los submétodos y, de todos modos, es lo que desea: un solo lugar para brindarle al usuario un diagnóstico útil sobre algo que salió mal.
Segundo, el guardado se cancela cada vez que se lanza una excepción. Con cada submétodo try-catcher, si se lanza una excepción, ingresa al bloque catch de ese método, la ejecución abandona la función y continúaSaveDocument()
. Si algo ya salió mal, probablemente quieras detenerte ahí mismo.
Tres, todos sus submétodos pueden asumir que cada llamada se realiza correctamente . 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:
int ret = SaveFirstSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
ret = SaveSecondSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
ret = SaveThirdSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
Así es como se podría escribir eso con excepciones:
// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();
Ahora está mucho más claro lo que está pasando.
Tenga en cuenta que el código seguro de excepción puede ser más complicado de escribir de otras maneras: no desea perder 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 que las excepciones.
Herb Sutter escribió sobre este problema aquí . Seguro que vale la pena leerlo.
Un adelanto:
"Escribir código seguro para excepciones consiste fundamentalmente en escribir 'intentar' y 'capturar' en los lugares correctos". Conversar.
Dicho sin rodeos, esa afirmación refleja un malentendido fundamental sobre la seguridad de las excepciones. Las excepciones son solo otra forma de informe de errores, y ciertamente sabemos que escribir código a prueba de errores no se trata solo de dónde verificar los códigos de retorno y manejar las condiciones de error.
En realidad, resulta que la seguridad excepcional rara vez se trata de escribir "intentar" y "capturar", y cuanto menos, mejor. Además, nunca olvide que la seguridad de las excepciones afecta el diseño de una parte del código; nunca es una ocurrencia de último momento que pueda complementarse con algunas declaraciones adicionales como si fuera un condimento.