¿Por qué es intentar {...} finalmente {...} bueno; intente {…} atrapar {} mal?

201

He visto a personas decir que es una mala forma usar catch sin argumentos, especialmente si ese catch no hace nada:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

Sin embargo, esto se considera buena forma:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

Por lo que puedo decir, la única diferencia entre poner el código de limpieza en un bloque finalmente y poner el código de limpieza después de los bloques try..catch es si tiene declaraciones de retorno en su bloque try (en ese caso, el código de limpieza finalmente será ejecutar, pero el código después del intento ... captura no lo hará).

De lo contrario, ¿qué tiene de especial finalmente?

mbeckish
fuente
77
Antes de intentar atrapar un tigre que no puede manejar, debe documentar sus deseos finales.
El tema de excepciones en la documentación puede dar algunas buenas ideas. También eche un vistazo al ejemplo de Finalmente bloque .
Athafoud

Respuestas:

357

La gran diferencia es que try...catchtragará la excepción, ocultando el hecho de que ocurrió un error. try..finallyejecutará su código de limpieza y luego la excepción continuará, para ser manejada por algo que sepa qué hacer con él.

Khoth
fuente
11
Es probable que cualquier código escrito con la encapsulación en mente solo pueda manejar la excepción en el punto donde se genera. Simplemente pasarlo de nuevo a la pila de llamadas con la desesperada esperanza de que algo más pueda manejar alguna excepción arbitraria es una receta para el desastre.
David Arno
3
En la mayoría de los casos, es más evidente por qué ocurriría una excepción particular desde el nivel de la aplicación (por ejemplo, una determinada configuración) que en el nivel librray de la clase.
Mark Cidade
88
David: Prefiero que el programa falle rápidamente para que pueda conocer el problema en lugar de dejar el programa ejecutándose en un estado desconocido.
Erik Forbes
66
Si su programa está en un estado desconocido después de una excepción, entonces está haciendo mal el código.
Zan Lynx
41
@DavidArno, cualquier código escrito con la encapsulación en mente solo debe manejar excepciones dentro de su alcance. Cualquier otra cosa se debe pasar para que otra persona se encargue. Si tengo una aplicación que obtiene un nombre de archivo de los usuarios, luego lee el archivo y mi lector de archivos recibe una excepción al abrir el archivo, debería pasarlos por alto (o consumir la excepción y lanzar una nueva) para que la aplicación pueda decir , oye, el archivo no se abrió, solicitemos al usuario uno diferente. El lector de archivos no debe poder solicitar a los usuarios ni realizar ninguna otra acción en respuesta. Su único propósito es leer archivos.
iheanyi
62

"Finalmente" es una declaración de "Algo que siempre debe hacer para asegurarse de que el estado del programa sea correcto". Como tal, siempre es una buena forma tener uno, si hay alguna posibilidad de que las excepciones puedan alterar el estado del programa. El compilador también hace todo lo posible para garantizar que se ejecute su código de Finalmente.

"Capturar" es una declaración de "Puedo recuperarme de esta excepción". Solo debe recuperarse de las excepciones que realmente puede corregir: la captura sin argumentos dice "¡Hola, puedo recuperarme de cualquier cosa!", Lo que casi siempre es falso.

Si fuera posible recuperarse de cada excepción, entonces realmente sería una objeción semántica, sobre lo que estás declarando tu intención de ser. Sin embargo, no lo es, y casi con certeza los marcos superiores al suyo estarán mejor equipados para manejar ciertas excepciones. Como tal, use finalmente, haga que su código de limpieza se ejecute de forma gratuita, pero aún así permita que manejadores más expertos manejen el problema.

Adam Wright
fuente
1
Su sentimiento está muy extendido, pero desafortunadamente ignora otro caso importante: invalidar expresamente un objeto cuyos invariantes ya no pueden contener. Un patrón común es que el código adquiera un bloqueo, realice algunos cambios en un objeto y libere el bloqueo. Si se produce una excepción después de realizar algunos pero no todos los cambios, el objeto puede quedar en un estado no válido. Aunque en mi humilde opinión deberían existir mejores alternativas , no conozco un mejor enfoque que detectar cualquier excepción que ocurra mientras el estado del objeto puede ser inválido, invalidar expresamente el estado y volver a lanzar.
supercat
32

Porque cuando esa única línea arroja una excepción, no lo sabrías.

