A fines de los 90, trabajé bastante con una base de código que usaba excepciones como control de flujo. Implementó una máquina de estados finitos para manejar aplicaciones de telefonía. Últimamente me acuerdo de esos días porque he estado haciendo aplicaciones web MVC.
Ambos tienen Controller
s que deciden a dónde ir después y suministran los datos a la lógica de destino. Las acciones de los usuarios del dominio de un teléfono de la vieja escuela, como los tonos DTMF, se convirtieron en parámetros para los métodos de acción, pero en lugar de devolver algo como a ViewResult
, arrojaron un StateTransitionException
.
Creo que la principal diferencia era que los métodos de acción eran void
funciones. No recuerdo todas las cosas que hice con este hecho, pero he dudado incluso de recordar mucho porque desde ese trabajo, como hace 15 años, nunca vi esto en el código de producción en ningún otro trabajo . Asumí que esto era una señal de que se trataba de un llamado antipatrón.
¿Es este el caso, y si es así, por qué?
Actualización: cuando hice la pregunta, ya tenía en mente la respuesta de @ MasonWheeler, así que elegí la respuesta que más me pareció. Creo que la suya también es una buena respuesta.
fuente
Respuestas:
Hay una discusión detallada de esto en la Wiki de Ward . En general, el uso de excepciones para flujo de control es un anti-patrón, con muchos notable situación - y el lenguaje específico (véase, por ejemplo Python ) tos excepciones tos .
Como resumen rápido de por qué, en general, es un antipatrón:
Lea la discusión en el wiki de Ward para obtener información mucho más detallada.
Vea también un duplicado de esta pregunta, aquí
fuente
if
yforeach
también son sofisticadosGOTO
s. Francamente, creo que la comparación con goto no es útil; Es casi como un término despectivo. El uso de GOTO no es intrínsecamente malo: tiene problemas reales, y las excepciones pueden compartirlos, pero pueden no serlo. Sería más útil escuchar esos problemas.El caso de uso para el que se diseñaron las excepciones es "Acabo de encontrar una situación que no puedo manejar adecuadamente en este momento, porque no tengo suficiente contexto para manejarlo, pero la rutina que me llamó (o algo más adelante) pila) debería saber cómo manejarlo ".
El caso de uso secundario es "Acabo de encontrar un error grave, y en este momento salir de este flujo de control para evitar la corrupción de datos u otros daños es más importante que tratar de continuar".
Si no está utilizando excepciones por alguna de estas dos razones, probablemente haya una mejor manera de hacerlo.
fuente
const myvar = theFunction
porque myvar debe crearse a partir de try-catch, así no es más constante. Eso no significa que no lo use en C #, ya que es una corriente principal allí por cualquier razón, pero estoy tratando de reducirlos de todos modos.Las excepciones son tan poderosas como las Continuaciones y
GOTO
. Son una construcción de flujo de control universal.En algunos idiomas, son la única construcción de flujo de control universal. JavaScript, por ejemplo, no tiene ni Continuaciones ni
GOTO
siquiera tiene Llamadas de cola adecuadas. Por lo tanto, si se desea implementar el flujo de control sofisticado en JavaScript, que tiene de utilizar excepciones.El proyecto Microsoft Volta fue un proyecto de investigación (ahora descontinuado) para compilar código .NET arbitrario a JavaScript. .NET tiene excepciones cuya semántica no se asigna exactamente a JavaScript, pero lo más importante es que tiene subprocesos , y debe asignarlos de alguna manera a JavaScript. Volta hizo esto implementando Continuaciones de Volta usando Excepciones de JavaScript y luego implementó todas las construcciones de flujo de control .NET en términos de Continuaciones de Volta. Ellos tuvieron que utilizar excepciones como el flujo de control, porque no hay otro flujo de control construir lo suficientemente potente.
Usted mencionó las máquinas estatales. Las SM son triviales de implementar con llamadas de cola adecuadas: cada estado es una subrutina, cada transición de estado es una llamada de subrutina. Las SM también se pueden implementar fácilmente con
GOTO
Coroutines o Continuations. Sin embargo, Java no tiene ninguno de esos cuatro, pero sí tiene Excepciones. Por lo tanto, es perfectamente aceptable usarlos como flujo de control. (Bueno, en realidad, la opción correcta probablemente sería usar un lenguaje con la construcción de flujo de control adecuada, pero a veces puede estar atascado con Java).fuente
Como otros han mencionado en numerosas ocasiones ( por ejemplo, en esta pregunta de desbordamiento de pila ), el principio de menor asombro prohibirá que use excepciones en exceso para fines de control de flujo solamente. Por otro lado, ninguna regla es 100% correcta, y siempre hay casos en los que una excepción es "la herramienta correcta", muy parecida a
goto
sí misma, por cierto, que se presenta en formabreak
ycontinue
en lenguajes como Java, que a menudo son la forma perfecta de saltar de bucles muy anidados, que no siempre son evitables.La siguiente publicación de blog explica un caso de uso bastante complejo pero también bastante interesante para un no local
ControlFlowException
:Explica cómo dentro de jOOQ (una biblioteca de abstracción SQL para Java) (descargo de responsabilidad: trabajo para el proveedor), tales excepciones se usan ocasionalmente para abortar el proceso de representación SQL temprano cuando se cumple alguna condición "rara".
Ejemplos de tales condiciones son:
Se encuentran demasiados valores de enlace. Algunas bases de datos no admiten números arbitrarios de valores de enlace en sus sentencias SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). En esos casos, jOOQ aborta la fase de representación de SQL y vuelve a representar la instrucción SQL con valores de enlace en línea. Ejemplo:
Si extrajéramos explícitamente los valores de enlace de la consulta AST para contarlos cada vez, desperdiciaríamos valiosos ciclos de CPU para el 99.9% de las consultas que no sufren este problema.
Parte de la lógica está disponible solo indirectamente a través de una API que queremos ejecutar solo "parcialmente". El
UpdatableRecord.store()
método genera una declaraciónINSERT
oUPDATE
, dependiendo de losRecord
indicadores internos de. Desde el "exterior", no sabemos qué tipo de lógica está contenidastore()
(por ejemplo, bloqueo optimista, manejo de escucha de eventos, etc.), por lo que no queremos repetir esa lógica cuando almacenamos varios registros en una declaración por lotes, donde nos gustaría tenerstore()
solo generar la instrucción SQL, no ejecutarla realmente. Ejemplo:Si hubiéramos externalizado la
store()
lógica en una API "reutilizable" que se puede personalizar para que opcionalmente no ejecute el SQL, estaríamos buscando crear una API bastante difícil de mantener y difícilmente reutilizable.Conclusión
En esencia, nuestro uso de estos mensajes no locales
goto
es similar a lo que Mason Wheeler dijo en su respuesta:Ambos usos de
ControlFlowExceptions
fueron bastante fáciles de implementar en comparación con sus alternativas, lo que nos permitió reutilizar una amplia gama de lógica sin refactorizarla de las partes internas relevantes.Pero la sensación de que esto sea una sorpresa para los futuros mantenedores permanece. El código se siente bastante delicado y, si bien fue la elección correcta en este caso, siempre preferimos no usar excepciones para el flujo de control local , donde es fácil evitar el uso de ramificaciones ordinarias
if - else
.fuente
El uso de excepciones para el flujo de control generalmente se considera un antipatrón, pero hay excepciones (sin juego de palabras).
Se ha dicho mil veces que las excepciones son para condiciones excepcionales. Una conexión de base de datos rota es una condición excepcional. Un usuario que ingresa letras en un campo de entrada que solo debe permitir números no lo es .
Un error en su software que hace que se llame a una función con argumentos ilegales, por ejemplo,
null
donde no se permite, es una condición excepcional.Al utilizar excepciones para algo que no es excepcional, está utilizando abstracciones inapropiadas para el problema que está tratando de resolver.
Pero también puede haber una penalización de rendimiento. Algunos idiomas tienen una implementación de manejo de excepciones más o menos eficiente, por lo que si su idioma de elección no tiene un manejo eficiente de excepciones, puede ser muy costoso, en términos de rendimiento *.
Pero otros lenguajes, por ejemplo Ruby, tienen una sintaxis similar a una excepción para el flujo de control. Las situaciones excepcionales son manejadas por los operadores
raise
/rescue
. Pero puede usarthrow
/catch
para construcciones de flujo de control similares a excepciones **.Entonces, aunque las excepciones generalmente no se usan para el flujo de control, su idioma de elección puede tener otros modismos.
* Por ejemplo, el uso costoso de las excepciones de rendimiento: una vez fui configurado para optimizar una aplicación ASP.NET Web Form de bajo rendimiento. Resultó que el renderizado de una mesa grande requería
int.Parse()
aprox. mil cadenas vacías en una página promedio, lo que resulta en aprox. mil excepciones siendo manejadas. ¡Al reemplazar el código conint.TryParse()
me afeité un segundo! ¡Por cada solicitud de página!** Esto puede ser muy confuso para un programador que viene a Ruby desde otros lenguajes, ya que ambos
throw
ycatch
son las palabras clave asociadas con excepciones en muchos otros idiomas.fuente
Es completamente posible manejar condiciones de error sin el uso de excepciones. Algunos lenguajes, especialmente C, ni siquiera tienen excepciones, y las personas aún logran crear aplicaciones bastante complejas con él. La razón por la que las excepciones son útiles es que le permiten especificar sucintamente dos flujos de control esencialmente independientes en el mismo código: uno si se produce un error y otro si no es así. Sin ellos, terminas con un código en todo el lugar que se ve así:
O equivalente en su idioma, como devolver una tupla con un valor como estado de error, etc. A menudo las personas que señalan cuán "costoso" es el manejo de excepciones, descuidan todas las
if
declaraciones adicionales como las anteriores que deben agregar si no ' No use excepciones.Si bien este patrón ocurre con mayor frecuencia cuando se manejan errores u otras "condiciones excepcionales", en mi opinión, si comienza a ver código repetitivo como este en otras circunstancias, tiene un argumento bastante bueno para usar excepciones. Dependiendo de la situación y la implementación, puedo ver que las excepciones se usan de manera válida en una máquina de estados, porque tiene dos flujos de control ortogonales: uno que cambia el estado y otro para los eventos que ocurren dentro de los estados.
Sin embargo, esas situaciones son raras, y si va a hacer una excepción (juego de palabras) a la regla, es mejor que esté preparado para mostrar su superioridad a otras soluciones. Una desviación sin tal justificación se llama correctamente un antipatrón.
fuente
En Python, las excepciones se utilizan para la terminación del generador y la iteración. Python tiene bloques try / except muy eficientes, pero en realidad generar una excepción tiene algo de sobrecarga.
Debido a la falta de saltos de varios niveles o una declaración goto en Python, a veces he usado excepciones:
fuente
return
en lugar degoto
.try:
bloques en 1 o 2 líneas, por favor, nuncatry: # Do lots of stuff
.Dibujemos un uso de excepción de este tipo:
El algoritmo busca de forma recursiva hasta encontrar algo. Entonces, volviendo de la recursión, uno tiene que verificar el resultado para ser encontrado, y luego regresar, de lo contrario, continuar. Y eso regresa repetidamente desde cierta profundidad de recursión.
Además de necesitar un booleano adicional
found
(para empacar en una clase, donde de lo contrario tal vez solo se hubiera devuelto un int), y para la profundidad de recursión ocurre el mismo postludio.Tal desenrollamiento de una pila de llamadas es para lo que es una excepción. Entonces, me parece un medio de codificación no similar a Goto, más inmediato y apropiado. No es necesario, uso raro, quizás mal estilo, pero va al grano. Comparable con la operación de corte Prolog.
fuente
La programación es sobre el trabajo.
Creo que la forma más fácil de responder a esto es entender el progreso que OOP ha logrado a lo largo de los años. Todo lo que se hace en OOP (y la mayoría de los paradigmas de programación, para el caso) se basa en la necesidad de trabajar .
Cada vez que se llama a un método, la persona que llama dice "No sé cómo hacer este trabajo, pero tú sabes cómo hacerlo, así que lo haces por mí".
Esto presentaba una dificultad: ¿qué sucede cuando el método llamado generalmente sabe cómo hacer el trabajo, pero no siempre? Necesitábamos una forma de comunicarnos "Quería ayudarte, realmente lo hice, pero no puedo hacer eso".
Una metodología temprana para comunicar esto fue simplemente devolver un valor "basura". Tal vez espere un número entero positivo, por lo que el método llamado devuelve un número negativo. Otra forma de lograr esto era establecer un valor de error en alguna parte. Desafortunadamente, ambas formas resultaron en un código repetitivo de " déjenme revisar-aquí-para-asegurar-todo-kosher ". A medida que las cosas se vuelven más complicadas, este sistema se desmorona.
Una analogía excepcional
Digamos que tienes un carpintero, un plomero y un electricista. Desea que el plomero arregle su fregadero, así que él lo mira. No es muy útil si solo te dice: "Lo siento, no puedo arreglarlo. Está roto". Demonios, es aún peor si él echara un vistazo, se fuera y le enviara una carta diciéndole que no podía arreglarlo. Ahora tiene que revisar su correo antes de saber que él no hizo lo que usted quería.
Lo que preferiría es que le dijera: "Mire, no pude arreglarlo porque parece que su bomba no funciona".
Con esta información, puede concluir que desea que el electricista analice el problema. Quizás el electricista encuentre algo relacionado con el carpintero, y necesitará que el carpintero lo arregle.
Demonios, es posible que ni siquiera sepas que necesitas un electricista, es posible que no sepas a quién necesitas. Usted es solo una gerencia intermedia en un negocio de reparación de viviendas, y su enfoque es la plomería. Entonces le dices a tu jefe sobre el problema, y luego le dice al electricista que lo arregle.
Esto es lo que las excepciones son el modelado: modos de falla complejos de manera desacoplada. El plomero no necesita saber acerca de electricista, ni siquiera necesita saber que alguien en la cadena puede solucionar el problema. Simplemente informa sobre el problema que encontró.
Entonces ... ¿un antipatrón?
Ok, entonces entender el punto de excepciones es el primer paso. Lo siguiente es entender qué es un antipatrón.
Para calificar como un antipatrón, necesita
El primer punto se cumple fácilmente: el sistema funcionó, ¿verdad?
El segundo punto es más pegajoso. La razón principal para usar excepciones ya que el flujo de control normal es malo es porque ese no es su propósito. Cualquier pieza de funcionalidad dada en un programa debe tener un propósito relativamente claro, y cooptar ese propósito conduce a una confusión innecesaria.
Pero eso no es un daño definitivo . Es una forma pobre de hacer las cosas, y raro, pero ¿un antipatrón? No. Solo ... extraño.
fuente