java - ¿Por qué no debería envolver cada bloque en "intentar"-"atrapar"?
c++ exception (15)
Siempre he creído que si un método puede lanzar una excepción, es imprudente no proteger esta llamada con un bloque de intento significativo.
Acabo de publicar '' SIEMPRE deberías envolver las llamadas que pueden lanzar en try, catch blocks. ''a esta pregunta y me dijeron que era'' un consejo muy malo ''- me gustaría entender por qué.
Además de los consejos anteriores, personalmente uso un poco de try + catch + throw; por el siguiente motivo:
- En el límite de un codificador diferente, uso try + catch + throw en el código escrito por mí mismo, antes de que se lance la excepción a la persona que llama que está escrita por otros, esto me da la oportunidad de saber que ocurrió una condición de error en mi código, y este lugar está mucho más cerca del código que inicialmente lanzó la excepción, cuanto más cerca, más fácil es encontrar la razón.
- En el límite de los módulos, aunque se pueden escribir módulos diferentes para mi misma persona.
- Con el propósito de Learning + Debug, en este caso uso catch (...) en C ++ y catch (Exception ex) en C #, para C ++, la biblioteca estándar no lanza demasiadas excepciones, por lo que este caso es raro en C ++. Pero el lugar común en C #, C # tiene una gran biblioteca y una jerarquía de excepciones madura, el código de la biblioteca de C # arroja toneladas de excepción, en teoría I (y usted) debe conocer todas las excepciones de la función a la que llamó, y conocer la razón / el caso por qué estas excepciones se lanzan y saben cómo manejarlas (pasarlas o atraparlas y manejarlas en el lugar) con gracia. Desafortunadamente, en realidad es muy difícil saber todo sobre las posibles excepciones antes de escribir una línea de código. Así que capto todo y dejo que mi código hable en voz alta mediante el registro (en el entorno del producto) / el diálogo de confirmación (en el entorno de desarrollo) cuando realmente se produce una excepción. De esta manera agrego progresivamente el código de manejo de excepciones. Sé que está conflitido con un buen consejo, pero en realidad funciona para mí y no conozco ninguna manera mejor para este problema.
Como Mitch and others declararon, no debe detectar una excepción que no planea manejar de alguna manera. Debe considerar cómo la aplicación manejará las excepciones de manera sistemática 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 Es un DB bajo el capó en alguna parte.
Hay algunos olores de código relacionados que definitivamente desea evitar además del olor a "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 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 en el que está interesado y, voila, usted tiene registro y no hay una lógica innecesaria detry
/catch
."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 en una más genérica, probablemente tenga una capa de abstracción innecesaria ... y no diga que "podría necesitarla mañana" .
"Coger, limpiar, rehacer" : esta es una de mis manías. Si ve mucho de esto, entonces debe aplicar Técnicas de Adquisición de Recursos de Inicialización y colocar la parte de limpieza en el destructor de una instancia de objeto de portero .
Considero que el código que está lleno de bloques try
/ catch
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 una amœba y que tiene una gran necesidad de refactorización.
Como se indica en otras respuestas, solo debería 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 interrogador pregunta si es seguro ignorar las excepciones para un lexical_cast
de un número entero a una cadena. Tal reparto nunca debe fallar. Si falló, algo salió mal en el programa. ¿Qué podrías hacer para recuperarte en esa situación? Probablemente sea mejor dejar el programa muerto, 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.
Debido a que la siguiente pregunta es "He captado una excepción, ¿qué hago después?" ¿Qué harás? Si no hace nada, es un error que se oculta 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 capturado la excepción y solo detectará si lo sabe.
El consejo que me dio una vez mi profesor de ciencias de la computación fue: "Use los bloques Try y Catch solo cuando no sea posible manejar el error usando medios estándar".
Como ejemplo, nos dijo que si un programa se topa con un problema grave en un lugar donde no es posible hacer algo como:
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
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.
El mejor consejo que he escuchado es que solo debe detectar excepciones en los puntos en los que pueda hacer algo sensatamente sobre la condición excepcional, y que "capturar, registrar y liberar" no es una buena estrategia (si es inevitable en las bibliotecas).
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 es necesario 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 fragmento de código desarrollado actualmente, haciendo que la excepción resuelva el problema de otra persona o más adelante.
Ese problema crece dramáticamente en el desarrollo distribuido, donde es posible que deba llamar a un método implementado por un compañero de trabajo. Y luego tiene que inspeccionar una cadena anidada de llamadas de métodos para averiguar por qué le está lanzando alguna excepción, lo que podría haberse manejado mucho más fácilmente con el método anidado más profundo.
Herb Sutter escribió sobre este problema here . Seguro que vale la pena leerlo.
Un teaser:
"Escribir código de excepción segura es fundamentalmente sobre escribir ''intentar'' y ''atrapar'' en los lugares correctos". Discutir.
Dicho sin rodeos, esa afirmación refleja un malentendido fundamental de la seguridad de excepción. Las excepciones son solo otra forma de informe de errores, y ciertamente sabemos que escribir un código seguro contra 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 de excepción rara vez se trata de escribir ''try'' y ''catch'', y cuanto más raramente, mejor. Además, nunca olvide que la seguridad de excepción afecta el diseño de un código; nunca es solo una idea de último momento que se pueda adaptar con unas cuantas declaraciones de captura adicionales como para sazonar.
Me gustaría agregar a esta discusión que, dado que C ++ 11 , tiene mucho sentido, siempre que cada bloque catch
rethrow
la excepción hasta el punto en que pueda / deba manejarse. De esta manera se puede generar un retroceso . Por eso creo que las opiniones anteriores están en parte desactualizadas.
Utilice std::nested_exception
y std::throw_with_nested
Se describe en here y here cómo lograrlo.
Ya que puede hacer esto con cualquier clase de excepción derivada, ¡puede agregar mucha información a ese retroceso! También puede echar un vistazo a mi MWE en GitHub , donde un retroceso se vería así:
Library API: Exception caught in function ''api_function''
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
No es necesario que cubra cada bloque con try-catch porque un try-catch aún puede detectar excepciones no manejadas en funciones más abajo en la pila de llamadas. Entonces, en lugar de que cada función tenga un try-catch, puede tener una en la lógica de nivel superior de su aplicación. Por ejemplo, puede haber una rutina de nivel superior SaveDocument()
, que llama a muchos métodos que llaman a otros métodos, etc. Estos sub-métodos no necesitan sus propios intentos de captura, porque si se lanzan, todavía está atrapado por SaveDocument()
la captura.
Esto es bueno por tres razones: es útil porque tiene un solo lugar para informar un error: el SaveDocument()
bloque (es) catch (s) de SaveDocument()
). No hay necesidad de repetir esto en todos los sub-métodos, y es lo que quiere de todos modos: un solo lugar para dar 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 try-catch, si se lanza una excepción, SaveDocument()
bloque catch de ese método, la ejecución abandona la función y continúa a través de SaveDocument()
. Si algo ya salió mal, es probable que desee detenerse allí mismo.
Tres, todos tus sub-métodos pueden asumir que cada llamada tiene éxito . Si una llamada falla, 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;
}
Aquí es cómo se podría escribir con excepciones:
// these throw if failed, caught in SaveDocument''s catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();
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 difícil de escribir de otras maneras: no desea perder ninguna memoria si se lanza una excepción. Asegúrese de conocer los contenedores RAII , STL, punteros inteligentes y otros objetos que liberan sus recursos en los destructores, ya que los objetos siempre se destruyen antes que las excepciones.
No es necesario que cubra cada parte de su código dentro de try-catch
. El uso principal del bloque try-catch
es el manejo de errores y errores / excepciones en su programa. Algún uso de try-catch
-
- Puede usar este bloque donde desee manejar una excepción o simplemente puede decir que el bloque de código escrito puede generar una excepción.
- Si desea desechar sus objetos inmediatamente después de su uso, puede usar el bloque
try-catch
.
Si desea probar el resultado de cada función, use los códigos de retorno.
El propósito de las Excepciones es para que pueda probar los resultados MENOS con frecuencia. La idea es separar las condiciones excepcionales (inusuales y raras) de su código más común. Esto mantiene el código ordinario más limpio y simple, pero aún así es capaz de manejar esas condiciones excepcionales.
En un código bien diseñado, las funciones más profundas podrían arrojarse y las funciones más altas podrían captar. Pero la clave es que muchas funciones "intermedias" estarán exentas de la carga de manejar condiciones excepcionales. Solo tienen que ser "excepción segura", lo que no significa que deban ser atrapados.
Si desea solucionar fácilmente los problemas de producción intermitente, entonces debería ajustar cada bloque de código en un bloque try..catch. Básicamente, esto equipa el código con el objetivo de proporcionar una amplia información de depuración que le permite depurar sin un depurador en producción. Los usuarios no tienen que enviar correos electrónicos o chatear con asistencia, y toda la información necesaria para solucionar el problema se encuentra allí. No hay necesidad de reproducir problemas.
Para funcionar correctamente, debe combinarse con un registro extenso que pueda capturar el espacio de nombres / módulo, el nombre de la clase, el método, las entradas y el mensaje de error, y almacenarlos en una base de datos para que se pueda agregar y resaltar qué método falla más para poder hacerlo. arreglado primero
Las excepciones son de 100 a 1000 veces más lentas que el código normal y nunca deben volver a producirse. Tampoco cree una excepción y lancela. Esto es muy destrutivo. Las excepciones se capturan por lo que se puede arreglar con el código regular.
Esta técnica se usó para estabilizar rápidamente una aplicación de buggy en una compañía Fortune 500 desarrollada por 12 Devs durante 2 años. Usando esto, identifiqué, arreglé, construí pruebas e implementé 3000 arreglos en 4 meses, en cuyo caso el sistema ya no reportó ninguna excepción, ya que todos fueron manejados. Esto promedia una corrección cada 15 minutos en promedio durante 4 meses.
Si siempre maneja las excepciones de forma inmediata en el llamador de un método que puede generar una excepción, las excepciones se vuelven inútiles y es mejor que utilice códigos de error.
El punto central de las excepciones es que no es necesario manejarlas en todos los métodos de la cadena de llamadas.
Un método solo debería detectar una excepción cuando puede manejarlo de alguna manera sensata.
De lo contrario, avíselo, con la esperanza de que un método más alto en la pila de llamadas pueda darle sentido.
Como han señalado otros, es una buena práctica tener un controlador de excepciones no manejado (con registro) en el nivel más alto de la pila de llamadas para garantizar que se registren los errores fatales.