Con el primer bloque de código, la excepción simplemente se absorberá , el programa continuará ejecutándose incluso cuando el estado del programa sea incorrecto.

Con el segundo bloque, la excepción será lanzada y se propaga hacia arriba , pero el reader.Close()todavía se garantiza la ejecución.

Si no se espera una excepción, entonces no intentes el bloque try..catch, será difícil de depurar más tarde cuando el programa entró en mal estado y no tienes idea de por qué.

chakrit
fuente
21

Finalmente se ejecuta pase lo que pase. Entonces, si su bloque try fue exitoso, se ejecutará, si su bloque try falla, entonces ejecutará el bloque catch y luego el bloque finalmente.

Además, es mejor intentar usar la siguiente construcción:

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

Como la declaración de uso se ajusta automáticamente en un intento / finalmente y la secuencia se cerrará automáticamente. (Necesitará poner un try / catch alrededor de la instrucción de uso si realmente quiere capturar la excepción).

Mark Ingram
fuente
55
Esto no es correcto. El uso no ajusta el código con try / catch, debería decir try / finally
pr0nin
8

Si bien los siguientes 2 bloques de código son equivalentes, no son iguales.

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. 'finalmente' es un código que revela la intención. Usted declara al compilador y a otros programadores que este código debe ejecutarse sin importar qué.
  2. Si tiene varios bloques catch y tiene un código de limpieza, finalmente necesita. Sin finalmente, estaría duplicando su código de limpieza en cada bloque catch. (Principio SECO)

Finalmente los bloques son especiales. El CLR reconoce y trata el código dentro de un bloque finalmente separado de los bloques catch, y el CLR hace todo lo posible para garantizar que un bloque finalmente siempre se ejecute. No es solo azúcar sintáctico del compilador.

Robert Paulson
fuente
5

Estoy de acuerdo con lo que parece ser el consenso aquí: una 'captura' vacía es mala porque oculta cualquier excepción que pueda haber ocurrido en el bloque try.

Además, desde el punto de vista de la legibilidad, cuando veo un bloque 'intentar', supongo que habrá una declaración 'catch' correspondiente. Si solo está usando un 'intento' para asegurarse de que los recursos se desasignen en el bloque 'finalmente', puede considerar la declaración 'usar' en su lugar:

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

Puede usar la instrucción 'using' con cualquier objeto que implemente IDisposable. El método dispose () del objeto se llama automáticamente al final del bloque.

Chris Lawlor
fuente
4

Utilizar Try..Catch..Finally , si su método sabe cómo manejar la excepción localmente. La excepción ocurre en Try, Handled in Catch y después de eso, la limpieza se realiza en Finalmente.

En caso de que su método no sepa cómo manejar la excepción pero necesita una limpieza una vez que ha ocurrido, use Try..Finally

De este modo, la excepción se propaga a los métodos de llamada y se maneja si hay alguna declaración Catch adecuada en los métodos de llamada. Si no hay controladores de excepción en el método actual o en ninguno de los métodos de llamada, la aplicación se bloquea.

Por Try..Finallyello se garantiza que las tareas de limpieza local, se hace antes de la propagación de la excepción a los métodos de llamada.

manjuv
fuente
1
Tan básica como es esta respuesta, es absolutamente la mejor. Es bueno tener el hábito de intentar / atrapar / finalmente, incluso si uno de los dos últimos se deja vacío. Hay circunstancias MUY RARAS en las que puede existir un bloque catch y estar vacío, pero al menos si siempre escribe try / catch / finalmente, verá el bloque vacío mientras examina el código. Tener un bloque finalmente vacío es útil de la misma manera. Si necesita una limpieza posterior, o necesita depurar un estado en el momento de la excepción, es increíblemente útil.
Jesse Williams el
3

El bloque try..finally aún arrojará cualquier excepción que se genere. Todasfinally hace es asegurarse de que el código de limpieza se ejecute antes de que se genere la excepción.

El try..catch con una captura vacía consumirá por completo cualquier excepción y ocultará el hecho de que sucedió. El lector estará cerrado, pero no se sabe si sucedió lo correcto. ¿Qué pasa si su intención era escribir i en el archivo? En este caso, no llegará a esa parte del código y myfile.txt estará vacío. ¿Todos los métodos posteriores manejan esto correctamente? Cuando vea el archivo vacío, ¿podrá adivinar correctamente que está vacío porque se produjo una excepción? Es mejor lanzar la excepción y dejar que se sepa que estás haciendo algo mal.

