Argumentos a favor o en contra del uso de Try / Catch como operadores lógicos [cerrado]

36

Acabo de descubrir un código encantador en la aplicación de nuestra compañía que usa bloques Try-Catch como operadores lógicos.
Es decir, "haz algo de código, si eso arroja este error, haz este código, pero si eso arroja este error, haz esto en tercer lugar".
Utiliza "Finalmente" como la declaración "else" que aparece.
Sé que esto está mal intrínsecamente, pero antes de ir a pelear, esperaba algunos argumentos bien pensados.
Y oye, si tienes argumentos para el uso de Try-Catch de esta manera, por favor dilo.

Para cualquiera que se pregunte, el lenguaje es C # y el código en cuestión tiene más de 30 líneas y está buscando excepciones específicas, no está manejando TODAS las excepciones.

James P. Wright
fuente
13
Solo tengo argumentos en contra de esta práctica, y como ya parece convencido de que es una mala idea, no me molestaré en publicarlos.
FrustratedWithFormsDesigner
15
@FrustratedWIthFormsDesigner: Ese es un mal razonamiento. ¿Qué pasa con las personas que no están convencidas? ¿Qué pasa con mi pregunta real donde pregunto específicamente por razones ya que realmente no puedo decir "por qué" solo que sé que está mal?
James P. Wright
3
Mis opiniones dependen en gran medida de lo que realmente hace el código en cuestión. Hay cosas que no pueden verificarse de antemano (o permitir condiciones de carrera cuando se intenta, como muchas operaciones de archivo; en el retraso entre la verificación y la operación, cualquier cosa puede sucederle al archivo) y deben ser try'd'. No todos los casos excepcionales que justifican una excepción en general tienen que ser fatales en este caso específico. Entonces, ¿podría hacerlo de una manera más simple, igual o más robusta sin usar excepciones?
11
Espero que realmente no usen el bloque "finalmente" como "otro" porque el bloque "finalmente" siempre se ejecuta después del código anterior, independientemente de las excepciones que se generen.
jimreed
2
¿Qué idioma están usando? Está perfectamente bien en, digamos, OCaml, donde la biblioteca estándar arroja excepciones de forma rutinaria. El manejo de excepciones es muy barato allí. Pero no será tan eficiente en CLI o JVM.
SK-logic

Respuestas:

50

