La forma clásica de programar es con try ... catch
. ¿Cuándo es apropiado usar try
sin catch
?
En Python, lo siguiente parece legal y puede tener sentido:
try:
#do work
finally:
#do something unconditional
Sin embargo, el código no hizo catch
nada. Del mismo modo, se podría pensar en Java que sería de la siguiente manera:
try {
//for example try to get a database connection
}
finally {
//closeConnection(connection)
}
Se ve bien y de repente no tengo que preocuparme por los tipos de excepción, etc. Si es una buena práctica, ¿cuándo es una buena práctica? Alternativamente, ¿cuáles son las razones por las cuales esto no es una buena práctica o no es legal? (No compilé la fuente. Lo pregunto, ya que podría ser un error de sintaxis para Java. Verifiqué que Python seguramente compila).
Un problema relacionado con el que me he encontrado es este: sigo escribiendo la función / método, al final del cual debe devolver algo. Sin embargo, puede estar en un lugar que no debe ser alcanzado y debe ser un punto de retorno. Entonces, incluso si manejo las excepciones anteriores, todavía estoy regresando NULL
o una cadena vacía en algún punto del código que no se debe alcanzar, a menudo al final del método / función. Siempre he logrado reestructurar el código para que no tenga que hacerlo return NULL
, ya que parece que no parece una buena práctica.
fuente
try/catch
no es "la forma clásica de programar". Es la forma clásica de programar en C ++ , porque C ++ carece de una construcción adecuada de prueba / finalmente, lo que significa que debe implementar cambios de estado reversibles garantizados utilizando hacks feos que involucran RAII. Pero los lenguajes OO decentes no tienen ese problema, ya que proporcionan prueba / finalmente. Se usa para un propósito muy diferente al de try / catch.Respuestas:
Depende de si puede lidiar con las excepciones que se pueden generar en este momento o no.
Si puede manejar las excepciones localmente, debería hacerlo, y es mejor manejar el error lo más cerca posible de donde se genera.
Si no puede manejarlos localmente, simplemente tener un
try / finally
bloque es perfectamente razonable, suponiendo que haya algún código que deba ejecutar independientemente de si el método tuvo éxito o no. Por ejemplo ( del comentario de Neil ), abrir un flujo y luego pasarlo a un método interno para cargarlo es un excelente ejemplo de cuándo lo necesitaríatry { } finally { }
, utilizando la cláusula final para garantizar que el flujo se cierre independientemente del éxito o fallo de la lectura.Sin embargo, aún necesitará un controlador de excepciones en algún lugar de su código, a menos que, por supuesto, desee que su aplicación se bloquee por completo. Depende de la arquitectura de su aplicación exactamente dónde está ese controlador.
fuente
try { } finally { }
, aprovechando la cláusula final para garantizar que la secuencia finalmente se cierre independientemente del éxito / fracaso.El
finally
bloque se utiliza para el código que siempre debe ejecutarse, independientemente de si se produjo una condición de error (excepción) o no.El código en el
finally
bloque se ejecuta después detry
que se completa el bloque y, si se produce una excepción detectada, después de que secatch
completa el bloque correspondiente . Siempre se ejecuta, incluso si se produjo una excepción no detectada en el bloquetry
ocatch
.El
finally
bloque se usa generalmente para cerrar archivos, conexiones de red, etc. que se abrieron en eltry
bloque. La razón es que el archivo o la conexión de red deben cerrarse, ya sea que la operación con ese archivo o conexión de red haya tenido éxito o haya fallado.Se debe tener cuidado en el
finally
bloque para asegurarse de que no arroje una excepción. Por ejemplo, asegúrese doblemente de verificar todas las variablesnull
, etc.fuente
try-finally
se pueden reemplazar con unawith
declaración.try/finally
construcción. C # hasusing
, Python haswith
, etc.using
ytry-finally
, ya queDispose
elusing
bloque no llamará al método si ocurre una excepción en elIDisposable
constructor del objeto.try-finally
le permite ejecutar código incluso si el constructor del objeto arroja una excepción.Un ejemplo donde intentar ... finalmente sin una cláusula catch es apropiado (y aún más, idiomático ) en Java es el uso de Lock en el paquete de bloqueos de utilidades concurrentes.
fuente
l.lock()
probarlo?try{ l.lock(); }finally{l.unlock();}
try
en este fragmento se pretende envolver el acceso a los recursos, por qué contaminarlo con algo que no está relacionado con esol.lock()
falla, elfinally
bloque aún se ejecutará sil.lock()
está dentro deltry
bloque. Si lo hace como sugiere el mosquito, elfinally
bloqueo solo se ejecutará cuando sepamos que se adquirió el bloqueo.En un nivel básico
catch
yfinally
resolver dos problemas relacionados pero diferentes:catch
se usa para manejar un problema que fue informado por el código que llamófinally
se usa para limpiar datos / recursos que el código actual creó / modificó, sin importar si ocurrió un problema o noEntonces, ambos están relacionados de alguna manera con problemas (excepciones), pero eso es casi todo lo que tienen en común.
Una diferencia importante es que el
finally
bloque debe estar en el mismo método donde se crearon los recursos (para evitar pérdidas de recursos) y no se puede colocar en un nivel diferente en la pila de llamadas.Sin
catch
embargo, el asunto es diferente: el lugar correcto depende de dónde puede manejar la excepción. No sirve de nada detectar una excepción en un lugar donde no se puede hacer nada al respecto, por lo tanto, a veces es mejor simplemente dejarla pasar.fuente
finally
una declaración de prueba adjunta (estática o dinámica) ... y aún así estar 100% a prueba de fugas.@yfeldblum tiene la respuesta correcta: try-finally sin una declaración catch generalmente debe reemplazarse con un lenguaje apropiado.
En C ++, está utilizando RAII y constructores / destructores; en Python es una
with
declaración; y en C #, es unausing
declaración.Casi siempre son más elegantes porque el código de inicialización y finalización se encuentra en un lugar (el objeto abstraído) en lugar de en dos lugares.
fuente
En muchos idiomas, una
finally
declaración también se ejecuta después de la declaración de devolución. Esto significa que puede hacer algo como:Que libera los recursos independientemente de cómo se terminó el método con una excepción o una declaración de devolución regular.
Si esto es bueno o malo está en debate, pero
try {} finally {}
no siempre se limita al manejo de excepciones.fuente
Podría invocar la ira de Pythonistas (no sé, ya que no uso mucho Python) o programadores de otros idiomas con esta respuesta, pero en mi opinión, la mayoría de las funciones no deberían tener un
catch
bloqueo, idealmente hablando. Para mostrar por qué, permítanme comparar esto con la propagación manual del código de error del tipo que tenía que hacer cuando trabajaba con Turbo C a fines de los 80 y principios de los 90.Entonces, digamos que tenemos una función para cargar una imagen o algo así en respuesta a un usuario que selecciona un archivo de imagen para cargar, y esto está escrito en C y ensamblado:
Omití algunas funciones de bajo nivel, pero podemos ver que he identificado diferentes categorías de funciones, codificadas por colores, en función de las responsabilidades que tienen con respecto al manejo de errores.
Punto de falla y recuperación
Ahora nunca fue difícil escribir las categorías de funciones que llamo el "posible punto de fallas" (las que
throw
, es decir) y las funciones de "recuperación e informe de errores" (las quecatch
, es decir).Esas funciones fueron siempre trivial para escribir correctamente antes de que el manejo de excepciones ya estaba disponible una función que se puede ejecutar en un fallo externo, como no asignar memoria, simplemente puede devolver una
NULL
o0
o-1
o establecer un código de error global o algo a este efecto. Y la recuperación / informe de errores siempre fue fácil, ya que una vez que bajó por la pila de llamadas hasta un punto en el que tenía sentido recuperarse e informar fallas, simplemente toma el código y / o mensaje de error y se lo informa al usuario. Y, naturalmente, una función en la hoja de esta jerarquía que nunca puede fallar, sin importar cómo se cambie en el futuro (Convert Pixel
) es muy simple de escribir correctamente (al menos con respecto al manejo de errores).Propagación de error
Sin embargo, las funciones tediosas propensas a errores humanos fueron los propagadores de errores , los que no se encontraron directamente con fallas, sino que se llamaron funciones que podrían fallar en algún lugar más profundo de la jerarquía. En ese punto,
Allocate Scanline
podría tener que manejar una fallamalloc
y luego devolver un error aConvert Scanlines
, luegoConvert Scanlines
tendría que verificar ese error y pasarlo aDecompress Image
, luegoDecompress Image->Parse Image
, yParse Image->Load Image
, yLoad Image
al comando de final de usuario donde finalmente se informa el error .Aquí es donde muchos humanos cometen errores, ya que solo se necesita un propagador de errores para no verificar y transmitir el error para que toda la jerarquía de funciones se derrumbe cuando se trata de manejar adecuadamente el error.
Además, si las funciones devuelven códigos de error, prácticamente perdemos la capacidad, por ejemplo, en el 90% de nuestra base de código, de devolver valores de interés en caso de éxito, ya que muchas funciones tendrían que reservar su valor de retorno para devolver un código de error en fracaso .
Reducción del error humano: códigos de error globales
Entonces, ¿cómo podemos reducir la posibilidad de error humano? Aquí incluso podría invocar la ira de algunos programadores de C, pero una mejora inmediata en mi opinión es usar códigos de error globales , como OpenGL con
glGetError
. Esto al menos libera las funciones para devolver valores significativos de interés en el éxito. Hay formas de hacer que este hilo sea seguro y eficiente donde el código de error se localiza en un hilo.También hay algunos casos en los que una función puede encontrarse con un error, pero es relativamente inofensivo que continúe un poco más antes de que regrese prematuramente como resultado de descubrir un error anterior. Esto permite que tal cosa suceda sin tener que verificar si hay errores en el 90% de las llamadas a funciones realizadas en cada función, por lo que aún puede permitir el manejo adecuado de errores sin ser tan meticuloso.
Reducción de errores humanos: manejo de excepciones
Sin embargo, la solución anterior todavía requiere tantas funciones para tratar el aspecto del flujo de control de la propagación de errores manual, incluso si pudiera haber reducido el número de líneas
if error happened, return error
de código de tipo manual . No lo eliminaría por completo, ya que a menudo necesitaría al menos un lugar para verificar un error y regresar para casi todas las funciones de propagación de errores. Entonces esto es cuando el manejo de excepciones entra en escena para salvar el día (más o menos).Pero el valor del manejo de excepciones aquí es liberar la necesidad de lidiar con el aspecto del flujo de control de la propagación de errores manual. Eso significa que su valor está vinculado a la capacidad de evitar tener que escribir una gran cantidad de
catch
bloques en toda su base de código. En el diagrama anterior, el único lugar que debería tener uncatch
bloque esLoad Image User Command
donde se informa el error. Idealmente, nada más debería tenercatch
algo porque, de lo contrario, comienza a ser tan tedioso y propenso a errores como el manejo de códigos de error.Entonces, si me preguntas, si tienes una base de código que realmente se beneficia del manejo de excepciones de una manera elegante, debería tener el número mínimo de
catch
bloques (por mínimo no me refiero a cero, sino más como uno por cada efecto único) operación del usuario final que podría fallar, y posiblemente incluso menos si todas las operaciones del usuario de gama alta se invocan a través de un sistema de comando central).Limpieza de recursos
Sin embargo, el manejo de excepciones solo resuelve la necesidad de evitar tratar manualmente los aspectos del flujo de control de la propagación de errores en rutas excepcionales separadas de los flujos normales de ejecución. A menudo, una función que sirve como un propagador de errores, incluso si lo hace automáticamente ahora con EH, podría adquirir algunos recursos que necesita destruir. Por ejemplo, dicha función podría abrir un archivo temporal que necesita cerrar antes de regresar de la función sin importar qué, o bloquear un mutex que necesita desbloquear sin importar qué.
Para esto, podría invocar la ira de muchos programadores de todo tipo de lenguajes, pero creo que el enfoque de C ++ es ideal. El lenguaje introduce destructores que se invocan de manera determinista en el instante en que un objeto sale del alcance. Debido a esto, el código C ++ que, por ejemplo, bloquea un mutex a través de un objeto mutex con un destructor no necesita desbloquearlo manualmente, ya que se desbloqueará automáticamente una vez que el objeto salga del alcance sin importar lo que suceda (incluso si se produce una excepción encontrado). Por lo tanto, realmente no es necesario que un código C ++ bien escrito tenga que lidiar con la limpieza de recursos locales.
En los idiomas que carecen de destructores, es posible que necesiten usar un
finally
bloque para limpiar manualmente los recursos locales. Dicho esto, aún supera tener que ensuciar su código con propagación de errores manual siempre que no tenga que hacercatch
excepciones en todo el lugar.Revertir los efectos secundarios externos
Este es el problema conceptual más difícil de resolver. Si alguna función, ya sea un propagador de error o un punto de falla, causa efectos secundarios externos, entonces debe revertir o "deshacer" esos efectos secundarios para devolver el sistema a un estado como si la operación nunca ocurriera, en lugar de un " medio válido "estado en el que la operación hasta la mitad tuvo éxito. No conozco ningún lenguaje que facilite mucho este problema conceptual, excepto los que simplemente reducen la necesidad de que la mayoría de las funciones causen efectos secundarios externos en primer lugar, como los lenguajes funcionales que giran en torno a la inmutabilidad y las estructuras de datos persistentes.
Podría
finally
decirse que esta es una de las soluciones más elegantes para el problema en los lenguajes que giran en torno a la mutabilidad y los efectos secundarios, porque a menudo este tipo de lógica es muy específica para una función en particular y no se corresponde tan bien con el concepto de "limpieza de recursos". ". Y recomiendo usarlofinally
libremente en estos casos para asegurarse de que su función revierta los efectos secundarios en los idiomas que lo admiten, independientemente de si necesita o no uncatch
bloqueo (y de nuevo, si me pregunta, el código bien escrito debe tener la cantidad mínima decatch
bloques, y todos loscatch
bloques deben estar en lugares donde tenga más sentido como en el diagrama de arribaLoad Image User Command
).Lenguaje soñado
Sin embargo, IMO
finally
es casi ideal para la reversión de efectos secundarios, pero no del todo. Necesitamos introducir unaboolean
variable para revertir efectivamente los efectos secundarios en el caso de una salida prematura (de una excepción lanzada o de otra manera), así:Si alguna vez pudiera diseñar un lenguaje, mi forma ideal de resolver este problema sería la siguiente: automatizar el código anterior:
... con destructores para automatizar la limpieza de los recursos locales, haciendo que solo lo necesitemos
transaction
,rollback
ycatch
(aunque todavía podría querer agregarfinally
, por ejemplo, trabajar con recursos C que no se limpian solos). Sin embargo,finally
con unaboolean
variable es lo más parecido a hacer esto directo que he encontrado hasta ahora sin el lenguaje de mis sueños. La segunda solución más sencilla que he encontrado para esto son los protectores de alcance en lenguajes como C ++ y D, pero siempre encontré los protectores de alcance un poco incómodos conceptualmente, ya que desdibuja la idea de "limpieza de recursos" y "inversión de efectos secundarios". En mi opinión, esas son ideas muy distintas que deben abordarse de manera diferente.Mi pequeño sueño de un lenguaje también giraría en gran medida en torno a la inmutabilidad y las estructuras de datos persistentes para que sea mucho más fácil, aunque no obligatorio, escribir funciones eficientes que no tengan que copiar en profundidad estructuras de datos masivas en su totalidad a pesar de que la función causa sin efectos secundarios.
Conclusión
De todos modos, con mis divagaciones a un lado, creo que su
try/finally
código para cerrar el zócalo está bien y es excelente teniendo en cuenta que Python no tiene el equivalente C ++ de destructores, y personalmente creo que debería usarlo libremente para lugares que necesitan revertir los efectos secundarios y minimice la cantidad de lugares donde tiene que ircatch
a los lugares donde tiene más sentido.fuente
Se recomienda encarecidamente detectar errores / excepciones y manejarlos de manera ordenada, incluso si no es obligatorio.
La razón por la que digo esto es porque creo que cada desarrollador debe conocer y abordar el comportamiento de su aplicación; de lo contrario, no ha completado su trabajo de manera adecuada. No existe una situación en la que un bloque try-finally sustituya al bloque try-catch-finally.
Le daré un ejemplo simple: suponga que ha escrito el código para cargar archivos en el servidor sin detectar excepciones. Ahora, si por alguna razón falla la carga, el cliente nunca sabrá qué salió mal. Pero, si ha detectado la excepción, puede mostrar un mensaje de error claro que explique qué salió mal y cómo puede remediarlo el usuario.
Regla de oro: siempre atrapa la excepción, porque adivinar lleva tiempo
fuente
catch
siempre debe haber una cláusula siempre que haya untry
. Los contribuyentes más experimentados, incluido yo, creen que este es un mal consejo. Tu razonamiento es defectuoso. El método que contienetry
no es el único lugar posible donde se puede detectar la excepción. A menudo es más simple y mejor permitir capturas e informar excepciones desde el nivel más alto, en lugar de duplicar cláusulas de captura en todo el código.