exception - tipos - try catch
¿Por qué el manejo de excepciones es malo? (15)
El lenguaje Go de Google no tiene excepciones como una opción de diseño, y la fama de Linus of Linux ha llamado a la mierda de las excepciones. ¿Por qué?
Bien, aburrida respuesta aquí. Supongo que depende del idioma realmente. Cuando una excepción puede dejar recursos asignados, deben evitarse. En los lenguajes de scripting, simplemente abandonan o saltan en exceso partes del flujo de la aplicación. Eso es desagradable en sí mismo, pero escapar de los errores casi fatales con excepciones es una idea aceptable.
Para la señalización de error generalmente prefiero señales de error. Todo depende de la API, caso de uso y gravedad, o si el registro es suficiente. También estoy tratando de redefinir el comportamiento y throw Phonebooks()
lugar. La idea es que las "Excepciones" son a menudo callejones sin salida, pero una "Guía telefónica" contiene información útil sobre recuperación de errores o rutas alternativas de ejecución. (Aún no se ha encontrado un buen caso de uso, pero sigue intentándolo).
Desde la perspectiva de Golang, supongo que no tener manejo de excepciones mantiene el proceso de compilación simple y seguro.
Desde la perspectiva de Linus, entiendo que el código del núcleo es TODO sobre casos de esquina. Entonces tiene sentido rechazar excepciones.
Las excepciones tienen sentido en el código si está bien dejar caer la tarea actual en el piso, y donde el código de caso común tiene más importancia que el manejo de errores. Pero requieren generación de código del compilador.
Por ejemplo, están bien en la mayoría de los códigos de usuario de alto nivel, como el código de la aplicación web y de escritorio.
El paradigma de manejo de excepciones de C ++, que forma una base parcial para Java, y a su vez .net, presenta algunos buenos conceptos, pero también tiene algunas limitaciones severas. Una de las intenciones clave de diseño del manejo de excepciones es permitir que los métodos garanticen que satisfarán sus condiciones posteriores o lanzarán una excepción, y también garantizarán que cualquier limpieza que deba suceder antes de que un método pueda finalizar. Desafortunadamente, los paradigmas de manejo de excepciones de C ++, Java y .net no proporcionan ningún medio adecuado para manejar la situación donde factores inesperados impiden que se realice la limpieza esperada. Esto a su vez significa que uno debe arriesgarse a que todo se frene bruscamente si ocurre algo inesperado (el enfoque de C ++ para manejar una excepción ocurre durante el desenrollado de la pila), acepte la posibilidad de que una condición no se pueda resolver debido a un problema que ocurrió durante la limpieza de desenrollado de pila se confundirá con uno que puede resolverse (y podría haber sido, si la limpieza hubiera tenido éxito), o aceptar la posibilidad de que un problema irresoluble cuya limpieza desenrollar desencadena una excepción que normalmente podría resolverse desapercibido como código que maneja el último problema lo declara "resuelto".
Incluso si el manejo de excepciones generalmente fuera bueno, no es irrazonable considerar como inaceptable un paradigma de manejo de excepciones que no proporciona un buen medio para manejar problemas que ocurren cuando se limpia después de otros problemas. Esto no quiere decir que un marco no pueda diseñarse con un paradigma de manejo de excepciones que pueda garantizar un comportamiento sensato incluso en escenarios de fallas múltiples, pero ninguno de los principales lenguajes o marcos puede hacerlo todavía.
En teoría, son realmente malos. En el mundo matemático perfecto no puedes obtener situaciones de excepción. Mire los lenguajes funcionales, no tienen efectos secundarios, por lo que virtualmente no tienen una fuente para situaciones excepcionales.
Pero, la realidad es otra historia. Siempre tenemos situaciones que son "inesperadas". Es por eso que necesitamos excepciones.
Creo que podemos pensar en excepciones como azúcar de sintaxis para ExceptionSituationObserver. Usted acaba de recibir notificaciones de excepciones. Nada mas.
Con Go, creo que presentarán algo que tratará situaciones "inesperadas". Adivino que tratarán de hacer que suene menos destructivo como excepciones y más como lógica de la aplicación. Pero esto es solo mi suposición.
La razón por la que Go no tiene excepciones se explica en las preguntas frecuentes de diseño de Go Language:
Las excepciones son una historia similar. Se han propuesto varios diseños para excepciones, pero cada uno agrega una complejidad significativa al lenguaje y al tiempo de ejecución. Por su propia naturaleza, las excepciones abarcan funciones y tal vez incluso rutinas; tienen amplias implicaciones. También existe preocupación sobre el efecto que tendrían en las bibliotecas. Son, por definición, excepcionales, pero la experiencia con otros lenguajes que los respaldan muestran que tienen un profundo efecto en la especificación de la biblioteca y la interfaz. Sería agradable encontrar un diseño que les permita ser verdaderamente excepcionales sin alentar errores comunes para convertirlos en un flujo de control especial que requiere que cada programador lo compense.
Al igual que los genéricos, las excepciones siguen siendo un problema abierto.
En otras palabras, aún no han descubierto cómo admitir las excepciones en Go de una manera que consideren satisfactoria. No dicen que las excepciones son malas per se ;
ACTUALIZACIÓN - mayo de 2012
Los diseñadores de Go ahora han bajado de la valla. Sus preguntas frecuentes ahora dicen esto:
Creemos que acoplar excepciones a una estructura de control, como en la expresión try-catch-finally, da como resultado un código intrincado. También tiende a alentar a los programadores a etiquetar demasiados errores comunes, como no abrir un archivo, como excepcionales.
Ir toma un enfoque diferente. Para el manejo simple de errores, los retornos multi-valor de Go facilitan el reporte de un error sin sobrecargar el valor de retorno. Un tipo de error canónico, junto con otras características de Go, hace que el manejo de errores sea agradable, pero bastante diferente del de otros lenguajes.
Go también tiene un par de funciones integradas para señalar y recuperarse de condiciones realmente excepcionales. El mecanismo de recuperación se ejecuta solo como parte del estado de una función que se derriba después de un error, que es suficiente para manejar una catástrofe pero no requiere estructuras de control adicionales y, cuando se usa bien, puede dar como resultado un código limpio de manejo de errores.
Vea el artículo Defer, Panic and Recover para más detalles.
Entonces, la respuesta corta es que pueden hacerlo de manera diferente utilizando el retorno de múltiples valores. (Y de todos modos tienen una forma de manejo de excepciones).
... y la fama de Linus de Linux ha llamado basura de excepciones.
Si quieres saber por qué Linus cree que las excepciones son basura, lo mejor es buscar sus escritos sobre el tema. Lo único que he rastreado hasta ahora es esta cita que está integrada en un par de correos electrónicos en C ++ :
"Todo el asunto de la gestión de excepciones de C ++ está fundamentalmente roto. Está especialmente roto para los kernels".
Notará que está hablando de excepciones de C ++ en particular, y no de excepciones en general. (Y las excepciones de C ++ aparentemente tienen algunos problemas que los hacen difíciles de usar correctamente).
¡Mi conclusión es que Linus no ha llamado excepciones (en general) "basura" en absoluto!
Las excepciones en sí mismas no son "malas", es la manera en que a veces se manejan las excepciones que tienden a ser malas. Hay varias pautas que se pueden aplicar al manejar excepciones para ayudar a aliviar algunos de estos problemas. Algunos de estos incluyen (pero seguramente no están limitados a):
- No use excepciones para controlar el flujo del programa, es decir, no confíe en las declaraciones de "captura" para cambiar el flujo de la lógica. Esto no solo tiende a ocultar varios detalles en torno a la lógica, sino que puede conducir a un rendimiento deficiente.
- No arroje excepciones desde dentro de una función cuando un "estado" devuelto tenga más sentido, solo arroje excepciones en una situación excepcional. Crear excepciones es una operación costosa y de alto rendimiento. Por ejemplo, si llama a un método para abrir un archivo y dicho archivo no existe, ejecute una excepción "FileNotFound". Si llama a un método que determina si existe una cuenta de cliente, devuelva un valor booleano, no devuelva una excepción "CustomerNotFound".
- Al determinar si se maneja o no una excepción, no use una cláusula "try ... catch" a menos que pueda hacer algo útil con la excepción. Si no puede manejar la excepción, debería dejar que suba la pila de llamadas. De lo contrario, las excepciones pueden ser "tragadas" por el controlador y los detalles se perderán (a menos que vuelva a lanzar la excepción).
Las excepciones hacen que sea realmente fácil escribir código donde una excepción arrojada romperá invariantes y dejará los objetos en un estado incoherente. Básicamente te obligan a recordar que la mayoría de las afirmaciones que haces pueden potencialmente arrojar, y manejarlas correctamente. Hacerlo puede ser complicado y contra-intuitivo.
Considera algo como esto como un simple ejemplo:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Suponiendo que FrobManager
delete
FrobObject
, se ve bien, ¿verdad? O tal vez no ... Imagine entonces si FrobManager::HandleFrob()
o el operator new
arroja una excepción. En este ejemplo, el incremento de m_NumberOfFrobs
no se m_NumberOfFrobs
. Por lo tanto, cualquier persona que use esta instancia de Frobber
tendrá un objeto posiblemente dañado.
Este ejemplo puede parecer estúpido (vale, tuve que estirarme un poco para construir uno :-)), pero, para llevarlo es que si un programador no está constantemente pensando en excepciones, y asegurándose de que cada permutación de estado se lance de vuelta cada vez que hay tiros, te metes en problemas de esta manera.
Como ejemplo, puedes pensarlo como piensas en mutexes. Dentro de una sección crítica, usted confía en varias declaraciones para asegurarse de que las estructuras de datos no estén dañadas y que otros subprocesos no puedan ver sus valores intermedios. Si cualquiera de esas declaraciones simplemente no se ejecuta al azar, terminas en un mundo de dolor. Ahora quite los bloqueos y la concurrencia, y piense en cada método de esa manera. Piensa en cada método como una transacción de permutaciones en el estado del objeto, si lo deseas. Al comienzo de la llamada a su método, el objeto debería estar limpio y al final también debería haber un estado limpio. En el medio, la variable foo
puede ser inconsistente con la bar
, pero tu código eventualmente lo rectificará. Lo que significan las excepciones es que cualquiera de sus declaraciones puede interrumpirlo en cualquier momento. La responsabilidad recae en usted en cada método individual para hacerlo bien y retroceder cuando eso ocurra, u ordenar sus operaciones para que los lanzamientos no afecten el estado del objeto. Si te equivocas (y es fácil cometer este tipo de error), la persona que llama termina viendo tus valores intermedios.
Los métodos como RAII, que los programadores de C ++ adoran mencionar como la solución definitiva a este problema, son un gran avance para protegerse de esto. Pero no son una bala de plata. Se asegurará de que libere los recursos en un lanzamiento, pero no le libera de tener que pensar en la corrupción del estado del objeto y los llamadores que ven valores intermedios. Entonces, para mucha gente, es más fácil decir, por decreto de estilo de codificación, sin excepciones . Si restringe el tipo de código que escribe, es más difícil introducir estos errores. Si no lo haces, es bastante fácil cometer un error.
Se han escrito libros completos sobre la codificación segura de excepciones en C ++. Muchos expertos lo entendieron mal. Si es realmente tan complejo y tiene tantos matices, tal vez sea una buena señal de que debes ignorar esa característica. :-)
Las excepciones no son malas per se, pero si sabes que van a suceder mucho, pueden ser costosas en términos de rendimiento.
La regla general es que las excepciones deben marcar condiciones excepcionales, y que no debe usarlas para controlar el flujo del programa.
Las excepciones no son malas. Encajan bien con el modelo RAII de C ++, que es lo más elegante de C ++. Si ya tienes un montón de código que no es una excepción segura, entonces son malos en ese contexto. Si estás escribiendo un software realmente de bajo nivel, como el sistema operativo Linux, entonces son malos. Si le gusta ensuciar su código con un montón de comprobaciones de retorno de error, entonces no son útiles. Si no tiene un plan para el control de recursos cuando se lanza una excepción (que los destructores de C ++ proporcionan), entonces son malos.
Los argumentos típicos son que no hay forma de saber qué excepciones saldrán de un fragmento de código en particular (según el idioma) y que se parecen demasiado a goto
s, lo que dificulta la localización mental de la ejecución.
http://www.joelonsoftware.com/items/2003/10/13.html
Definitivamente no hay consenso sobre este tema. Diría que desde el punto de vista de un programador de C duro como Linus, las excepciones son definitivamente una mala idea. Sin embargo, un programador Java típico se encuentra en una situación muy diferente.
No estoy de acuerdo con "solo lanzar excepciones en una situación excepcional". Aunque generalmente es cierto, es engañoso. Las excepciones son para condiciones de error (fallas de ejecución).
Independientemente del idioma que use, obtenga una copia de las Pautas de diseño de marcos : Convenciones, modismos y patrones para bibliotecas .NET reutilizables (2da edición). El capítulo sobre lanzamiento de excepción no tiene igual. Algunas citas de la primera edición (la segunda en mi trabajo):
- NO devuelva códigos de error.
- Los códigos de error se pueden ignorar fácilmente, y a menudo lo son.
- Las excepciones son el principal medio para informar errores en los marcos.
- Una buena regla empírica es que si un método no hace lo que sugiere su nombre, debe considerarse una falla a nivel de método, lo que da como resultado una excepción.
- NO use excepciones para el flujo normal de control, si es posible.
Hay páginas de notas sobre los beneficios de las excepciones (consistencia API, elección de la ubicación del código de manejo de errores, robustez mejorada, etc.). Hay una sección sobre el rendimiento que incluye varios patrones (Tester-Doer, Try-Parse).
Las excepciones y el manejo de excepciones no son malos. Al igual que cualquier otra característica, pueden ser mal utilizados.
No he leído todas las otras respuestas, por lo que ya se ha mencionado, pero una crítica es que provocan que los programas se rompan en largas cadenas, lo que dificulta localizar los errores al depurar el código. Por ejemplo, si Foo () llama a Bar () que llama a Wah () que llama a ToString (), entonces presionar accidentalmente los datos incorrectos en ToString () termina pareciendo un error en Foo (), una función casi no relacionada.
Para mí, el problema es muy simple. Muchos programadores usan el manejador de excepciones de forma inapropiada. Más recursos de idiomas es mejor. Ser capaz de manejar excepciones es bueno. Un ejemplo de uso incorrecto es un valor que debe ser entero no verificado, u otra entrada que puede dividirse y no verificarse para división de cero ... el manejo de excepciones puede ser una manera fácil de evitar más trabajo y pensamiento difícil, el programador puede querer hacer un acceso directo sucio y aplicar un manejo de excepción ... La afirmación: "un código profesional NUNCA falla" podría ser ilusorio, si algunos de los problemas procesados por el algoritmo son inciertos por su propia naturaleza. Tal vez en las situaciones desconocidas por naturaleza es bueno entrar en juego el controlador de excepción. Las buenas prácticas de programación son una cuestión de debate.
Un gran caso de uso para excepciones es, por tanto ...
Digamos que está en un proyecto y cada controlador (alrededor de 20 diferentes) amplía un único controlador de superclase con un método de acción. Entonces cada controlador hace un montón de cosas diferentes entre sí llamando a los objetos B, C, D en un caso y F, G, D en otro caso. Las excepciones vienen al rescate aquí en muchos casos donde había toneladas de código de retorno y CADA controlador lo manejaba de manera diferente. Golpeé todo ese código, arrojé la excepción correcta de "D", lo pillé en el método de acción del controlador de superclase y ahora todos nuestros controladores son consistentes. Previamente D devolvía nulo para MÚLTIPLES casos de error diferentes que queremos decirle al usuario final pero no podía y no quería convertir StreamResponse en un desagradable objeto ErrorOrStreamResponse (mezclando una estructura de datos con errores en mi opinión es un mal olor y veo un montón de código devolver un "flujo" u otro tipo de entidad con información de error incrustada en él (realmente debería ser la función devuelve la estructura de éxito O la estructura de error que puedo hacer con excepciones vs. códigos de retorno ) ... aunque la forma C # de respuestas múltiples es algo que podría considerar a veces, aunque en muchos casos, la excepción puede omitir un montón de capas (capas en las que no necesito limpiar los recursos tampoco).
sí, tenemos que preocuparnos por cada nivel y cualquier limpieza / fuga de recursos, pero en general ninguno de nuestros controladores tenía recursos para limpiar después.
gracias a Dios que tuvimos excepciones o me hubiera gustado tener un refactor enorme y perdí demasiado tiempo en algo que debería ser un problema de programación simple.
- La excepción que no se maneja es generalmente mala.
- La excepción manejada mal es mala (por supuesto).
- La "bondad / maldad" del manejo de excepciones depende del contexto / alcance y lo apropiado, y no por el simple hecho de hacerlo.