El manejo de excepciones tiende a ser una forma costosa de manejar el control de flujo (ciertamente para C # y Java).

El tiempo de ejecución hace mucho trabajo cuando se construye un objeto de excepción: obtener el seguimiento de la pila, averiguar dónde se maneja la excepción y más.

Todo esto cuesta en memoria y recursos de CPU que no necesitan expandirse si se utilizan declaraciones de control de flujo para el control de flujo.

Además, hay un problema semántico. Las excepciones son para situaciones excepcionales, no para el control de flujo normal. Uno debe usar el manejo de excepciones para manejar situaciones inesperadas / no anticipadas, no como un flujo de programa normal, porque de lo contrario, una excepción no detectada le dirá mucho menos.

Aparte de estos dos, está la cuestión de que otros lean el código. Usar excepciones de esa manera no es algo que la mayoría de los programadores esperarán, por lo que la legibilidad y la comprensión de su código se ven afectados. Cuando uno ve "Excepción", uno piensa: algo malo sucedió, algo que no se supone que suceda normalmente. Entonces, usar excepciones de esta manera es simplemente confuso.

Oded
fuente
1
Un buen punto sobre el rendimiento, aunque esto también dependerá de los detalles de la plataforma.
FrustratedWithFormsDesigner
44
Si ese es el único inconveniente, bueno, gracias: renunciaré al rendimiento cualquier día si me compra un mejor código (excepto en rutas críticas de rendimiento, eso es; pero afortunadamente eso es solo el 20% de todo el código, incluso en aplicaciones que generalmente son de rendimiento -crítico).
55
@delnan - No, no es el único inconveniente.
Oded el
2
Aparte de la palabra no anticipada, estoy de acuerdo con tu publicación. Hay momentos en su código donde ocurren excepciones. La mayoría de ellos deben ser predecibles, como fallas de conexión con la base de datos o una llamada de servicio, o en cualquier momento que se trate de código no administrado. Captura problemas fuera de su control y trata con ellos. Pero estoy de acuerdo en que nunca debe tener control de flujo basado en excepciones que puede evitar dentro de su código.
SoylentGray
44
"El manejo de excepciones tiende a ser una forma costosa de manejar el control de flujo". Falso para Python.
S.Lott
17

Acabo de descubrir un código encantador en la aplicación de nuestra compañía que usa bloques Try-Catch como operadores lógicos. Es decir, "haz algo de código, si eso arroja este error, haz este código, pero si eso arroja este error, haz esta tercera cosa". Utiliza "Finalmente" como la declaración "else" que aparece. Sé que esto es inherentemente incorrecto ...

¿Como sabes eso? Renuncié a todo ese tipo de "conocimiento" y ahora solo creo que el código más simple es el mejor. Suponga que desea convertir una cadena a Opcional que está vacía si falla el análisis. No hay nada malo con:

try { 
    return Optional.of(Long.valueOf(s)); 
} catch (NumberFormatException) { 
    return Optional.empty(); 
}

Estoy completamente en desacuerdo con la interpretación habitual de "Las excepciones son para condiciones excepcionales". Cuando una función no puede devolver un valor utilizable o un método no puede cumplir con sus condiciones posteriores, arroje una excepción. No importa con qué frecuencia se lanzan estas excepciones hasta que se demuestre un problema de rendimiento.

Las excepciones simplifican el código al permitir la separación del manejo de errores del flujo normal. Simplemente escriba el código más simple posible, y si es más fácil usar try-catch o lanzar una excepción, hágalo.

Las excepciones simplifican las pruebas al reducir el número de rutas a través del código. Una función sin ramas completará o lanzará una excepción. Una función con múltiples ifdeclaraciones para verificar los códigos de error tiene muchas rutas posibles. Es muy fácil equivocarse en una de las condiciones u olvidarla por completo, de modo que se ignora alguna condición de error.

kevin cline
fuente
44
Creo que este es realmente un buen argumento. Su código es limpio y conciso y tiene sentido en lo que está haciendo. Esto es realmente similar a lo que está sucediendo en el código que estoy viendo, solo que es mucho más complejo, está anidado y abarca más de 30 líneas de código.
James P. Wright
@ James: parece que el código estaría bien si extraes algunos métodos más pequeños.
Kevin Cline
1
Se podría argumentar que Java realmente podría usar algo más como TryParse de C #. En C # ese código sería int i; if(long.TryParse(s, out i)) return i; else return null;. TryParse devuelve falso si no se pudo analizar la cadena. Si se puede analizar, establece el argumento de salida a los resultados del análisis y devuelve verdadero. No se producen excepciones (incluso internamente en TryParse) y, especialmente, no hay excepciones que sean de un tipo que signifique un error del programador, como FormatExceptionsiempre indica .NET .
Kevin Cathcart
1
@Kevin Cathcart, pero ¿por qué es eso mejor? el código que detecta la excepción NumberFormatException es mucho más obvio para mí que verificar el resultado de TryParse para un valor nulo.
Winston Ewert
1
@ChrisMiskowiec, sospecho que cuál te parece más claro es una cuestión de lo que estás acostumbrado. Encuentro que atrapar la excepción es mucho más fácil de seguir y luego buscar valores mágicos. Pero eso es todo subjetivo. Objetivamente, creo que deberíamos preferir excepciones porque tienen un valor predeterminado sensato (bloquear el programa), en lugar de un valor predeterminado que oculta el error (continúe como si no hubiera pasado nada). Es cierto que .NET funciona mal con excepciones. Pero ese es el resultado del diseño de .NET, no la naturaleza de las excepciones. Si en .NET, sí, tienes que hacer eso. Pero, en principio, las excepciones son mejores.
Winston Ewert
12

El trabajo de depuración y mantenimiento es muy difícil cuando el flujo de control se realiza utilizando excepciones.

Inherentemente, las excepciones están diseñadas para ser un mecanismo para alterar el flujo de control normal de su programa, de realizar actividades inusuales, causando efectos secundarios inusuales, como una forma de salir de un vínculo particularmente apretado que no se puede manejar con menos complicaciones. medio. Las excepciones son excepcionales. Esto significa que, dependiendo del entorno particular en el que esté trabajando, el uso de una excepción para el flujo de control regular puede causar:

  • Ineficiencia Los aros adicionales que el entorno tiene que atravesar para realizar de manera segura los cambios de contexto relativamente difíciles requeridos para las excepciones requieren instrumentación y recursos.

  • Dificultades de depuración A veces, la información útil (cuando se intenta depurar un programa) se arroja por la ventana cuando se producen excepciones. Puede perder la noción del estado o historial del programa que sea relevante para comprender el comportamiento en tiempo de ejecución

  • Problemas de mantenimiento El flujo de ejecución es difícil de seguir a través de saltos de excepción. Más allá de eso, se puede generar una excepción desde el código de tipo recuadro negro, que puede no comportarse de manera fácil de entender cuando se lanza una excepción.

  • Decisiones de diseño deficientes Los programas creados de esta manera fomentan un estado de ánimo que, en la mayoría de los casos, no se mapea fácilmente para resolver problemas con elegancia. La complejidad del programa final desalienta al programador de comprender completamente su ejecución y alienta a tomar decisiones que conducen a mejoras a corto plazo con altos costos a largo plazo.

blueberryfields
fuente
10

He visto este patrón usado varias veces.

Hay 2 problemas principales:

  • Es extremadamente costoso (creación de instancias del objeto de excepción, recopilación de la pila de llamadas, etc.). Es posible que algunos compiladores puedan optimizarlo, pero no contaría con eso en este caso, porque las excepciones no están destinadas a dicho uso, por lo tanto, no puede esperar que las personas lo optimicen.
  • Usar excepciones para el flujo de control es, de hecho, un salto, muy parecido a a goto. Los saltos se consideran dañinos, por varias razones. Deben usarse, si todas las alternativas tienen desventajas considerables. De hecho, en todo mi código recuerdo solo 2 casos, donde los saltos fueron claramente la mejor solución.
back2dos
fuente
2
Solo poniéndolo allí, si / de lo contrario también se considera un salto. La diferencia es que es un salto que solo puede ocurrir en ese punto específico (y se lee mucho más fácilmente) y no tiene que preocuparse por los nombres de las etiquetas (que tampoco tiene que intentar con try / catch , pero en comparación con goto). Pero solo decir "es un salto, lo cual es malo" también dice que si / si no es malo, cuando no lo es.
jsternberg
1
@jsternbarg: Sí, si / de hecho se compila para saltar a nivel de máquina, pero a nivel de lenguaje, el objetivo de salto es la siguiente declaración, mientras que en el caso de los bucles, es la declaración actual. Yo diría que eso es más un paso que un salto;)
back2dos
9