Otra razón es el try..catch hecho así es completamente incorrecto. Lo que estás diciendo al hacer esto es: "No importa lo que pase, puedo manejarlo". ¿Qué tal StackOverflowException, puedes limpiar después de eso? ¿Qué hay de OutOfMemoryException? En general, solo debe manejar las excepciones que espera y sabe cómo manejar.

OwenP
fuente
2

Si no sabe qué tipo de excepción atrapar o qué hacer con él, no tiene sentido tener una declaración catch. Simplemente debe dejarlo para que una persona que llama desde arriba tenga más información sobre la situación para saber qué hacer.

Todavía debe tener una declaración finalmente allí en caso de que haya una excepción, para que pueda limpiar los recursos antes de que esa excepción sea lanzada a la persona que llama.

Mark Cidade
fuente
2

Desde una perspectiva de legibilidad, es más explícito decirle a los futuros lectores de códigos "estas cosas aquí son importantes, deben hacerse sin importar lo que pase". Esto es bueno.

Además, las declaraciones de captura vacías tienden a tener un cierto "olor". Pueden ser una señal de que los desarrolladores no están pensando en las diversas excepciones que pueden ocurrir y cómo manejarlas.

Ryan
fuente
2

Finalmente es opcional: no hay ninguna razón para tener un bloque "Finalmente" si no hay recursos para limpiar.

Guy Starbuck
fuente
2

Tomado de: aquí

El aumento y la captura de excepciones no deberían ocurrir rutinariamente como parte de la ejecución exitosa de un método. Al desarrollar bibliotecas de clases, el código del cliente debe tener la oportunidad de probar una condición de error antes de emprender una operación que pueda generar una excepción. Por ejemplo, System.IO.FileStream proporciona una propiedad CanRead que se puede verificar antes de llamar al método Read, evitando que se genere una posible excepción, como se ilustra en el siguiente fragmento de código:

Dim str As Stream = GetStream () If (str.CanRead) Then 'code to read stream End If

La decisión de verificar el estado de un objeto antes de invocar un método particular que puede generar una excepción depende del estado esperado del objeto. Si se crea un objeto FileStream utilizando una ruta de archivo que debería existir y un constructor que debería devolver un archivo en modo de lectura, no es necesario verificar la propiedad CanRead; la imposibilidad de leer el FileStream sería una violación del comportamiento esperado de las llamadas al método realizadas, y se debería plantear una excepción. Por el contrario, si se documenta que un método devuelve una referencia de FileStream que puede o no ser legible, es recomendable verificar la propiedad CanRead antes de intentar leer los datos.

Para ilustrar el impacto en el rendimiento que puede causar el uso de una técnica de codificación de "ejecución hasta excepción", el rendimiento de un lanzamiento, que arroja una InvalidCastException si falla el lanzamiento, se compara con el C # como operador, que devuelve nulos si falla un lanzamiento. El rendimiento de las dos técnicas es idéntico para el caso donde el lanzamiento es válido (ver Prueba 8.05), pero para el caso donde el lanzamiento es inválido, y usar un lanzamiento causa una excepción, usar un lanzamiento es 600 veces más lento que usar el lanzamiento como operador (ver Prueba 8.06). El impacto de alto rendimiento de la técnica de lanzamiento de excepciones incluye el costo de asignación, lanzamiento y captura de la excepción y el costo de la posterior recolección de basura del objeto de excepción, lo que significa que el impacto instantáneo de lanzar una excepción no es tan alto. A medida que se lanzan más excepciones,

SpoiledTechie.com
fuente
2
Scott: si el texto que has citado arriba está detrás del muro de pago de expertsexchange.com, probablemente no deberías publicarlo aquí. Puedo estar equivocado en esto, pero apuesto a que no es una buena idea.
Onorio Catenacci
2

Es una mala práctica agregar una cláusula catch solo para volver a lanzar la excepción.

Bastien Vandamme
fuente
2

Si lee C # para programadores , comprenderá que el bloque finalmente fue diseñado para optimizar una aplicación y evitar pérdidas de memoria.

El CLR no elimina por completo las fugas ... pueden producirse fugas de memoria si el programa mantiene inadvertidamente referencias a objetos no deseados

