Usamos excepciones para permitir que el consumidor del código maneje comportamientos inesperados de una manera útil. Por lo general, las excepciones se basan en el escenario "qué sucedió", como FileNotFound
(no pudimos encontrar el archivo que especificó) o ZeroDivisionError
(no pudimos realizar la 1/0
operación).
¿Qué sucede si existe la posibilidad de especificar el comportamiento esperado del consumidor?
Por ejemplo, imagine que tenemos un fetch
recurso, que realiza la solicitud HTTP y devuelve los datos recuperados. Y en lugar de errores como ServiceTemporaryUnavailable
o RateLimitExceeded
simplemente plantearíamos una RetryableError
sugerencia al consumidor de que debería volver a intentar la solicitud y no preocuparse por fallas específicas. Entonces, básicamente estamos sugiriendo una acción a la persona que llama: el "qué hacer".
No hacemos esto a menudo porque no conocemos todos los casos de uso de los consumidores. Pero imagine que es un componente específico que conocemos el mejor curso de acción para una persona que llama, ¿entonces deberíamos utilizar el enfoque de "qué hacer"?
fuente
if( ! function() ) handle_something();
, pero al ser capaz de manejar el error en algún lugar que realmente conozca el contexto de la llamada, es decir, decirle a un cliente que llame a un administrador del sistema si su servidor falló o que se recargará automáticamente si se corta la conexión, pero le avisará caso la persona que llama es otro microservicio. Deje que los bloques de captura manejen la captura.Respuestas:
Esto casi siempre falla para al menos una de las personas que llaman, para lo cual este comportamiento es increíblemente irritante. No asumas que sabes mejor. Dígale a sus usuarios lo que está sucediendo, no lo que supone que deberían hacer al respecto. En muchos casos, ya está claro cuál debería ser un curso de acción sensato (y, si no lo es, haga una sugerencia en su manual de usuario).
Por ejemplo, incluso las excepciones dadas en su pregunta demuestran su suposición equivocada: a
ServiceTemporaryUnavailable
equivale a "intentarlo más tarde", yRateLimitExceeded
equivale a "woah allí relajarse, tal vez ajustar los parámetros del temporizador e intentar nuevamente en unos minutos". Pero el usuario también puede querer activar algún tipo de alarmaServiceTemporaryUnavailable
(que indica un problema del servidor), y no paraRateLimitExceeded
(que no lo hace).Dales la opción .
fuente
RetryableError
.Ante esta idea:
... una cosa que sugeriría es que puede estar mezclando preocupaciones de informar un error con cursos de acción para responder de una manera que pueda degradar la generalidad de su código o requerir muchos "puntos de traducción" para las excepciones .
Por ejemplo, si modelo una transacción que implica cargar un archivo, podría fallar por varias razones. Quizás cargar el archivo implica cargar un complemento que no existe en la máquina del usuario. Quizás el archivo simplemente está dañado y encontramos un error al analizarlo.
Pase lo que pase, digamos que el curso de acción es informar lo que sucedió al usuario y preguntarle qué quiere hacer al respecto ("reintentar, cargar otro archivo, cancelar").
Lanzador contra receptor
Ese curso de acción se aplica independientemente del tipo de error que encontramos en este caso. No está integrado en la idea general de un error de análisis, no está integrado en la idea general de no cargar un complemento. Está incrustado en la idea de encontrar tales errores durante el contexto preciso de cargar un archivo (la combinación de cargar un archivo y fallar). Por lo general, lo veo, hablando en términos crudos, como la
catcher's
responsabilidad de determinar el curso de acción en respuesta a una excepción lanzada (por ejemplo, preguntar al usuario con opciones), no elthrower's
.Dicho de otra manera, los sitios cuyas
throw
excepciones generalmente carecen de este tipo de información contextual, especialmente si las funciones que arrojan son generalmente aplicables. Incluso en un contexto totalmente desgeneralizado cuando tienen esta información, terminas arrinconándote en términos de comportamiento de recuperación al insertarlo en elthrow
sitio. Los sitios quecatch
generalmente tienen la mayor cantidad de información disponible para determinar un curso de acción y le brindan un lugar central para modificar si ese curso de acción alguna vez cambia para esa transacción dada.Cuando comienzas a intentar lanzar excepciones que ya no informan qué está mal, sino que intentas determinar qué hacer, eso podría degradar la generalidad y flexibilidad de tu código. Un error de análisis no siempre debe conducir a este tipo de mensaje, varía según el contexto en el que se produce dicha excepción (la transacción en la que se produjo).
El lanzador ciego
Solo en general, gran parte del diseño del manejo de excepciones a menudo gira en torno a la idea de un lanzador ciego. No sabe cómo se detectará la excepción ni dónde. Lo mismo se aplica incluso a las formas más antiguas de recuperación de errores utilizando la propagación manual de errores. Los sitios que encuentran errores no incluyen un curso de acción del usuario, solo incrustan la información mínima para informar qué tipo de error se encontró.
Responsabilidades invertidas y generalización del receptor
Al pensar en esto con más cuidado, estaba tratando de imaginar el tipo de base de código donde esto podría convertirse en una tentación. Mi imaginación (posiblemente equivocada) es que su equipo todavía está desempeñando el papel de "consumidor" aquí e implementando la mayor parte del código de llamada también. Quizás tenga muchas transacciones dispares (muchos
try
bloques) que pueden encontrarse con los mismos conjuntos de errores, y todos deberían, desde una perspectiva de diseño, conducir a un curso uniforme de acción de recuperación.Teniendo en cuenta los sabios consejos de una
Lightness Races in Orbit's
buena respuesta (que creo que realmente proviene de una mentalidad avanzada orientada a la biblioteca), aún podría sentirse tentado a lanzar excepciones de "qué hacer", solo más cerca del sitio de recuperación de transacciones.Podría ser posible encontrar aquí un sitio intermediario y común de manejo de transacciones que realmente centralice las preocupaciones de "qué hacer", pero aún dentro del contexto de la captura.
Esto solo se aplicaría si puede diseñar algún tipo de función general que utilicen todas estas transacciones externas (por ejemplo: una función que ingresa otra función para llamar o una clase base de transacción abstracta con comportamiento reemplazable que modela este sitio de transacciones intermedias que realiza la captura sofisticada )
Sin embargo, ese podría ser responsable de centralizar el curso de acción del usuario en respuesta a una variedad de posibles errores, y aún dentro del contexto de atrapar en lugar de tirar. Ejemplo simple (pseudocódigo de Python-ish, y no soy un desarrollador experimentado de Python en lo más mínimo, por lo que podría haber una forma más idiomática de hacerlo):
[Ojalá con un nombre mejor que
general_catcher
]. En este ejemplo, puede pasar una función que contiene qué tarea realizar, pero aún así beneficiarse del comportamiento de captura generalizado / unificado para todos los tipos de excepciones que le interesan, y continuar extendiendo o modificando la parte "qué hacer" le gusta desde esta ubicación central y aún dentro de uncatch
contexto en el que esto generalmente se fomenta. Lo mejor de todo es que podemos evitar que los sitios de lanzamiento se preocupen por "qué hacer" (preservando la noción de "lanzador ciego").Si no encuentra ninguna de estas sugerencias útiles aquí y hay una fuerte tentación de lanzar excepciones de "qué hacer" de todos modos, tenga en cuenta que esto es muy anti-idiomático por lo menos, y también puede desalentar una mentalidad generalizada.
fuente
Creo que la mayoría de las veces sería mejor pasar argumentos a la función diciéndole cómo manejar esas situaciones.
Por ejemplo, considere una función:
Puedo pasar RetryPolicy.noRetries () o RetryPolicy.retries (3) o lo que sea. En el caso de una falla recuperable, consultará la política para decidir si debe volver a intentarlo o no.
fuente
new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)
o algo así, ya que esRateLimitExceeded
posible que deba manejarse de manera diferenteServiceTemporaryUnavailable
. Después de escribir eso, mi pensamiento es: mejor lanzar una excepción, porque da un control más flexible.