c++ - que - ser y estar diferencias
¿Por qué deberían las excepciones ser usadas de manera conservadora? (28)
Posible duplicado:
¿Por qué el manejo de excepciones es malo?
A menudo veo / escucho que las personas dicen que las excepciones solo deben usarse con poca frecuencia, pero nunca explican por qué. Si bien eso puede ser cierto, la lógica es normalmente simplista: "se llama excepción por una razón" que, para mí, parece ser el tipo de explicación que nunca debe ser aceptada por un programador / ingeniero respetable.
Existe una gama de problemas que una excepción puede usarse para resolver. ¿Por qué no es prudente usarlos para controlar el flujo? ¿Cuál es la filosofía detrás de ser excepcionalmente conservador con la forma en que se utilizan? ¿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 se deberían guardar para circunstancias especiales, pero me pregunto cuál es la razón de consenso (si tal cosa existe).
- Costoso
llamadas al kernel (u otras invocaciones de la API del sistema) para administrar las interfaces de señal del kernel (sistema) - Difícil de analizar
Muchos de los problemas de la declaracióngoto
aplican a las excepciones. Saltan sobre cantidades potencialmente grandes de código a menudo en rutinas múltiples y archivos fuente. Esto no siempre es evidente al leer el código fuente intermedio. (Está en Java.) - No siempre anticipado por el código intermedio
El código que se salta puede haber sido escrito o no con la posibilidad de una excepción de salida en mente. Si originalmente escrito así, puede que no se haya mantenido con eso en mente. Piensa: pérdidas de memoria, pérdidas de descriptores de archivos, pérdidas de socket, ¿quién sabe? - Complicaciones de mantenimiento
Es más difícil mantener el código que salta alrededor de las excepciones de procesamiento.
- Maintainability: As mentioned by people above, throwing exceptions at a drop of a hat is akin to using gotos.
- Interoperability: You can''t interface C++ libraries with C/Python modules (atleast not easily) if you are using exceptions.
- Performance degradation: RTTI is used to actually find the type of the exception which imposes additional overhead. Thus exceptions are not suitable for handling commonly occurring use cases(user entered int instead of string etc).
El punto principal de fricción es la semántica. Muchos desarrolladores abusan de las excepciones y las tiran en cada oportunidad. La idea es usar excepción para una situación algo excepcional. 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 trataste de crear un archivo y no había suficiente espacio en el disco, entonces sí, esta es una excepción definitiva.
Otro problema es que a menudo se lanzan y se tragan excepciones. Los desarrolladores usan esta técnica para simplemente "silenciar" el programa y dejarlo funcionar el mayor tiempo posible hasta que se colapse por completo. Esto está muy mal. Si no procesa las excepciones, si no reacciona de forma adecuada liberando algunos recursos, si no registra la ocurrencia de la excepción o al menos no notifica al usuario, entonces no está utilizando una excepción para lo que significa.
Respondiendo directamente su pregunta. Las excepciones rara vez se deben usar porque las situaciones excepcionales son raras y las excepciones son costosas.
Raro, porque no esperas que tu programa falle cada vez que presionas un botón o en cada entrada de usuario mal formada. Digamos que la base de datos puede no estar accesible de repente, puede que no haya suficiente espacio en disco, que un servicio de terceros del que dependa esté desconectado, 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 controlador 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 excepciones al uso de excepciones (sonrisa). A veces puede simplificar la estructura del código si lanza una excepción en lugar de reenviar los códigos de retorno a través de muchas capas. Como regla simple, si espera que algún método se llame con frecuencia y descubra 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 excepcionales, 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 tu 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.
¿Por qué no es prudente usarlos para controlar el flujo?
Porque las excepciones interrumpen el "flujo de control" normal. Plantea una excepción y se abandona la ejecución normal del programa, lo que puede dejar los objetos en estado incoherente y algunos recursos abiertos sin actualizar. Claro, C # tiene la instrucción de uso que asegurará que el objeto se eliminará incluso si se arroja una excepción desde el cuerpo de uso. Pero vamos a abstraer por el momento del lenguaje. Supongamos que el marco no eliminará objetos para usted. Lo haces manualmente Usted tiene algún sistema sobre cómo solicitar y liberar recursos y memoria. Tiene un acuerdo en todo el sistema que es responsable de liberar objetos y recursos en qué situaciones. Usted tiene reglas sobre cómo lidiar 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, lanzas una excepción. La mitad de los recursos se dejan sin actualizar. La mitad no ha sido solicitada todavía. 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 desea usar esos recursos, puede encontrarlos en un estado incoherente y colapsar también porque no pudieron predecir esta situación particular.
Digamos, usted quería un método M () llame al método N () para hacer algún trabajo y disponer de algún recurso, luego regréselo a M () que lo usará y luego deséchelo. Multa. Ahora algo sale mal en N () y arroja una excepción que no esperabas en M () por lo que la excepción burbujea 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 y cómo liberar algunos recursos.
Al lanzar excepciones, crea una forma de llevar su programa a muchos estados intermedios nuevos e impredecibles que son difíciles de predecir, comprender y manejar. Es algo similar al uso de 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 va a perder una visión general de cuándo y dónde está sucediendo menos para arreglarlo.
Lanzar una excepción es, hasta cierto punto, similar a una declaración goto. Hazlo para controlar el flujo, y terminas con un código de espagueti incomprensible. Lo que es peor, en algunos casos ni siquiera sabes a dónde va exactamente el salto (es decir, si no estás atrapando la excepción en el contexto dado). Esto viola descaradamente el principio de "menos sorpresa" que mejora la capacidad de mantenimiento.
Las excepciones hacen que sea más difícil razonar sobre el estado de su programa. En C ++, por ejemplo, tienes que pensar más para asegurarte de que tus funciones sean totalmente seguras a excepción de lo que tendrías que hacer si no fuera necesario.
La razón es que sin excepciones, una llamada a función puede regresar, o puede terminar el programa primero. Con excepciones, una llamada a función puede regresar, o puede terminar el programa, o puede saltar a un bloque catch en alguna parte. Entonces ya no puedes seguir el flujo de control solo mirando el código que tienes delante. Necesitas saber si las funciones llamadas pueden arrojar. Es posible que necesite saber qué puede tirar y dónde se captura, dependiendo de si le importa dónde va el control, o si solo le importa que deje el alcance actual.
Por esta razón, la gente dice "no use excepciones a menos que la situación sea realmente excepcional". Cuando llega al final, "realmente excepcional" significa que "ha ocurrido una situación en la que los beneficios de manejarlo con un valor de retorno de error son superados 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 empírica. Cuando las personas hablan de control de flujo, quieren decir que la capacidad de razonar localmente (sin 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 ++. Los programadores de C ++ son más propensos a querer 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". Por lo tanto, una secuencia Java arroja si no puede leer un archivo, mientras que una secuencia C ++ (de forma predeterminada) devuelve un valor que indica un error. En todos los casos, sin embargo, es una cuestión de qué código está dispuesto a obligar a la persona que llama a escribir. Por lo tanto, es una cuestión de estilo de codificación: debe llegar a un consenso sobre cómo debería ser el código y cuánto código de "comprobación de errores" desea escribir en relación con el razonamiento de "excepción-seguridad" que desea hacer.
El amplio consenso en todos los idiomas parece ser que esto se hace mejor en términos de cuán recuperable es el error (ya que los errores irrecuperables no dan lugar a ningún código con excepciones, pero aún necesitan un check-and-return-your-own- error en el código que usa el error devuelve). Entonces, la gente espera que "esta función que llamo arroja una excepción" significa " No puedo continuar", no solo " No puede continuar". Eso no es inherente a las excepciones, es solo una costumbre, pero como toda buena práctica de programación, es una costumbre defendida por personas inteligentes que lo han intentado de otra manera y no han disfrutado los resultados. Yo también he tenido malas experiencias arrojando demasiadas excepciones. Personalmente, pienso en términos de "realmente excepcional", a menos que algo sobre la situación haga una excepción particularmente atractiva.
Por cierto, aparte de razonar sobre el estado de tu código, también hay implicaciones de rendimiento. Las excepciones suelen ser baratas ahora, en idiomas en los que tiene derecho a preocuparse por el rendimiento. Pueden ser más rápidos que los niveles múltiples de "oh, el resultado es un error, es mejor que me salga yo también con un error". En los malos viejos tiempos, existían temores reales de que lanzar una excepción, atraparla y continuar con lo siguiente, haría que lo que estás haciendo sea tan lento que sea inútil. Entonces, en ese caso, "realmente excepcional" significa, "la situación es tan mala que el rendimiento 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.
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. Estas rutas de código son posiblemente 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.
Mis dos centavos:
I like to use exceptions, because it allows me to program as if no errors will happen. So my code remains readable, not scattered with all kinds of error-handling. Of course, the error handling (exception handling) is moved to the end (the catch block) or is considered the responsability of the calling level.
A great example for me, is either file handling, or database handling. Presume everything is ok, and close your file at the end or if some exception occurs. Or rollback your transaction when an exception occurred.
The problem with exceptions, is that it quickly gets very verbose. While it was meant to allow your code to remain very readable, and just focus on the normal flow of things, but if used consistently almost every function call needs to be wrapped in a try/catch block, and it starts to defeat the purpose.
For a ParseInt as mentioned before, i like the idea of exceptions. Just return the value. If the parameter was not parseable, throw an exception. It makes your code cleaner on the one hand. At the calling level, you need to do something like
try
{
b = ParseInt(some_read_string);
}
catch (ParseIntException &e)
{
// use some default value instead
b = 0;
}
The code is clean. When i get ParseInt like this scattered all over, i make wrapper functions that handle the exceptions and return me default values. P.ej
int ParseIntWithDefault(String stringToConvert, int default_value=0)
{
int result = default_value;
try
{
result = ParseInt(stringToConvert);
}
catch (ParseIntException &e) {}
return result;
}
So to conclude: what i missed througout the discussion was the fact that exceptions allow me to make my code easier/more readable because i can ignore the error conditions more. Problems:
- the exceptions still need to be handled somewhere. Extra problem: c++ does not have the syntax that allows it to specify which exceptions a function might throw (like java does). So the calling level is not aware which exceptions might need to be handled.
- sometimes code can get very verbose, if every function needs to be wrapped in a try/catch block. But sometimes this still makes sense.
So that makes it hard to find a good balance sometimes.
No creo que las excepciones se utilicen en raras ocasiones. Pero.
No todos los equipos y proyectos están listos para usar excepciones. El uso de excepciones requiere una alta calificación de programadores, técnicas especiales y la falta de un gran código heredado que no sea de excepción. Si tiene una gran base de código antigua, casi siempre no es a prueba de excepciones. Estoy seguro de que no quieres volver a escribirlo.
Si vas a usar excepciones extensamente, entonces:
- prepárese para enseñarle a su gente sobre qué excepción es la seguridad
- no deberías usar la gestión de memoria sin formato
- usa RAII extensivamente
Por otro lado, el uso de excepciones en proyectos nuevos con un equipo fuerte puede hacer que el código sea más limpio, más fácil de mantener e incluso más rápido:
- no perderá ni ignorará los errores
- no tiene que escribir esas verificaciones de códigos de retorno, sin saber realmente qué hacer con el código incorrecto a bajo nivel
- cuando se ve obligado a escribir código de excepción, se vuelve más estructurado
No es que rara vez se usen excepciones. Es solo que solo deberían ser arrojados en circunstancias excepcionales. Por ejemplo, si un usuario ingresa la contraseña incorrecta, eso no es excepcional.
La razón es simple: las excepciones salen de una función abruptamente y propagan la pila a un bloque catch
. 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 produce 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ón f
que plantea excepciones a menudo, ahora tenemos que tener cuidado de usar nuestros bloques try
/ catch
alrededor de cada llamada de f
. Esa es una mala interfaz / acoplamiento de implementación.
Realmente no hay consenso Todo el asunto es algo subjetivo, porque la "idoneidad" de lanzar una excepción a menudo es sugerida por 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 excepciones, incluso para errores esperados, como la 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, espacio en disco, el apocalipsis sucedido, etc. Pero no insisto en que esta sea la forma correcta y absoluta de hacerlo. cosas.
Si bien "arrojar excepciones en circunstancias excepcionales" es la respuesta simplista, en realidad puede definir cuáles son esas circunstancias: cuando se satisfacen las condiciones previas, pero las postcondiciones no se pueden cumplir . Esto le permite escribir postcondiciones más estrictas, más estrictas y más ú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.
- Las condiciones previas deben ser verdaderas antes de llamar a una función.
- La poscondición es lo que la función garantiza después de que vuelve .
- La excepción de seguridad establece cómo las excepciones afectan la consistencia interna de una función o estructura de datos, y a menudo se ocupan del comportamiento transmitido desde el exterior (por ejemplo, functor, ctor de un parámetro de plantilla, etc.).
Constructores
Hay muy poco que decir acerca de cada constructor para cada clase que posiblemente pueda escribirse en C ++, pero hay algunas cosas. El principal de ellos es que los objetos construidos (es decir, para los cuales el constructor tuvo éxito al regresar) serán destruidos. No puede modificar esta condición posterior porque el lenguaje asume que es verdadera y llamará a destructores automáticamente. (Técnicamente, puede aceptar la posibilidad de un comportamiento indefinido para el cual el lenguaje no garantiza nada, pero probablemente esté mejor cubierto en otra parte).
La única alternativa para lanzar una excepción cuando un constructor no puede tener éxito es modificar la definición básica de la clase (la "invariante de clase") para permitir estados válidos "nulos" o zombies y así permitir que el constructor "tenga éxito" construyendo un zombie .
Ejemplo 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, siempre se garantiza que puede usarlo inmediatamente después de la construcción. Imagínese si tuviera que escribir código como este ejemplo, y si se olvidó de verificar el estado zombie, obtendría silenciosamente resultados erróneos o dañarí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 la invariante de la clase y la interfaz para una cadena de situación que no puede predecir ni manejarse a sí misma.
Ejemplo de validación de entrada
Vamos a abordar un ejemplo común: validar la entrada del usuario. Solo porque queremos 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 necesita 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 se rompa RAII / SBRM. Un ejemplo en Python que no tiene ese problema y muestra algo que deseo que haya tenido 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 comprobarse estrictamente: infringir una indica siempre una falla lógica, y son responsabilidad de la persona que llama, pero si las comprueba, es conveniente lanzar una excepción. (En algunos casos, es más apropiado devolver basura o bloquear el programa, aunque esas acciones pueden ser terriblemente incorrectas en otros contextos. Otro tema es cómo manejar mejor el comportamiento indefinido ).
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 infracciones de condiciones previas, mientras que el segundo es más adecuado para las violaciones posteriores a la condición.
Todas las reglas generales sobre excepciones se reducen a términos subjetivos. No debe esperar obtener definiciones rápidas y rápidas de cuándo usarlas 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 cubo que "¿cómo sé si este código es de una clase o dos?" Es en parte un tema de estilo, en parte una preferencia. Las excepciones son una herramienta. Se pueden usar y abusar, 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.
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:
El costo de rendimiento de lanzar una excepción es significativo. Aunque el manejo de excepciones estructurado es la forma recomendada de manejar las condiciones de error, asegúrese de usar excepciones solo en circunstancias excepcionales cuando se producen condiciones de error. No use excepciones para el flujo de control regular.
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í, hay muchos desarrolladores de .NET por ahí, así que sentí que valía la pena señalarlo.
EDITAR :
Bien, antes que nada, aclaremos una cosa: no tengo 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 hacer dos puntos:
El afiche solicita un razonamiento objetivo detrás de la sabiduría convencional de que las excepciones deben usarse con moderación. Podemos analizar la legibilidad y el diseño adecuado todo lo que queremos; pero estos son asuntos subjetivos con personas dispuestas a discutir de cualquier lado. Creo que el póster es consciente de esto. El hecho es que el uso de excepciones para controlar el flujo de programas a menudo es una forma ineficiente de hacer las cosas. No, no siempre , pero a menudo . Esta es la razón por la cual es un consejo razonable usar excepciones con moderación, al igual que es un buen consejo comer carne roja o beber vino con moderación.
Hay una diferencia entre optimizar sin una buena razón y escribir un código eficiente. El corolario de esto es que hay una diferencia entre escribir algo que es robusto, si no optimizado, y algo que simplemente es ineficiente. A veces pienso que cuando la gente discute sobre cosas como el manejo de excepciones, en realidad solo están hablando el uno del otro, 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 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 así como:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
Ejemplo 2: verificar la existencia de un archivo
Creo que este escenario es realmente muy común. Ciertamente, parece mucho más "aceptable" para muchas personas, 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 lo suficientemente razonable, ¿verdad? Estamos tratando de abrir un archivo; si no está allí, detectamos esa excepción y tratamos de abrir un archivo diferente ... ¿Qué pasa con 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 rendimiento de estos dos enfoques fuera realmente el mismo, esto realmente sería puramente una cuestión 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 entero adecuado, y luego agregué una cadena entera válida al final. Usando ambos enfoques anteriores, estos fueron mis resultados:
Usando el bloque try
/ catch
: 25.455 segundos
Usando int.TryParse
: 1.637 milisegundos
Para 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 hasta el final. Estos fueron los resultados:
Usando el bloque try
/ catch
: 29.989 segundos
Usando File.Exists
: 22.820 milisegundos
Mucha gente respondería a esto diciendo: "Sí, bueno, arrojar 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 va a 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 tan legibles, si no más.
Es por eso que GetNine()
el ejemplo del método GetNine()
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 .
De nuevo, estos son solo dos ejemplos. Por supuesto , habrá momentos en los que el rendimiento de utilizar excepciones no sea tan grave (el derecho de Pavel; después de todo, depende de la implementación). Todo lo que digo es: enfrentemos los hechos, muchachos; en casos como el anterior, lanzar y atrapar una excepción es análogo a GetNine()
; es solo una forma ineficiente de hacer algo que podría hacerse mejor fácilmente .
Usted está pidiendo un razonamiento como si esta fuera una de esas situaciones en las que todos se subieron a un carro sin saber por qué. Pero, de hecho, la respuesta es obvia, y creo que ya lo sabes. El manejo de excepciones tiene un rendimiento horrendo.
OK, tal vez esté bien para su escenario de negocios en particular, pero en términos relativos , lanzar / atrapar una excepción introduce mucho más sobrecarga de la necesaria en muchos, muchos casos. Tú lo sabes, lo sé: la mayoría de las veces , si usas excepciones para controlar el flujo de programas, 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, se encontraría con un rendimiento bastante aceptable para su aplicación comercial típica. Eso no cambia el hecho de que es una manera terriblemente ineficiente de lograr algo que podría mejorarse mucho más.
Eso es lo que las personas quieren decir cuando hablan de "abuso" de excepción.
Basically, exceptions are an unstructured and hard to understand form of flow control. This is necessary when dealing with error conditions that are not part of the normal program flow, to avoid having error handling logic clutter up the normal flow control of your code too much.
IMHO exceptions should be used when you want to provide a sane default in case the caller neglects to write error handling code, or if the error might best be handled further up the call stack than the immediate caller. The sane default is to exit the program with a reasonable diagnostic error message. The insane alternative is that the program limps along in an erroneous state and crashes or silently produces bad output at some later, harder to diagnose point. If the "error" is enough a normal part of program flow that the caller could not reasonably forget to check for it, then exceptions should not be used.
Exceptions are a very unusual method of flow control compared to the traditional constructs (loops, ifs, functions, etc.) The normal control flow constructs (loops, ifs, function calls, etc.) can handle all the normal situations. If you find yourself reaching for an exception for a routine occurrence, then perhaps you need to consider how your code is structured.
But there are certain types of errors that cannot be handled easy with the normal constructs. Catastrophic failures (like resource allocation failure) can be detected at a low level but probably can''t be handled there, so a simple if-statement is inadequate. These types of failures generally need to be handled at a much higher level (eg, save the file, log the error, quit). Trying to report an error like this through traditional methods (like return values) is tedious and error-prone. Furthermore, it injects overhead into layers of mid-level APIs to handle this bizarre, unusual failure. The overhead distracts client of these APIs and requires them to worry about issues that are beyond their control. Exceptions provide a way to do non-local handling for big errors that''s mostly invisible to all the layers between the detection of the problem and the handler for it.
If a client calls ParseInt
with a string, and the string doesn''t contain an integer, then the immediate caller probably cares about the error and knows what to do about it. So you''d design ParseInt to return a failure code for something like that.
On the other hand, if ParseInt
fails because it couldn''t allocate a buffer because memory is horribly fragmented, then the caller isn''t going to know what to do about that. It would have to bubble this unusual error up and up to some layer that deals with these fundamental failures. That taxes everyone in between (because they have to accommodate the error passing mechanism in their own APIs). An exception makes it possible to skip over those layers (while still ensuring necessary clean-up happens).
When you''re writing low-level code, it can be hard to decide when to use traditional methods and when to throw exceptions. The low-level code has to make the decision (throw or not). But it''s the higher level code that truly knows what''s expected and what''s exceptional.
I prefer to use exceptions as little as possible. Exceptions force the developer to handle some condition that may or may not be a real error. The definition of whether the exception in question is a fatal problem or a problem that must be handled immediately.
The counter argument to that is it just requires lazy people to type more in order to shoot themselves in their feet.
Google''s coding policy says to never use exceptions , especially in C++. Your application either isn''t prepared to handle exceptions or it is. If it isn''t, then the exception will probably propagate it up until your application dies.
It''s never fun to find out some library you have used throws exceptions and you were not prepared to handle them.
I read some of the answers here. I''m still amazed on what all this confusion is about. I strongly disagree with all this exceptions==spagetty code. With confusion I mean, that there are people, which don''t appreciate C++ exception handling. I''m not certain how I learned about C++ exception handling -- but I understood the implications within minutes. This was around 1996 and I was using the borland C++ compiler for OS/2. I never had a problem to decide, when to use exceptions. I usually wrap fallible do-undo actions into C++ classes. Such do-undo actions include:
- creating/destroying a system handle (for files, memory maps, WIN32 GUI handles, sockets, and so on)
- setting/unsetting handlers
- allocating/deallocating memory
- claiming/releasing a mutex
- incrementing/decrementing a reference count
- showing/hiding a window
Than there are functional wrappers. To wrap system calls (which do not fall into the former category) into C++. Eg read/write from/to a file. If something fails, an exception will be thrown, which contains full information about the error.
Then there is catching/rethrowing exceptions to add more information to a failure.
Overall C++ exception handling leads to more clean code. The amount of code is drasticly reduced. Finally one can use a constructor to allocate fallible resources and still maintain a corruption free environment after such a failure.
One can chain such classes into complex classes. Once a constructor of some member/base object is exectued, one can rely on that all other constructors of the same object (executed before) executed successfully.
I think, "use it rarely" ist not the right sentence. I would prefer "throw only in exceptional situations".
Many have explained, why exceptions should not used in normal situations. Exceptions have their right for error handling and purely for error handling.
I will focus on an other point:
An other thing is the performance issue. Compilers struggled long to get them fast. I am not sure, how the exact state is now, but when you use exceptions for control flow, than you will get an other trouble: Your program will become slow!
The reason is, that exceptions are not only very mighty goto-statements, they also have to unwind the stack for all the frames they leave. Thus implicitely also the objects on stack have to be deconstructed and so on. So without be aware of it, one single throw of an exception will really get a whole bunch of mechanics be involved. The processor will have to do a mighty lot.
So you will end up, elegantly burning your processor without knowing.
So: use exceptions only in exceptional cases -- Meaning: When real errors occured!
I use exceptions if:
- an error occured that cannot be recovered from locally AND
- if the error is not recovered from the program should terminate.
If the error can be recovered from (the user entered "apple" instead of a number) then recover (ask for the input again, change to default value, etc.).
If the error cannot be recovered from locally but the application can continue (the user tried to open a file but the file does not exist) then an error code is appropriate.
If the error cannot be recovered from locally and the application cannot continue without recovering (you are out of memory/disk space/etc.), then an exception is the right way to go.
I would say that exceptions are a mechanism to get you out of current context (out of current stack frame in the simplest sense, but it''s more than that) in a safe way. It''s the closest thing structured programming got to a goto. To use exceptions in the way they were intended to be used, you have to have a situation when you can''t continue what you''re doing now, and you can''t handle it at the point where you are now. So, for example, when user''s password is wrong, you can continue by returning false. But if the UI subsystem reports that it can''t even prompt the user, simply returning "login failed" would be wrong. The current level of code simply does not know what to do. So it uses an exception mechanism to delegate the responsibility to someone above who may know what to do.
I''m sorry but the answer is "they are called exceptions for a reason." That explanation is a "rule of thumb". You can''t give a complete set of circumstances under which exceptions should or should not be used because what a fatal exception (English definition) is for one problem domain is normal operating procedure for a different problem domain. Rules of thumb are not designed to be followed blindly. Instead they are designed to guide your investigation of a solution. "They are called exceptions for a reason" tells you that you should determine ahead of time what is a normal error the caller can handle and what is an unusual circumstance the caller cannot handle without special coding (catch blocks).
Just about every rule of programming is really a guideline saying "Don''t do this unless you have a really good reason": "Never use goto", "Avoid global variables", "Regular expressions pre-increment your number of problems by one", etc. Exceptions are no exception....
Legitimate case to throw an exception:
- You try to open a file, it''s not there, a FileNotFoundException is thrown;
Illegitimate case:
- You want to do something only if a file doesn''t exist, you try to open the file, and then add some code to the catch block.
I use exceptions when I want to break the flow of the application up to a certain point . This point is where the catch(...) for that exception is. For example, it''s very common that we have to process a load of projects, and each project should be processed independently of the others. So the loop that process the projects has a try...catch block, and if some exception is thrown during the project processing, everything is rolled back for that project, the error is logged, and the next project is processed. La vida continua.
I think you should use exceptions for things like a file that doesn''t exist, an expression that is invalid, and similar stuff. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there''s an easy/ cheap alternative to it. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there''s an easy/ cheap alternative to it because this sort of logic makes the code hard to understand:
RecordIterator<MyObject> ri = createRecordIterator();
try {
MyObject myobject = ri.next();
} catch(NoSuchElement exception) {
// Object doesn''t exist, will create it
}
This would be better:
RecordIterator<MyObject> ri = createRecordIterator();
if (ri.hasNext()) {
// It exists!
MyObject myobject = ri.next();
} else {
// Object doesn''t exist, will create it
}
COMMENT ADDED TO THE ANSWER:
Maybe my example wasn''t very good - the ri.next() should not throw an exception in the second example, and if it does, there''s something really exceptional and some other action should be taken somewhere else. When the example 1 is heavily used, developers will catch a generic exception instead of the specific one and assume that the exception is due to the error that they''re expecting, but it can be due to something else. In the end, this leads to real exceptions being ignored as exceptions became part of the application flow, and not an exception to it.
The comments on this may add more than my answer itself.
My approach to error handling is that there are three fundamental types of errors:
- An odd situation that can be handled at the error site. This might be if a user inputs an invalid input at a command line prompt. The correct behavior is simply to complain to the user and loop in this case. Another situation might be a divide-by-zero. These situations aren''t really error situations, and are usually caused by faulty input.
- A situation like the previous kind, but one that can''t be handled at the error site. For instance, if you have a function that takes a filename and parses the file with that name, it might not be able to open the file. In this case, it can''t deal with the error. This is when exceptions shine. Rather than use the C approach (return an invalid value as a flag and set a global error variable to indicate the problem), the code can instead throw an exception. The calling code will then be able to deal with the exception - for instance to prompt the user for another filename.
- A situation that Should Not Happen. This is when a class invariant is violated, or a function receives an invalid paramter or the like. This indicates a logic failure within the code. Depending on the level of failure, an exception may be appropriate, or forcing immediate termination may be preferable (as
assert
does). Generally, these situations indicate that something has broken somewhere in the code, and you effectively cannot trust anything else to be correct - there may be rampant memory corruption. Your ship is sinking, get off.
To paraphrase, exceptions are for when you have a problem you can deal with, but you can''t deal with at the place you notice it. Problems you can''t deal with should simply kill the program; problems you can deal with right away should simply be dealt with.
One very practical reason is that when debugging a program I often flip on First Chance Exceptions (Debug -> Exceptions) to debug an application. If there are a lot of exceptions happening it''s very difficult to find where something has gone "wrong".
Also, it leads to some anti-patterns like the infamous "catch throw" and obfuscates the real problems. For more information on that see a blog post I made on the subject.
The purpose of exceptions is to make software fault tolerant. However having to provide a response to every exception thrown by a function leads to suppression. Exceptions are just a formal structure forcing programmers to acknowledge that certain things can go wrong with a routine and that the client programmer needs to be aware of these conditions and cater for them as necessary.
To be honest, exceptions are a kludge added to programming languages to provide developers with some formal requirement that shifts the responsibility of handling error cases from the immediate developer to some future developer.
I believe that a good programming language does not support exceptions as we know them in C++ and Java. You should opt for programming languages that can provide alternative flow for all sorts of return values from functions. The programmer should be responsible for anticipating all forms of outputs of a routine and handle them in a seperate code file if I could have my way.
There''s several reasons in C++.
First, it''s frequently hard to see where exceptions are coming from (since they can be thrown from almost anything) and so the catch block is something of a COME FROM statement. It''s worse than a GO TO, since in a GO TO you know where you''re coming from (the statement, not some random function call) and where you''re going (the label). They''re basically a potentially resource-safe version of C''s setjmp() and longjmp(), and nobody wants to use those.
Second, C++ doesn''t have garbage collection built in, so C++ classes that own resources get rid of them in their destructors. Therefore, in C++ exception handling the system has to run all the destructors in scope. In languages with GC and no real constructors, like Java, throwing exceptions is a lot less burdensome.
Third, the C++ community, including Bjarne Stroustrup and the Standards Committee and various compiler writers, has been assuming that exceptions should be exceptional. In general, it''s not worth going against language culture. The implementations are based on the assumption that exceptions will be rare. The better books treat exceptions as exceptional. Good source code uses few exceptions. Good C++ developers treat exceptions as exceptional. To go against that, you''d want a good reason, and all the reasons I see are on the side of keeping them exceptional.
This is a bad example of using exceptions as control flow:
int getTotalIncome(int incomeType) {
int totalIncome= 0;
try {
totalIncome= calculateIncomeAsTypeA();
} catch (IncorrectIncomeTypeException& e) {
totalIncome= calculateIncomeAsTypeB();
}
return totalIncome;
}
Which is very bad, but you should be writing:
int getTotalIncome(int incomeType) {
int totalIncome= 0;
if (incomeType == A) {
totalIncome= calculateIncomeAsTypeA();
} else if (incomeType == B) {
totalIncome= calculateIncomeAsTypeB();
}
return totalIncome;
}
This second example obviously needs some refactoring (like using the design pattern strategy), but illustrates well that exceptions are not meant for control flow.
Exceptions also have some performance penalties associated, but performance problems should follow the rule: "premature optimization is the root of all evil"
Who said they should be used conservatively ? Just never use exceptions for flow control and thats it. And who cares about the cost of exception when it already thrown ?