A menudo veo / escucho a personas decir que las excepciones solo deben usarse raras veces, pero nunca explicar por qué. Si bien eso puede ser cierto, la lógica es normalmente simplista: "se llama una excepción por una razón" que, para mí, parece ser el tipo de explicación que nunca debería ser aceptada por un programador / ingeniero respetable.
Existe una variedad de problemas que se pueden resolver con una excepción. ¿Por qué no es prudente utilizarlos para controlar el flujo? ¿Cuál es la filosofía detrás de ser excepcionalmente conservador con la forma en que se usan? ¿Semántica? ¿Actuación? ¿Complejidad? ¿Estética? ¿Convención?
He visto algunos análisis sobre el rendimiento antes, pero a un nivel que sería relevante para algunos sistemas e irrelevante para otros.
Una vez más, no estoy necesariamente en desacuerdo con que deban guardarse para circunstancias especiales, pero me pregunto cuál es la razón fundamental del consenso (si tal cosa existe).
Respuestas:
El principal punto de fricción es la semántica. Muchos desarrolladores abusan de las excepciones y las aprovechan en cada oportunidad. La idea es utilizar la excepción para situaciones algo excepcionales. Por ejemplo, la entrada incorrecta del usuario no cuenta como una excepción porque espera que esto suceda y está listo para eso. Pero si intentó crear un archivo y no había suficiente espacio en el disco, entonces sí, esta es una excepción definitiva.
Otro problema es que las excepciones a menudo se lanzan y se tragan. Los desarrolladores utilizan esta técnica para simplemente "silenciar" el programa y dejar que se ejecute el mayor tiempo posible hasta que colapse por completo. Esto está muy mal. Si no procesa las excepciones, si no reacciona apropiadamente liberando algunos recursos, si no registra la ocurrencia de la excepción o al menos no notifica al usuario, entonces no está usando la excepción para lo que significan.
Respondiendo directamente a tu pregunta. Las excepciones rara vez deben usarse porque las situaciones excepcionales son raras y las excepciones son costosas.
Rara, porque no espera que su programa se bloquee cada vez que presione un botón o cada entrada de usuario con formato incorrecto. Por ejemplo, es posible que la base de datos de repente no sea accesible, puede que no haya suficiente espacio en el disco, algún servicio de terceros del que depende esté fuera de línea, todo esto puede suceder, pero muy raramente, estos serían casos excepcionales claros.
Caro, porque lanzar una excepción interrumpirá el flujo normal del programa. El tiempo de ejecución desenrollará la pila hasta que encuentre un manejador de excepciones apropiado que pueda manejar la excepción. También recopilará la información de la llamada a lo largo del camino para pasarla al objeto de excepción que recibirá el controlador. Todo tiene costos.
Esto no quiere decir que no pueda haber excepción al uso de excepciones (sonrisa). A veces, puede simplificar la estructura del código si lanza una excepción en lugar de reenviar códigos de retorno a través de muchas capas. Como regla simple, si espera que algún método sea llamado con frecuencia y descubre alguna situación "excepcional" la mitad del tiempo, entonces es mejor encontrar otra solución. Sin embargo, si espera un flujo de operación normal la mayor parte del tiempo, mientras que esta situación "excepcional" solo puede surgir en algunas circunstancias raras, entonces está bien lanzar una excepción.
@Comments: La excepción definitivamente se puede usar en algunas situaciones menos excepcionales si eso pudiera hacer que su código sea más simple y fácil. Esta opción está abierta, pero diría que es bastante rara en la práctica.
Porque las excepciones interrumpen el "flujo de control" normal. Genera una excepción y la ejecución normal del programa se abandona, dejando potencialmente los objetos en un estado inconsistente y algunos recursos abiertos sin completar. Claro, C # tiene la declaración using que se asegurará de que el objeto se elimine incluso si se lanza una excepción desde el cuerpo using. Pero abstraigamos por el momento del lenguaje. Suponga que el marco no eliminará los objetos por usted. Lo haces manualmente. Tienes algún sistema sobre cómo solicitar y liberar recursos y memoria. Tiene un acuerdo en todo el sistema sobre quién es responsable de liberar objetos y recursos en qué situaciones. Tiene reglas sobre cómo tratar con bibliotecas externas. Funciona muy bien si el programa sigue el flujo de operación normal. Pero de repente, en medio de la ejecución, lanza una excepción. La mitad de los recursos quedan sin cultivar. La mitad aún no se ha solicitado. Si la operación estaba destinada a ser transaccional, ahora está rota. Sus reglas para manejar recursos no funcionarán porque las partes del código responsables de liberar recursos simplemente no se ejecutarán. Si alguien más quisiera usar esos recursos, puede encontrarlos en un estado inconsistente y colapsar también porque no pueden predecir esta situación en particular.
Digamos, usted quería un método M () llamar al método N () para hacer un trabajo y arreglar algún recurso y luego devolverlo a M () que lo usará y luego lo desechará. Multa. Ahora algo sale mal en N () y arroja una excepción que no esperaba en M (), por lo que la excepción burbujea en la parte superior hasta que tal vez quede atrapada en algún método C () que no tendrá idea de lo que estaba sucediendo en el fondo. en N () y si liberar algunos recursos y cómo hacerlo.
Con las excepciones de lanzamiento, crea una manera de llevar su programa a muchos estados intermedios nuevos e impredecibles que son difíciles de pronosticar, comprender y manejar. Es algo similar a usar GOTO. Es muy difícil diseñar un programa que pueda saltar aleatoriamente su ejecución de una ubicación a otra. También será difícil mantenerlo y depurarlo. Cuando el programa crece en complejidad, simplemente perderá una descripción general de qué, cuándo y dónde está sucediendo menos para solucionarlo.
fuente
Si bien "lanzar excepciones en circunstancias excepcionales" es la respuesta simplista, en realidad puede definir cuáles son esas circunstancias: cuando se satisfacen las condiciones previas, pero no se pueden satisfacer las condiciones posteriores . Esto le permite escribir condiciones posteriores más estrictas, estrictas y útiles sin sacrificar el manejo de errores; de lo contrario, sin excepciones, debe cambiar la condición posterior para permitir todos los posibles estados de error.
Constructores
Hay muy poco que pueda decir sobre cada constructor para cada clase que posiblemente podría estar escrito en C ++, pero hay algunas cosas. El principal de ellos es que los objetos construidos (es decir, para los cuales el constructor logró regresar) serán destruidos. No puede modificar esta condición posterior porque el lenguaje asume que es verdadera y llamará a los destructores automáticamente. (Técnicamente, puede aceptar la posibilidad de un comportamiento indefinido para el que el lenguaje no ofrece garantías sobre nada, pero que probablemente esté mejor cubierto en otro lugar).
La única alternativa a lanzar una excepción cuando un constructor no puede tener éxito es modificar la definición básica de la clase (la "clase invariante") para permitir estados válidos "nulos" o zombies y así permitir que el constructor "tenga éxito" construyendo un zombi .
Ejemplo de zombie
Un ejemplo de esta modificación zombie es std :: ifstream , y siempre debe verificar su estado antes de poder usarlo. Debido a que std :: string , por ejemplo, no lo hace, siempre se le garantiza que puede usarlo inmediatamente después de la construcción. Imagínese si tuviera que escribir código como este ejemplo, y si olvidara verificar el estado zombie, obtendría silenciosamente resultados incorrectos o corrompería otras partes de su programa:
string s = "abc"; if (s.memory_allocation_succeeded()) { do_something_with(s); // etc. }
Incluso nombrar ese método es un buen ejemplo de cómo debe modificar el invariante de la clase y la interfaz para una situación que la cadena no puede predecir ni manejarse por sí misma.
Ejemplo de validación de entrada
Abordemos un ejemplo común: validar la entrada del usuario. El hecho de que queramos permitir la entrada fallida no significa que la función de análisis deba incluir eso en su condición posterior. Sin embargo, significa que nuestro controlador debe verificar si el analizador falla.
// boost::lexical_cast<int>() is the parsing function here void show_square() { using namespace std; assert(cin); // precondition for show_square() cout << "Enter a number: "; string line; if (!getline(cin, line)) { // EOF on cin // error handling omitted, that EOF will not be reached is considered // part of the precondition for this function for the sake of example // // note: the below Python version throws an EOFError from raw_input // in this case, and handling this situation is the only difference // between the two } int n; try { n = boost::lexical_cast<int>(line); // lexical_cast returns an int // if line == "abc", it obviously cannot meet that postcondition } catch (boost::bad_lexical_cast&) { cout << "I can't do that, Dave.\n"; return; } cout << n * n << '\n'; }
Desafortunadamente, esto muestra dos ejemplos de cómo el alcance de C ++ requiere que rompa RAII / SBRM. Un ejemplo en Python que no tiene ese problema y muestra algo que me gustaría que tuviera C ++ - try-else:
# int() is the parsing "function" here def show_square(): line = raw_input("Enter a number: ") # same precondition as above # however, here raw_input will throw an exception instead of us # using assert try: n = int(line) except ValueError: print "I can't do that, Dave." else: print n * n
Condiciones previas
Las condiciones previas no tienen que verificarse estrictamente; violar una siempre indica una falla lógica y son responsabilidad de la persona que llama, pero si las verifica, entonces lanzar una excepción es apropiado. (En algunos casos, es más apropiado devolver basura o bloquear el programa; aunque esas acciones pueden ser terriblemente incorrectas en otros contextos. La mejor manera de manejar el comportamiento indefinido es otro tema).
En particular, contraste las ramas std :: logic_error y std :: runtime_error de la jerarquía de excepciones stdlib. El primero se usa a menudo para violaciones de condiciones previas, mientras que el segundo es más adecuado para violaciones posteriores a condiciones.
fuente
Llamadas costosas del kernel (u otras invocaciones de API del sistema) para administrar las interfaces de señal del kernel (sistema)
Muchos de los problemas de la
goto
declaración se aplican a las excepciones. Saltan sobre cantidades potencialmente grandes de código a menudo en múltiples rutinas y archivos fuente. Esto no siempre es evidente al leer el código fuente intermedio. (Está en Java).El código que se salta puede o no haber sido escrito con la posibilidad de una salida de excepción en mente. Si originalmente se escribió así, es posible que no se haya mantenido con eso en mente. Piense: fugas de memoria, fugas de descriptor de archivos, fugas de socket, ¿quién sabe?
Es más difícil mantener un código que salte el procesamiento de excepciones.
fuente
int f() { char* s = malloc(...); if (some_func() == error) { free(s); return error; } ... }
tienes que pagar para deshacer la pila, ya sea que lo hagas manualmente o mediante excepciones. No se puede comparar el uso de excepciones a ningún control de errores en absoluto .Lanzar una excepción es, hasta cierto punto, similar a una instrucción goto. Haga eso para controlar el flujo y terminará con un código espagueti incomprensible. Peor aún, en algunos casos ni siquiera sabe a dónde va exactamente el salto (es decir, si no está detectando la excepción en el contexto dado). Esto viola descaradamente el principio de "mínima sorpresa" que mejora la capacidad de mantenimiento.
fuente
catch
, aunque en ese caso casi podrías llamarte astd::terminate()
ti mismo. En general, este argumento me parece que dice "nunca use excepciones", en lugar de "solo use excepciones raramente".Las excepciones hacen que sea más difícil razonar sobre el estado de su programa. En C ++, por ejemplo, debe pensar más para asegurarse de que sus funciones sean muy seguras para las excepciones, de lo que tendría que hacer si no fuera necesario.
La razón es que, sin excepciones, una llamada de función puede regresar o puede terminar el programa primero. Con excepciones, una llamada de función puede regresar, o puede terminar el programa, o puede saltar a un bloque de captura en algún lugar. Por lo tanto, ya no puede seguir el flujo de control con solo mirar el código frente a usted. Necesita saber si las funciones llamadas pueden lanzar. Es posible que necesite saber qué se puede lanzar y dónde se atrapa, dependiendo de si le importa a dónde va el control o solo le importa que salga del alcance actual.
Por esta razón, la gente dice "no use excepciones a menos que la situación sea realmente excepcional". Cuando se analiza, "realmente excepcional" significa que "ha ocurrido alguna situación en la que los beneficios de manejarlo con un valor de retorno de error se ven compensados por los costos". Así que sí, esto es algo así como una declaración vacía, aunque una vez que tienes algunos instintos para "realmente excepcional", se convierte en una buena regla general. Cuando la gente habla de control de flujo, quiere decir que la capacidad de razonar localmente (sin hacer referencia a los bloques de captura) es un beneficio de los valores de retorno.
Java tiene una definición más amplia de "realmente excepcional" que C ++. Es más probable que los programadores de C ++ quieran ver el valor de retorno de una función que los programadores de Java, por lo que en Java "realmente excepcional" podría significar "No puedo devolver un objeto no nulo como resultado de esta función". En C ++, es más probable que signifique "Dudo mucho que mi interlocutor pueda continuar". Entonces, una secuencia de Java arroja si no puede leer un archivo, mientras que una secuencia de C ++ (por defecto) devuelve un valor que indica un error. En todos los casos, sin embargo, es una cuestión de qué código está dispuesto a forzar a escribir a la persona que llama. Por lo tanto, es una cuestión de estilo de codificación: debe llegar a un consenso sobre cómo debería verse su código y cuánto código de "verificación de errores" desea escribir en comparación con la cantidad de "seguridad de excepción"
El amplio consenso en todos los idiomas parece ser que esto se hace mejor en términos de cuán recuperable es probable que sea el error (ya que los errores irrecuperables no dan como resultado ningún código con excepciones, pero aún así es necesario verificar y devolver el suyo propio) error en el código que utiliza devoluciones de error). Así que la gente viene a esperar "esta llamada de función que se produce una excepción" en el sentido de " yo no puedo continuar", no sólo " seno puede continuar ". Eso no es inherente a las excepciones, es solo una costumbre, pero como cualquier buena práctica de programación, es una costumbre defendida por personas inteligentes que lo han probado de otra manera y no han disfrutado de los resultados. Yo también he tenido malas Experiencias que arrojan demasiadas excepciones. Así que, personalmente, pienso en términos de "realmente excepcional", a menos que algo en la situación haga que una excepción sea particularmente atractiva.
Por cierto, aparte de razonar sobre el estado de su código, también hay implicaciones de rendimiento. Las excepciones suelen ser económicas ahora, en idiomas en los que tiene derecho a preocuparse por el rendimiento. Pueden ser más rápidos que varios niveles de "oh, el resultado es un error, entonces será mejor que salga yo mismo con un error". En los viejos tiempos, existía el temor real de que lanzar una excepción, detectarla y continuar con lo siguiente, haría que lo que está haciendo sea tan lento que resulte inútil. Entonces, en ese caso, "realmente excepcional" significa "la situación es tan mala que el desempeño horrible ya no importa". Ese ya no es el caso (aunque todavía se nota una excepción en un circuito cerrado) y con suerte indica por qué la definición de "realmente excepcional" debe ser flexible.
fuente
new
) y la biblioteca estándar arrojan excepciones, no veo cómo no usar excepciones en su código lo libra de escribir código seguro para excepciones independientemente.Realmente no hay consenso. Todo el asunto es algo subjetivo, porque la "conveniencia" de lanzar una excepción a menudo es sugerida por las prácticas existentes dentro de la biblioteca estándar del lenguaje mismo. La biblioteca estándar de C ++ arroja excepciones con mucha menos frecuencia que, por ejemplo, la biblioteca estándar de Java, que casi siempre prefiere las excepciones, incluso para los errores esperados, como una entrada de usuario no válida (por ejemplo
Scanner.nextInt
). Esto, creo, influye significativamente en las opiniones de los desarrolladores sobre cuándo es apropiado lanzar una excepción.Como programador de C ++, personalmente prefiero reservar excepciones para circunstancias muy "excepcionales", por ejemplo, falta de memoria, falta de espacio en el disco, ocurrió el apocalipsis, etc. Pero no insisto en que esta sea la forma absolutamente correcta de hacerlo. cosas.
fuente
No creo que las excepciones deban usarse raramente. Pero.
No todos los equipos y proyectos están listos para usar excepciones. El uso de excepciones requiere una alta calificación de los programadores, técnicas especiales y falta de un gran código heredado que no sea seguro para excepciones. Si tiene una base de código antigua enorme, entonces casi siempre no es seguro para excepciones. Estoy seguro de que no querrás reescribirlo.
Si va a utilizar las excepciones de forma extensiva, entonces:
Por otro lado, el uso de excepciones en proyectos nuevos con un equipo sólido puede hacer que el código sea más limpio, más fácil de mantener e incluso más rápido:
fuente
EDITAR 20/11/2009 :
Estaba leyendo este artículo de MSDN sobre cómo mejorar el rendimiento del código administrado y esta parte me recordó esta pregunta:
Por supuesto, esto es solo para .NET, y también está dirigido específicamente a aquellos que desarrollan aplicaciones de alto rendimiento (como yo); así que obviamente no es una verdad universal. Aún así, somos muchos los desarrolladores de .NET, así que sentí que valía la pena señalarlo.
EDITAR :
Bien, en primer lugar, aclaremos una cosa: no tengo ninguna intención de pelear con nadie por la cuestión del rendimiento. En general, de hecho, me inclino a estar de acuerdo con aquellos que creen que la optimización prematura es un pecado. Sin embargo, permítanme señalar dos puntos:
El cartel pide una justificación objetiva detrás de la sabiduría convencional de que las excepciones deben usarse con moderación. Podemos discutir la legibilidad y el diseño adecuado todo lo que queramos; pero estos son asuntos subjetivos con personas dispuestas a discutir de ambos lados. Creo que el cartel lo sabe. El hecho es que usar excepciones para controlar el flujo del programa es a menudo una forma ineficiente de hacer las cosas. No, no siempre , pero a menudo . Es por eso que es un consejo razonable utilizar las excepciones con moderación, al igual que es un buen consejo comer carne roja o beber vino con moderación.
Existe una diferencia entre optimizar sin una buena razón y escribir código eficiente. El corolario de esto es que hay una diferencia entre escribir algo que es robusto, si no optimizado, y algo que es simplemente ineficiente. A veces pienso que cuando las personas discuten sobre cosas como el manejo de excepciones, en realidad están hablando entre ellos, porque están discutiendo cosas fundamentalmente diferentes.
Para ilustrar mi punto, considere los siguientes ejemplos de código C #.
Ejemplo 1: detección de entrada de usuario no válida
Este es un ejemplo de lo que yo llamaría abuso de excepción .
int value = -1; string input = GetInput(); bool inputChecksOut = false; while (!inputChecksOut) { try { value = int.Parse(input); inputChecksOut = true; } catch (FormatException) { input = GetInput(); } }
Este código es, para mí, ridículo. Por supuesto que funciona . Nadie está discutiendo con eso. Pero debería ser algo como:
int value = -1; string input = GetInput(); while (!int.TryParse(input, out value)) { input = GetInput(); }
Ejemplo 2: Comprobación de la existencia de un archivo
Creo que este escenario es muy común. Ciertamente, parece mucho más "aceptable" para mucha gente, ya que se trata de E / S de archivos:
string text = null; string path = GetInput(); bool inputChecksOut = false; while (!inputChecksOut) { try { using (FileStream fs = new FileStream(path, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { text = sr.ReadToEnd(); } } inputChecksOut = true; } catch (FileNotFoundException) { path = GetInput(); } }
Esto parece bastante razonable, ¿verdad? Estamos intentando abrir un archivo; si no está allí, detectamos esa excepción e intentamos abrir un archivo diferente ... ¿Qué hay de malo en eso?
Nada en realidad. Pero considere esta alternativa, que no arroja ninguna excepción:
string text = null; string path = GetInput(); while (!File.Exists(path)) path = GetInput(); using (FileStream fs = new FileStream(path, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { text = sr.ReadToEnd(); } }
Por supuesto, si el desempeño de estos dos enfoques fuera realmente el mismo, esto realmente sería una cuestión puramente doctrinal. Entonces, echemos un vistazo. Para el primer ejemplo de código, hice una lista de 10000 cadenas aleatorias, ninguna de las cuales representaba un número entero adecuado, y luego agregué una cadena entera válida al final. Usando los dos enfoques anteriores, estos fueron mis resultados:
Usando
try
/catch
block: 25.455 segundosUsando
int.TryParse
: 1.637 milisegundosPara el segundo ejemplo, hice básicamente lo mismo: hice una lista de 10000 cadenas aleatorias, ninguna de las cuales era una ruta válida, luego agregué una ruta válida al final. Estos fueron los resultados:
Usando
try
/catch
block: 29,989 segundosUsando
File.Exists
: 22,820 milisegundosMucha gente respondería a esto diciendo: "Sí, bueno, lanzar y atrapar 10,000 excepciones es extremadamente poco realista; esto exagera los resultados". Claro que lo hace. La diferencia entre lanzar una excepción y manejar una entrada incorrecta por su cuenta no será notoria para el usuario. El hecho es que el uso de excepciones es, en estos dos casos, de 1,000 a más de 10,000 veces más lento que los enfoques alternativos que son igualmente legibles, si no más.
Por eso incluí el ejemplo del
GetNine()
método a continuación. No es que sea intolerablemente lento o inaceptablemente lento; es que es más lento de lo que debería ser ... sin una buena razón .Nuevamente, estos son solo dos ejemplos. Por supuesto , habrá ocasiones en las que el impacto en el rendimiento del uso de excepciones no sea tan grave (Pavel tiene razón; después de todo, depende de la implementación). Todo lo que digo es: seamos realistas, muchachos. En casos como el anterior, lanzar y atrapar una excepción es análogo a
GetNine()
; es solo una forma ineficaz de hacer algo que fácilmente podría hacerse mejor .Estás pidiendo una justificación como si esta fuera una de esas situaciones en las que todos se subieron al carro sin saber por qué. Pero, de hecho, la respuesta es obvia y creo que ya la sabe. El manejo de excepciones tiene un rendimiento horrendo.
De acuerdo, tal vez esté bien para su escenario comercial particular, pero en términos relativos , lanzar / capturar una excepción introduce mucha más sobrecarga de la necesaria en muchos, muchos casos. Lo sabes, yo lo sé: la mayoría de las veces , si estás usando excepciones para controlar el flujo del programa, solo estás escribiendo código lento.
También podría preguntar: ¿por qué este código es malo?
private int GetNine() { for (int i = 0; i < 10; i++) { if (i == 9) return i; } }
Apuesto a que si perfilara esta función, encontraría que funciona bastante rápido para su aplicación comercial típica. Eso no cambia el hecho de que es una forma horriblemente ineficiente de lograr algo que podría hacerse mucho mejor.
Eso es lo que la gente quiere decir cuando habla de "abuso" de excepción.
fuente
GetNine
hace bien su trabajo. Mi punto es que no hay razón para usarlo. No es como si fuera la forma "rápida y fácil" de hacer algo, mientras que el enfoque más "correcto" requeriría mucho más esfuerzo. En mi experiencia, a menudo sucede que el enfoque "correcto" en realidad requeriría menos esfuerzo, o casi lo mismo. De todos modos, para mí el rendimiento es la única razón objetiva por la que se desaconseja el uso de excepciones a la izquierda y a la derecha. En cuanto a si la pérdida de rendimiento está "muy sobreestimada", eso es de hecho subjetivo.Todas las reglas generales sobre las excepciones se reducen a términos subjetivos. No debe esperar obtener definiciones estrictas y rápidas de cuándo usarlos y cuándo no. "Solo en circunstancias excepcionales". Buena definición circular: las excepciones son por circunstancias excepcionales.
Cuándo usar excepciones cae en el mismo grupo que "¿cómo sé si este código es una clase o dos?" Es en parte una cuestión de estilo, en parte una preferencia. Las excepciones son una herramienta. Se pueden usar y abusar de ellos, y encontrar la línea entre los dos es parte del arte y la habilidad de la programación.
Hay muchas opiniones y compensaciones por hacer. Encuentra algo que te hable y síguelo.
fuente
No es que las excepciones deban usarse raramente. Es solo que solo deben arrojarse en circunstancias excepcionales. Por ejemplo, si un usuario ingresa una contraseña incorrecta, eso no es excepcional.
La razón es simple: las excepciones salen de una función abruptamente y se propagan por la pila hasta un
catch
bloque. Este proceso es muy costoso desde el punto de vista computacional: C ++ construye su sistema de excepciones para tener poca sobrecarga en las llamadas a funciones "normales", por lo que cuando se genera una excepción, tiene que trabajar mucho para encontrar dónde ir. Además, dado que cada línea de código podría generar una excepción. Si tenemos alguna funciónf
que genera excepciones con frecuencia, ahora debemos tener cuidado de usar nuestros bloquestry
/catch
en cada llamada def
. Ese es un acoplamiento interfaz / implementación bastante malo.fuente
Mencioné este problema en un artículo sobre excepciones de C ++ .
La parte relevante:
Casi siempre, usar excepciones para afectar el flujo "normal" es una mala idea. Como ya comentamos en la sección 3.1, las excepciones generan rutas de código invisibles. Se puede decir que estas rutas de código son aceptables si se ejecutan solo en los escenarios de manejo de errores. Sin embargo, si usamos excepciones para cualquier otro propósito, nuestra ejecución de código "normal" se divide en una parte visible e invisible y hace que el código sea muy difícil de leer, comprender y extender.
fuente
Mi enfoque para el manejo de errores es que hay tres tipos fundamentales de errores:
assert
hace). Generalmente, estas situaciones indican que algo se ha roto en algún lugar del código y, de hecho, no puede confiar en que nada más sea correcto; puede haber una corrupción desenfrenada de la memoria. Tu barco se está hundiendo, bájate.Parafraseando, las excepciones son para cuando tienes un problema con el que puedes lidiar, pero no puedes resolverlo en el lugar donde lo notas. Los problemas con los que no puede lidiar deberían simplemente matar el programa; los problemas que puede resolver de inmediato simplemente deben resolverse.
fuente
Leí algunas de las respuestas aquí. Todavía me sorprende de qué se trata toda esta confusión. Estoy totalmente en desacuerdo con todas estas excepciones == código spagetty. Con confusión, quiero decir que hay gente que no aprecia el manejo de excepciones de C ++. No estoy seguro de cómo aprendí sobre el manejo de excepciones de C ++, pero entendí las implicaciones en minutos. Esto fue alrededor de 1996 y estaba usando el compilador de Borland C ++ para OS / 2. Nunca tuve problemas para decidir cuándo usar las excepciones. Por lo general, envuelvo acciones de deshacer y deshacer falibles en clases de C ++. Estas acciones de deshacer y deshacer incluyen:
Que hay envoltorios funcionales. Para envolver las llamadas del sistema (que no entran en la categoría anterior) en C ++. Por ejemplo, leer / escribir desde / a un archivo. Si algo falla, se lanzará una excepción, que contiene información completa sobre el error.
Luego están las excepciones de captura / relanzamiento para agregar más información a una falla.
El manejo general de excepciones de C ++ conduce a un código más limpio. La cantidad de código se reduce drásticamente. Finalmente, se puede usar un constructor para asignar recursos falibles y aún mantener un entorno libre de corrupción después de tal falla.
Uno puede encadenar tales clases en clases complejas. Una vez que se ejecuta un constructor de algún miembro / objeto base, se puede confiar en que todos los demás constructores del mismo objeto (ejecutados antes) se ejecutaron con éxito.
fuente
Las excepciones son un método de control de flujo muy inusual en comparación con las construcciones tradicionales (bucles, ifs, funciones, etc.) Las construcciones de flujo de control normales (bucles, ifs, llamadas a funciones, etc.) pueden manejar todas las situaciones normales. Si se encuentra buscando una excepción para una ocurrencia de rutina, entonces quizás deba considerar cómo está estructurado su código.
Pero hay ciertos tipos de errores que no se pueden manejar fácilmente con las construcciones normales. Las fallas catastróficas (como las fallas en la asignación de recursos) se pueden detectar en un nivel bajo, pero probablemente no se puedan manejar allí, por lo que una simple declaración if es inadecuada. Estos tipos de fallas generalmente deben manejarse a un nivel mucho más alto (por ejemplo, guardar el archivo, registrar el error, salir). Intentar informar un error como este a través de métodos tradicionales (como valores devueltos) es tedioso y propenso a errores. Además, inyecta gastos generales en capas de API de nivel medio para manejar esta falla extraña e inusual. La sobrecarga distrae al cliente de estas API y les obliga a preocuparse por problemas que están fuera de su control. Las excepciones proporcionan una forma de realizar un tratamiento no local para errores grandes que '
Si un cliente llama
ParseInt
con una cadena y la cadena no contiene un número entero, entonces la persona que llama inmediatamente probablemente se preocupa por el error y sabe qué hacer al respecto. Entonces, diseñaría ParseInt para devolver un código de falla para algo así.Por otro lado, si
ParseInt
falla porque no pudo asignar un búfer porque la memoria está terriblemente fragmentada, la persona que llama no sabrá qué hacer al respecto. Tendría que hacer burbujear este error inusual hacia arriba y hasta alguna capa que se ocupe de estas fallas fundamentales. Eso grava a todos los que están en el medio (porque tienen que adaptarse al mecanismo de paso de errores en sus propias API). Una excepción permite omitir esas capas (sin dejar de garantizar que se lleve a cabo la limpieza necesaria).Cuando escribe código de bajo nivel, puede ser difícil decidir cuándo usar métodos tradicionales y cuándo lanzar excepciones. El código de bajo nivel tiene que tomar la decisión (tirar o no). Pero es el código de nivel superior el que realmente sabe lo que se espera y lo que es excepcional.
fuente
parseInt
en realidad no lanzar una excepción. Por lo tanto, diría que las opiniones sobre la conveniencia de lanzar excepciones dependen en gran medida de las prácticas preexistentes dentro de las bibliotecas estándar del lenguaje de desarrollo elegido.Hay varias razones en C ++.
Primero, con frecuencia es difícil ver de dónde provienen las excepciones (ya que pueden lanzarse desde casi cualquier cosa) y, por lo tanto, el bloque de captura es algo así como una declaración COME FROM. Es peor que un IR A, ya que en un IR A sabes de dónde vienes (la declaración, no una llamada de función aleatoria) y hacia dónde vas (la etiqueta). Son básicamente una versión potencialmente segura para los recursos de setjmp () y longjmp () de C, y nadie quiere usarlos.
En segundo lugar, C ++ no tiene la recolección de basura incorporada, por lo que las clases de C ++ que poseen recursos se deshacen de ellos en sus destructores. Por lo tanto, en el manejo de excepciones de C ++, el sistema tiene que ejecutar todos los destructores dentro del alcance. En lenguajes con GC y sin constructores reales, como Java, lanzar excepciones es mucho menos oneroso.
En tercer lugar, la comunidad de C ++, incluidos Bjarne Stroustrup y el Comité de Estándares y varios redactores de compiladores, ha asumido que las excepciones deberían ser excepcionales. En general, no vale la pena ir en contra de la cultura del idioma. Las implementaciones se basan en el supuesto de que las excepciones serán raras. Los mejores libros tratan las excepciones como excepcionales. Un buen código fuente usa pocas excepciones. Los buenos desarrolladores de C ++ tratan las excepciones como excepcionales. Para ir en contra de eso, querrás una buena razón, y todas las razones que veo están del lado de mantenerlas excepcionales.
fuente
finally
bloques de Java no son diferentes de los destructores de C ++ en términos de sobrecarga de implementación.finally
no se garantiza que los bloques de Java se ejecuten, por supuesto que sí. Me estaba confundiendo con elfinalize()
método Java , que no se garantiza que se ejecute.Este es un mal ejemplo de uso de excepciones como flujo de control:
int getTotalIncome(int incomeType) { int totalIncome= 0; try { totalIncome= calculateIncomeAsTypeA(); } catch (IncorrectIncomeTypeException& e) { totalIncome= calculateIncomeAsTypeB(); } return totalIncome; }
Lo cual es muy malo, pero deberías estar escribiendo:
int getTotalIncome(int incomeType) { int totalIncome= 0; if (incomeType == A) { totalIncome= calculateIncomeAsTypeA(); } else if (incomeType == B) { totalIncome= calculateIncomeAsTypeB(); } return totalIncome; }
Este segundo ejemplo obviamente necesita algo de refactorización (como usar la estrategia del patrón de diseño), pero ilustra bien que las excepciones no están pensadas para controlar el flujo.
Las excepciones también tienen algunas penalizaciones de rendimiento asociadas, pero los problemas de rendimiento deben seguir la regla: "la optimización prematura es la raíz de todos los males"
fuente
incomeType
.fuente
at()
? Dicho esto, cualquieranew
puede lanzar, así que ...Yo diría que las excepciones son un mecanismo para sacarlo del contexto actual (fuera del marco de pila actual en el sentido más simple, pero es más que eso) de una manera segura. Es lo más parecido que tiene la programación estructurada a un goto. Para usar las excepciones en la forma en que fueron diseñadas, debe tener una situación en la que no pueda continuar con lo que está haciendo ahora y no pueda manejarlo en el punto en el que se encuentra ahora. Entonces, por ejemplo, cuando la contraseña del usuario es incorrecta, puede continuar devolviendo false. Pero si el subsistema de la interfaz de usuario informa que ni siquiera puede avisar al usuario, simplemente devolver "error de inicio de sesión" sería incorrecto. El nivel actual de código simplemente no sabe qué hacer. Por lo tanto, utiliza un mecanismo de excepción para delegar la responsabilidad a alguien superior que pueda saber qué hacer.
fuente
Una razón muy práctica es que, al depurar un programa, a menudo selecciono Excepciones a la primera oportunidad (Depurar -> Excepciones) para depurar una aplicación. Si ocurren muchas excepciones, es muy difícil encontrar dónde algo salió "mal".
Además, conduce a algunos anti-patrones como el infame "tiro de captura" y ofusca los problemas reales. Para obtener más información al respecto, consulte una publicación de blog que hice sobre el tema.
fuente
Prefiero utilizar las excepciones lo menos posible. Las excepciones obligan al desarrollador a manejar alguna condición que puede ser o no un error real. La definición de si la excepción en cuestión es un problema fatal o un problema que debe manejarse de inmediato.
El argumento contrario a eso es que solo requiere que las personas perezosas escriban más para dispararse en sus pies.
La política de codificación de Google dice que nunca use excepciones , especialmente en C ++. Su aplicación no está preparada para manejar excepciones o lo está. Si no es así, entonces la excepción probablemente lo propagará hasta que su aplicación muera.
Nunca es divertido descubrir que alguna biblioteca ha usado arroja excepciones y no estaba preparado para manejarlas.
fuente
Caso legítimo para lanzar una excepción:
Caso ilegítimo:
Utilizo excepciones cuando quiero interrumpir el flujo de la aplicación hasta cierto punto . Este punto es donde está el truco (...) para esa excepción. Por ejemplo, es muy común que tengamos que procesar una gran cantidad de proyectos, y cada proyecto debe procesarse independientemente de los demás. Entonces, el ciclo que procesa los proyectos tiene un bloque try ... catch, y si se lanza alguna excepción durante el procesamiento del proyecto, todo se revierte para ese proyecto, se registra el error y se procesa el siguiente proyecto. La vida continua.
Creo que debería usar excepciones para cosas como un archivo que no existe, una expresión que no es válida y cosas similares.
No debe usar excepciones para pruebas de rango / pruebas de tipo de datos / existencia de archivos / cualquier otra cosa si hay una alternativa fácil / barata.No debe usar excepciones para pruebas de rango / pruebas de tipo de datos / existencia de archivos / cualquier otra cosa si hay una alternativa fácil / barata porque este tipo de lógica hace que el código sea difícil de entender:RecordIterator<MyObject> ri = createRecordIterator(); try { MyObject myobject = ri.next(); } catch(NoSuchElement exception) { // Object doesn't exist, will create it }
Esto sería mejor:
RecordIterator<MyObject> ri = createRecordIterator(); if (ri.hasNext()) { // It exists! MyObject myobject = ri.next(); } else { // Object doesn't exist, will create it }
COMENTARIO AÑADIDO A LA RESPUESTA:
Tal vez mi ejemplo no fue muy bueno: ri.next () no debería generar una excepción en el segundo ejemplo, y si lo hace, hay algo realmente excepcional y alguna otra acción debería tomarse en otro lugar. Cuando el ejemplo 1 se usa mucho, los desarrolladores detectarán una excepción genérica en lugar de la específica y asumirán que la excepción se debe al error que esperan, pero puede deberse a otra cosa. Al final, esto lleva a que se ignoren las excepciones reales, ya que las excepciones se convierten en parte del flujo de la aplicación y no en una excepción.
Los comentarios sobre esto pueden agregar más que mi propia respuesta.
fuente
try
bloque y luego ya no estás seguro de que elcatch
bloque realmente esté capturando lo que creías que estaba atrapando, ¿es así? Si bien es más difícil hacer un mal uso del enfoque if / then / else de la misma manera porque solo puede probar 1 cosa a la vez en lugar de un conjunto de cosas a la vez, por lo que el enfoque excepcional puede conducir a un código más frágil. Si es así, discuta esto en su respuesta y felizmente haré +1, ya que creo que la fragilidad del código es una razón genuina.Básicamente, las excepciones son una forma de control de flujo no estructurada y difícil de entender. Esto es necesario cuando se trata de condiciones de error que no forman parte del flujo normal del programa, para evitar que la lógica de manejo de errores desordene demasiado el control de flujo normal de su código.
En mi humilde opinión, las excepciones deben usarse cuando desee proporcionar un valor predeterminado sano en caso de que la persona que llama se olvide de escribir el código de manejo de errores, o si el error podría manejarse mejor en la pila de llamadas que la persona que llama inmediatamente. Lo mejor por defecto es salir del programa con un mensaje de error de diagnóstico razonable. La loca alternativa es que el programa cojea en un estado erróneo y se bloquea o produce silenciosamente una mala salida en algún momento posterior, más difícil de diagnosticar. Si el "error" es una parte normal del flujo del programa suficiente para que la persona que llama no pueda olvidar razonablemente comprobarlo, no se deben utilizar excepciones.
fuente
Creo que "usarlo raramente" no es la oración correcta. Preferiría "lanzar solo en situaciones excepcionales".
Muchos han explicado por qué las excepciones no deben usarse en situaciones normales. Las excepciones tienen derecho a la gestión de errores y únicamente a la gestión de errores.
Me centraré en otro punto:
Otra cosa es la cuestión del rendimiento. Los compiladores se esforzaron mucho para conseguirlos rápidamente. No estoy seguro de cuál es el estado exacto ahora, pero cuando usa excepciones para controlar el flujo, tendrá otro problema: ¡su programa se volverá lento!
La razón es que las excepciones no son solo sentencias goto muy poderosas, sino que también tienen que desenrollar la pila para todos los marcos que dejan. Por lo tanto, implícitamente también los objetos en la pila tienen que ser deconstruidos y así sucesivamente. Entonces, sin ser consciente de ello, un solo lanzamiento de una excepción realmente involucrará a un montón de mecánicas. El procesador tendrá que hacer mucho.
Así que terminarás quemando elegantemente tu procesador sin saberlo.
Entonces: use excepciones solo en casos excepcionales - Significado: ¡Cuando ocurrieron errores reales!
fuente
std::terminate()
, todavía tienes que destruir.El propósito de las excepciones es hacer que el software sea tolerante a fallas. Sin embargo, tener que proporcionar una respuesta a cada excepción lanzada por una función conduce a la supresión. Las excepciones son solo una estructura formal que obliga a los programadores a reconocer que ciertas cosas pueden salir mal con una rutina y que el programador del cliente debe estar consciente de estas condiciones y atenderlas según sea necesario.
Para ser honesto, las excepciones son un lujo agregado a los lenguajes de programación para proporcionar a los desarrolladores algún requisito formal que transfiera la responsabilidad de manejar los casos de error del desarrollador inmediato a algún desarrollador futuro.
Creo que un buen lenguaje de programación no admite excepciones como las conocemos en C ++ y Java. Debe optar por lenguajes de programación que puedan proporcionar un flujo alternativo para todo tipo de valores de retorno de funciones. El programador debería ser responsable de anticipar todas las formas de salidas de una rutina y manejarlas en un archivo de código separado si pudiera hacer lo que quisiera.
fuente
Uso excepciones si:
Si el error se puede recuperar de (el usuario ingresó "manzana" en lugar de un número), recupere (solicite la entrada nuevamente, cambie al valor predeterminado, etc.).
Si el error no se puede recuperar de forma local pero la aplicación puede continuar (el usuario intentó abrir un archivo pero el archivo no existe), entonces un código de error es apropiado.
Si el error no se puede recuperar de forma local y la aplicación no puede continuar sin recuperarse (no tiene memoria / espacio en disco / etc.), entonces una excepción es el camino correcto a seguir.
fuente
¿Quién dijo que deberían usarse de forma conservadora? Nunca use excepciones para el control de flujo y eso es todo. ¿Y a quién le importa el costo de la excepción cuando ya se lanzó?
fuente
Mis dos centavos:
Me gusta usar excepciones, porque me permite programar como si no hubiera errores. Entonces mi código sigue siendo legible, no disperso con todo tipo de manejo de errores. Por supuesto, el manejo de errores (manejo de excepciones) se mueve al final (el bloque de captura) o se considera responsabilidad del nivel de llamada.
Un gran ejemplo para mí es el manejo de archivos o el manejo de bases de datos. Suponga que todo está bien y cierre su archivo al final o si ocurre alguna excepción. O deshaga su transacción cuando ocurrió una excepción.
El problema con las excepciones es que rápidamente se vuelve muy detallado. Si bien estaba destinado a permitir que su código permaneciera muy legible y solo se enfocara en el flujo normal de las cosas, pero si se usa de manera consistente, casi todas las llamadas a funciones deben estar envueltas en un bloque try / catch, y comienza a frustrar el propósito.
Para un ParseInt como se mencionó anteriormente, me gusta la idea de excepciones. Solo devuelve el valor. Si el parámetro no se puede analizar, lanza una excepción. Hace que su código sea más limpio por un lado. En el nivel de la llamada, debes hacer algo como
try { b = ParseInt(some_read_string); } catch (ParseIntException &e) { // use some default value instead b = 0; }
El código está limpio. Cuando obtengo ParseInt como este esparcido por todas partes, hago funciones de contenedor que manejan las excepciones y me devuelven los valores predeterminados. P.ej
int ParseIntWithDefault(String stringToConvert, int default_value=0) { int result = default_value; try { result = ParseInt(stringToConvert); } catch (ParseIntException &e) {} return result; }
Entonces, para concluir: lo que me perdí durante la discusión fue el hecho de que las excepciones me permiten hacer mi código más fácil / más legible porque puedo ignorar más las condiciones de error. Problemas:
Eso hace que a veces sea difícil encontrar un buen equilibrio.
fuente
Lo siento, pero la respuesta es "se llaman excepciones por una razón". Esa explicación es una "regla general". No puede proporcionar un conjunto completo de circunstancias bajo las cuales las excepciones deben o no deben usarse porque lo que es una excepción fatal (definición en inglés) para un dominio de problemas es un procedimiento operativo normal para un dominio de problemas diferente. Las reglas generales no están diseñadas para seguirse ciegamente. En cambio, están diseñados para guiar su investigación de una solución. "Se llaman excepciones por una razón" le indica que debe determinar con anticipación cuál es un error normal que la persona que llama puede manejar y cuál es una circunstancia inusual que la persona que llama no puede manejar sin una codificación especial (bloques de captura).
Casi todas las reglas de programación son en realidad una guía que dice "No hagas esto a menos que tengas una buena razón": "Nunca uses goto", "Evita las variables globales", "Las expresiones regulares incrementan previamente el número de problemas en uno. ", etc. Las excepciones no son una excepción ...
fuente