Por ejemplo, cuando abre una conexión de archivo o base de datos, su máquina asignará memoria para atender esa transacción, y esa memoria se mantendrá no a menos que se haya ejecutado el comando de eliminación o cierre. pero si durante la transacción se produjo un error, el comando en curso se cancelará a menos que se encuentre dentro del try.. finally..bloque.

catchera diferente finallyen el sentido de que, catch fue diseñado para darle forma de manejar / gestionar o interpretar el error por sí mismo. Piense en ello como una persona que le dice "oye, atrapé a algunos tipos malos, ¿qué quieres que les haga?" mientrasfinally fue diseñado para asegurarse de que sus recursos se ubicaron correctamente. Piense en alguien que si hay o no algunos tipos malos se asegurará de que su propiedad aún esté segura.

Y deberías permitir que esos dos trabajen juntos para siempre.

por ejemplo:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}
dr.Crow
fuente
1

Finalmente, puede limpiar recursos, incluso si su declaración catch arroja la excepción al programa de llamada. Con su ejemplo que contiene la declaración catch vacía, hay poca diferencia. Sin embargo, si en su captura, realiza un procesamiento y arroja el error, o incluso simplemente no tiene ninguna captura, el finalmente todavía se ejecutará.

Kibbee
fuente
1

Bueno, para empezar, es una mala práctica detectar excepciones que no te molestas en manejar. Consulte el Capítulo 5 sobre .Net Performance al mejorar el rendimiento y la escalabilidad de las aplicaciones .NET . Nota al margen, probablemente debería estar cargando la secuencia dentro del bloque de prueba, de esa manera, puede atrapar la excepción pertinente si falla. Crear la secuencia fuera del bloque try frustra su propósito.

Factor místico
fuente
0

Probablemente, entre muchas razones, las excepciones son muy lentas de ejecutar. Puede paralizar fácilmente sus tiempos de ejecución si esto sucede mucho.

muchoffreetime
fuente
0

El problema con los bloques try / catch que capturan todas las excepciones es que su programa ahora está en un estado indeterminado si ocurre una excepción desconocida. Esto va completamente en contra de la regla de falla rápida: no desea que su programa continúe si ocurre una excepción. El try / catch anterior incluso capturaría OutOfMemoryExceptions, pero ese es definitivamente un estado en el que su programa no se ejecutará.

Los bloques de prueba / finalmente le permiten ejecutar código de limpieza sin fallar rápidamente. Para la mayoría de las circunstancias, solo desea capturar todas las excepciones a nivel global, de modo que pueda iniciar sesión y luego salir.

David Mohundro
fuente
0

La diferencia efectiva entre sus ejemplos es insignificante siempre que no se produzcan excepciones.

Sin embargo, si se lanza una excepción mientras está en la cláusula 'try', el primer ejemplo la tragará por completo. El segundo ejemplo elevará la excepción al siguiente paso en la pila de llamadas, por lo que la diferencia en los ejemplos indicados es que uno oculta por completo cualquier excepción (primer ejemplo) y el otro (segundo ejemplo) retiene información de excepción para un posible manejo posterior mientras aún ejecutando el contenido en la cláusula 'finalmente'.

Si, por ejemplo, tuviera que poner código en la cláusula 'catch' del primer ejemplo que arrojó una excepción (ya sea la que se planteó inicialmente o una nueva), el código de limpieza del lector nunca se ejecutaría. Finalmente se ejecuta independientemente de lo que ocurra en la cláusula 'catch'.

Entonces, la principal diferencia entre 'atrapar' y 'finalmente' es que el contenido del bloque 'finalmente' (con algunas raras excepciones) puede considerarse garantizado para ejecutarse, incluso ante una excepción inesperada, mientras que cualquier código sigue una cláusula de "captura" (pero fuera de una cláusula de "finalmente") no conllevaría tal garantía.

Por cierto, Stream y StreamReader implementan IDisposable y se pueden envolver en un bloque de "uso". Los bloques 'usar' son el equivalente semántico de try / finally (no 'catch'), por lo que su ejemplo podría expresarse más tersamente como:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

... que cerrará y eliminará la instancia de StreamReader cuando salga del alcance. Espero que esto ayude.

Jared
fuente
0

intentar {…} catch {} no siempre es malo. No es un patrón común, pero tiendo a usarlo cuando necesito cerrar recursos sin importar qué, como cerrar un (posiblemente) enchufes abiertos al final de un hilo.

Martin Liesén
fuente