A veces las excepciones son más rápidas. He visto casos en los que las excepciones de objetos nulos fueron más rápidas incluso en Java que el uso de estructuras de control (no puedo citar el estudio en este momento, por lo que tendrá que confiar en mí). El problema surge cuando Java tiene que tomarse el tiempo y llenar el seguimiento de la pila de una clase de excepción personalizada en lugar de usar las nativas (que parecen estar al menos parcialmente en caché). Antes de decir que algo es unilateralmente más rápido o más lento, sería bueno compararlo.

En Python no solo es más rápido, sino que es mucho más correcto hacer algo que pueda causar una excepción y luego manejar el error. Sí, puede aplicar un sistema de tipos, pero eso va en contra de la filosofía del lenguaje; en su lugar, simplemente debe intentar llamar al método y obtener el resultado. (Probar si un archivo es editable es similar, solo intente escribir en él y detecte el error).

He visto momentos en los que es más rápido hacer algo estúpido como tablas de consulta que no estaban allí que averiguar si existe una tabla en PHP + MySQL (la pregunta donde comparo esto es en realidad mi única respuesta aceptada con votos negativos) .

Dicho todo esto, el uso de excepciones debe limitarse por varias razones:

  1. Ingestión accidental de excepciones anidadas. Esto es importante Si detecta alguna excepción profundamente anidada que alguien más está tratando de manejar, acaba de dispararle a su compañero programador (¡tal vez incluso a usted!) En el pie.
  2. Las pruebas se vuelven no obvias. Un bloque de código que tiene una excepción podría tener una de varias cosas mal. Un booleano, por otro lado, aunque en teoría podría ser molesto depurar, generalmente no lo es. Esto es especialmente cierto ya que los try...catchadherentes del flujo de control generalmente (en mi experiencia) no siguen la filosofía de "minimizar el código en el bloque de prueba".
  3. No permite un else ifbloqueo. Suficientemente dicho (y si alguien responde con un "Pero podrían usar diferentes clases de excepciones", mi respuesta es "Ve a tu habitación y no salgas hasta que hayas pensado en lo que has dicho").
  4. Es gramaticalmente engañoso. Exception, para el resto del mundo (como no adherentes a la try...catchfilosofía del flujo de control), significa que algo ha entrado en un estado inestable (aunque quizás recuperable). Los estados inestables son MALOS y debería mantenernos despiertos a todos por la noche si realmente tenemos excepciones evitables (en realidad me asusta, sin mentiras).
  5. No cumple con el estilo de codificación común. Nuestro trabajo, tanto con nuestro código como con nuestra interfaz de usuario, es hacer que el mundo sea lo más obvio posible. try...catchel flujo de control va en contra de lo que comúnmente se consideran las mejores prácticas. Esto significa que se necesita más tiempo para que alguien nuevo en un proyecto aprenda el proyecto, lo que significa un aumento en la cantidad de horas hombre sin absolutamente ninguna ganancia.
  6. Esto a menudo conduce a la duplicación de código. Finalmente, los bloques, aunque esto no es estrictamente necesario, necesitan resolver todos los punteros que quedaron abiertos por el bloque de prueba interrumpido. Así que prueba los bloques. Esto significa que puedes tener un try{ obj.openSomething(); /*something which causes exception*/ obj.doStuff(); obj.closeSomething();}catch(Exception e){obj.closeSomething();}. En un if...elseescenario más tradicional, closeSomething()es menos probable (una vez más, experiencia personal) ser un trabajo de copiar y pegar. (Es cierto que este argumento en particular tiene más que ver con las personas que he conocido que la filosofía en sí misma).
revs cwallenpoole
fuente
AFAIK, # 6 es un problema solo en Java, que, a diferencia de cualquier otro lenguaje moderno, no tiene cierres ni gestión de recursos basada en la pila. Ciertamente no es un problema inherente al uso de excepciones.
Kevin Cline
4 y 5 parecen ser el mismo punto para mí. 3-6 no se aplica si su idioma es python. 1 y 2 son problemas potenciales, pero creo que se manejan minimizando el código en el bloque de prueba.
Winston Ewert
1
@ Winston no estoy de acuerdo. 1 y 2 son problemas importantes que, si bien disminuyen con mejores estándares de codificación, todavía hacen que uno se pregunte si podría ser mejor simplemente evitar el error potencial para empezar. 3 se aplica si su idioma es Python. 4-5 también pueden solicitar Python, pero no es necesario. 4 y 5 son puntos muy diferentes: el engaño es diferente de las diferencias estilísticas. El código engañoso podría estar haciendo algo como nombrar el gatillo para arrancar un vehículo con el "botón de parada". Estilísticamente, por otro lado, también podría ser análogo a evitar toda sangría de bloque en C.
cwallenpoole
3) Python tiene un bloque else para excepciones. Es enormemente útil para evitar los problemas que normalmente obtendría para 1 y 2.
Winston Ewert
1
Para que quede claro, no estoy recomendando que el uso de excepciones sea su mecanismo general de control de flujo. Abogo por lanzar una excepción para cualquier tipo de modo de "falla", sin importar cuán menor sea. Creo que la versión de manejo de excepciones es más limpia y tiene menos probabilidades de ocultar errores que la versión de comprobación del valor de retorno. E idealmente, me gustaría que los idiomas dificulten la detección de errores anidados.
Winston Ewert
4

Mi argumento principal es que usar try / catch para la lógica interrumpe el flujo lógico. Seguir "lógica" a través de construcciones no lógicas es (para mí) contraintuitivo y confuso. Estoy acostumbrado a leer mi lógica como "si Condición entonces A otra B". Leer la misma declaración como "Intente A atrapar y luego ejecute B" se siente raro. Sería aún más extraño si la declaración Aes una asignación simple, que requiere un código adicional para forzar una excepción si conditiones falsa (y hacer eso probablemente requeriría una ifdeclaración de todos modos).

FrustratedWithFormsDesigner
fuente
2

Bueno, solo es una cuestión de etiqueta, antes de "comenzar una discusión con ellos", como usted lo dice, le preguntaría amablemente "¿Por qué usan el manejo de excepciones en todos estos lugares diferentes?"

Quiero decir, hay varias posibilidades:

  1. son incompetentes
  2. Hay una razón perfectamente válida para lo que hicieron, que puede no aparecer a primera vista.
  3. a veces es una cuestión de gustos y puede simplificar un flujo complejo de programas.
  4. Es una reliquia del pasado que nadie ha cambiado todavía y que estarían felices si alguien lo hace.

... Creo que todos estos son igualmente probables. Así que solo pregúntales amablemente.

dagnelies
fuente
1

Try Catch solo debe usarse para el manejo de excepciones. Más aún, manejo de excepciones específicas. Su intento de captura debe capturar solo las excepciones esperadas, de lo contrario, no está bien formado. Si necesita usar un catch all try catch, entonces probablemente esté haciendo algo mal.

EDITAR:

Motivo: puede argumentar que si usa try catch como operador condicional, ¿cómo va a contabilizar las excepciones REALES?

AJC
fuente
2
El OP pregunta por razones / argumentos. No has proporcionado ninguna.
Oded
"Puede argumentar que si usa try catch como operador condicional, ¿cómo va a explicar las excepciones REALES?" - ¿Al captar esas excepciones reales en una catchcláusula diferente de la que capta excepciones "no reales"?
@delnan - ¡Gracias! Has demostrado un punto que estaba a punto de hacer. Si una persona respondiera lo que acaba de decir tanto a mi razón como a la de Oded, dejaría de hablar porque no está buscando razones por las que está siendo terco y no pierdo mi tiempo con la terquedad.
AJC
¿Me estás llamando studdborn porque señalo fallas en algunos de los argumentos enumerados aquí? Tenga en cuenta que no tengo nada que decir en contra de otros puntos establecidos aquí. No soy fanático de usar excepciones para el control de flujo (aunque tampoco condenaré nada sin detalles, de ahí mi consulta de elaboración por parte de OP), simplemente estoy jugando al abogado del diablo.
1
El uso de try-catch como operador condicional de ninguna manera evita que funcione para capturar también excepciones "reales".
Winston Ewert
1

Las excepciones son cuando suceden cosas excepcionales. ¿El programa funciona de acuerdo con el flujo de trabajo regular excepcional?

jfrobishow
fuente
1
¿Pero por qué? El punto de la pregunta es por qué (o por qué no) las excepciones solo son buenas para eso.
Winston Ewert
No siempre es "excepcional". No encontrar una clave en una tabla hash no es tan excepcional, por ejemplo, y sin embargo, la biblioteca OCaml tiende a generar una excepción en tales casos. Y está bien: el manejo de excepciones es muy barato allí.
SK-logic
1
-1: declaración tautológica
Galletas de harina de arroz
1

Motivo para usar excepciones:

  1. Si olvida detectar una circunstancia excepcional, su programa morirá y el seguimiento de la pila le dirá exactamente por qué. Si olvida manejar el valor de retorno por una circunstancia de excepción, no se sabe qué tan lejos su programa exhibirá un comportamiento incorrecto.
  2. El uso de valores de retorno solo funciona si hay un valor centinela que puede devolver. Si todos los valores de retorno posibles ya son válidos, ¿qué vas a hacer?
  3. Una excepción llevará información adicional sobre lo sucedido que puede ser útil.

Razones para no usar excepciones:

  1. Muchos idiomas no están diseñados para hacer excepciones rápidamente.
  2. Las excepciones que viajan varias capas arriba de la pila pueden dejar un estado inconsistente a su paso.

En el final:

El objetivo es escribir código que comunique lo que está sucediendo. Las excepciones pueden ayudar / dificultar eso dependiendo de lo que esté haciendo el código. Un try / catch en python para un KeyError en una referencia de diccionario es perfectamente (siempre que conozca el idioma) try / catch para el mismo KeyError a cinco capas de funciones es peligroso.

Winston Ewert
fuente
0

Yo uso try-> catch como control de flujo en ciertas situaciones. Si su lógica principal depende de algo que falla, pero no desea simplemente lanzar una excepción y salir ... Para eso está el bloque try-> catch. Escribo muchos scripts de Unix del lado del servidor no supervisados, y es mucho más importante para ellos no fallar, que fallar con precisión.

Por lo tanto, intente el Plan A, y si el Plan A muere, atrape y ejecute con el Plan B ... Y si el plan B falla, úselo finalmente para iniciar el Plan C, que solucionará uno de los servicios fallidos en A o B, o la página yo.

Satanicpuppy
fuente
0

Depende del idioma y quizás de la implementación utilizada. El estándar en C ++ es que las excepciones deben guardarse para eventos verdaderamente excepcionales. Por otro lado, en Python una de las pautas es " es más fácil pedir perdón que permiso ", por lo que se fomenta la integración de los bloques try / except en la lógica del programa.

Charles E. Grant
fuente
"El estándar en C ++ es que las excepciones deben guardarse para eventos verdaderamente excepcionales". Esto no tiene sentido. Incluso el inventor del idioma no está de acuerdo con usted .
arayq2
-1

Es un mal hábito, pero lo hago de vez en cuando en Python. Al igual que en lugar de verificar si existe un archivo, solo trato de eliminarlo. Si arroja una excepción, lo atrapo y sigo adelante sabiendo que no estaba allí. <== (no necesariamente cierto si otro usuario posee el archivo, pero lo hago en el directorio de inicio de un usuario, por lo que esto NO DEBE suceder).

Aún así, el uso excesivo es una mala práctica.

RobotHumanos
fuente
3
En ese ejemplo, usar excepciones en lugar de "mirar antes de saltar" es una buena idea: evita las condiciones de carrera. Una verificación de la disponibilidad del archivo (existencia, permisos, no estar bloqueado) tener éxito no significa que más adelante, incluso si solo son unos pocos códigos de operación, el acceso (abrir, eliminar, mover, bloquear, lo que sea) tenga éxito, ya que el archivo podría ser cambiado (eliminado, movido, tener sus permisos cambiados) en el medio.
Si Python está lanzando una excepción solo porque no existe un archivo, parece alentar los malos hábitos, como describe Delnan.
Per Johansson
@delnan: Aunque es cierto, en mi humilde opinión, si es probable que se encuentre con tales condiciones de carrera, debe repensar su diseño. En tal caso, hay una clara separación de preocupaciones en sus sistemas. A menos que tenga muy buenas razones para hacer las cosas de esa manera, debe unificar el acceso por su parte o usar una base de datos adecuada para manejar múltiples clientes que intentan realizar transacciones en los mismos datos persistentes.
back2dos
1
Este no es un mal hábito. Es el estilo recomendado en Python.
Winston Ewert
@ back2dos, ¿puede evitarlo al tratar con recursos externos como otros usuarios de la base de datos / sistema de archivos?
Winston Ewert
-1

Me gusta la forma en que Apple lo define : las excepciones son solo para errores de programador y errores fatales de tiempo de ejecución. De lo contrario, use códigos de error. Me ayuda a decidir cuándo usarlo, pensando para mí mismo "¿Fue esto un error del programador?"

Por Johansson
fuente