java - propagacion - tipos de excepciones en programacion
Extendiendo Throwable en Java (7)
Java le permite crear un subtipo completamente nuevo de Throwable
, por ejemplo:
public class FlyingPig extends Throwable { ... }
Ahora, muy raramente , puedo hacer algo como esto:
throw new FlyingPig("Oink!");
y por supuesto en otro lugar:
try { ... } catch (FlyingPig porky) { ... }
Mis preguntas son:
- ¿Es una mala idea? Y si es así, ¿por qué?
- ¿Qué se podría haber hecho para evitar esta subtipificación si es una mala idea?
- Dado que no es prevenible (hasta donde yo sé), ¿qué catástrofes podrían resultar?
- Si esto no es una mala idea, ¿por qué no?
- ¿Cómo puedes hacer algo útil por el hecho de que puedes
extends Throwable
?
- ¿Cómo puedes hacer algo útil por el hecho de que puedes
Escenario propuesto n. ° 1
Un escenario en el que estuve realmente tentado de hacer algo como esto tiene las siguientes propiedades:
- El "evento" es algo que sucederá eventualmente. Es esperado . Definitivamente no es un
Error
, y no hay nadaException
sobre cuando ocurre.- Debido a que se espera , habrá una
catch
esperándolo. No se "deslizará" más allá de cualquier cosa. No se "escapará" de ningún intento decatch
Exception
y / oError
generales.
- Debido a que se espera , habrá una
- El "evento" ocurre extremadamente raramente .
- Cuando sucede, generalmente hay un rastro de pila profundo.
Entonces, quizás esté claro ahora lo que estoy tratando de decir: FlyingPig
es el resultado de una búsqueda recursiva exhaustiva.
El objeto a buscar existe: solo es cuestión de encontrarlo en el mar grande que es el espacio de búsqueda. El proceso de búsqueda será largo, por lo que el costo relativamente caro del manejo de excepciones es insignificante. De hecho, la alternativa de construcción de flujo de control tradicional de usar una bandera boolean isFound
puede ser más costosa, ya que debe verificarse continuamente durante todo el proceso de búsqueda, muy probablemente en cada nivel de la recursión. Esta verificación fallará el 99.99% del tiempo, pero es absolutamente necesario propagar la condición de terminación. De alguna manera, mientras es efectivo , ¡el cheque es ineficiente !
Simplemente throw
un FlyingPig
cuando se encuentra el objeto buscado, no es necesario que desordene el código con la administración del boolean isFound
. No solo es el código más limpio en ese aspecto, sino que puede correr más rápido debido a esta omisión.
Entonces, para resumir, la elección es entre estos dos:
- Enfoque de flujo de control tradicional
- Use un
boolean isFound
, verificado continuamente - 99.99% del tiempo, el cheque es un "desperdicio", porque aún sería
false
- Cuando finalmente se convierte en
true
, detienes la recurrencia y tienes que asegurarte de que puedes relajar adecuadamente la llamada inicial.
- Use un
- Enfoque
FlyingPig
- No te molestes con cualquier
boolean isFound
. - Si se encuentra, solo
throw new FlyingPig()
; se espera , por lo que habrá unacatch
para ello. - No hay administración de bandera
boolean
, no se desperdicia cheque si necesita continuar, no hay contabilidad para desenrollar manualmente la recursión, etc.
- No te molestes con cualquier
Preguntas:
- ¿Es válida esta técnica de (ab) usar la excepción? (¿Existe un nombre para eso?)
- Si es válido, ¿
FlyingPig extends Throwable
, oException
está bien? (a pesar de que no hay nada excepcional acerca de sus circunstancias?)
¿Es válida esta técnica de (ab) usar la excepción? (¿Existe un nombre para eso?)
En mi opinión, no es una buena idea:
- Viola el principio de menos asombro , hace que el código sea más difícil de leer, especialmente si no hay nada de excepción al respecto.
- Lanzar la excepción es una operación muy costosa en Java (sin embargo, podría no ser un problema aquí).
La excepción no debería usarse para controlar el flujo . Si tuviera que nombrar esta técnica, lo llamaría un olor a código o un patrón anti.
Ver también:
Si es válido, ¿
FlyingPig
extiendeFlyingPig
, oException
está bien? (a pesar de que no hay nada excepcional acerca de sus circunstancias?)
Puede haber situaciones en las que desee atrapar a Throwable
para que no solo capture Exception
sino también Error
pero es raro, la gente generalmente no Throwable
. Pero no encuentro una situación en la que te gustaría lanzar una subclase de Throwable
.
Y también creo que la extensión de Throwable
no hace que las cosas se vean menos "excepcionales" , sino que hacen que se vea peor, lo que frustra por completo la intención.
Entonces, para concluir, si realmente quieres lanzar algo, lanza una subclase de Exception
.
Ver también:
A medida que esta pregunta ha evolucionado, veo que no entiendo bien el punto de la pregunta original, por lo que algunas de las otras respuestas son probablemente más relevantes. Dejaré esta respuesta aquí, ya que puede ser útil para otras personas que tengan una pregunta similar, y encuentre esta página mientras busca una respuesta a su pregunta.
No hay nada de malo en extender Throwable para poder lanzar (y manejar) excepciones personalizadas. Sin embargo, debes tener en cuenta los siguientes puntos:
- Usar la excepción más específica posible es una gran idea. Le permitirá a la persona que llama manejar diferentes tipos de excepciones de manera diferente. Es importante tener en cuenta la jerarquía de clases de la excepción, por lo que su excepción personalizada debe extender otro tipo de Throwable que sea lo más parecido posible a su excepción.
- La expansión de Throwable puede ser de muy alto nivel. Intente extender Exception o RuntimeException en su lugar (o una excepción de nivel inferior que esté más cerca de la razón por la que está lanzando una excepción). Tenga en cuenta la diferencia entre una RuntimeException y una excepción.
- Una llamada a un método que arroje una excepción (o una subclase de excepción) tendrá que estar envuelto en un bloque try / catch que sea capaz de manejar la excepción. Esto es bueno para los casos en que esperas que las cosas salgan mal, debido a circunstancias que pueden estar fuera de tu control (por ejemplo, la red está inactiva).
- Una llamada a un método que arroje una RuntimeException (o una subclase de la misma) no necesita envolverse en un bloque try / catch que pueda manejar la excepción. (Ciertamente podría ser, pero no es necesario). Esto es más por excepciones que realmente no deberían esperarse.
Entonces, supongamos que tiene las siguientes clases de excepción en su base de código:
public class Pig extends Throwable { ... }
public class FlyingPig extends Pig { ... }
public class Swine extends Pig { ... }
public class CustomRuntimeException extends RuntimeException { ... }
Y algunos métodos
public void foo() throws Pig { ... }
public void bar() throws FlyingPig, Swine { ... }
// suppose this next method could throw a CustomRuntimeException, but it
// doesn''t need to be declared, since CustomRuntimeException is a subclass
// of RuntimeException
public void baz() { ... }
Ahora, podría tener algún código que llame a estos métodos de esta manera:
try {
foo();
} catch (Pig e) {
...
}
try {
bar();
} catch (Pig e) {
...
}
baz();
Tenga en cuenta que cuando llamamos a bar()
, podemos simplemente capturar Pig
, ya que tanto FlyingPig
como Swine
extienden Pig
. Esto es útil si desea hacer lo mismo para manejar cualquiera de las excepciones. Sin embargo, podría manejarlos de manera diferente:
try {
bar();
} catch (FlyingPig e) {
...
} catch (Swine e) {
...
}
Aquí hay una publicación del blog de John Rose, un arquitecto de HotSpot:
http://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive
Se trata de "abusar" de excepciones para el control de flujo. Casos de uso ligeramente diferentes, pero ... En resumen, funciona realmente bien, si preasigna / clona sus excepciones para evitar que se creen trazas de pila.
Creo que esta técnica es justificable si está "oculta" de los clientes. Por ejemplo, su FlyingPig nunca debería poder abandonar su biblioteca (todos los métodos públicos deben garantizarse transitoriamente que no se lance). Una forma de garantizar esto sería convertirlo en una excepción comprobada.
Creo que la única justificación para extender Throwable es porque desea permitir que las personas pasen las devoluciones de llamada que tienen cláusulas catch (Exception e), y desea que su resultado sea ignorado por ellos. Puedo comprar eso ...
El juego! framework usa algo como esto para el manejo de solicitudes. El proceso de solicitud pasa por muchas capas (enrutamiento, middleware, controladores, representación de plantilla) y en la capa final el HTML renderizado se envuelve en un throwable y lanzado, que la capa superior capta, desenvuelve y envía al cliente. Por lo tanto, ninguno de los métodos en las muchas capas implicadas debe devolver explícitamente un objeto de respuesta, ni necesitan tener un objeto de respuesta pasado como argumento para ser propagado y modificado.
Estoy un poco superficial en los detalles. ¡Puedes mirar a través del código de Play! marco para detalles.
La anotación org.junit.Test
incluye la clase None
que extiende Throwable
y se usa como el valor predeterminado para el parámetro de anotación expected
.
Si puede justificar lo que distingue a un FlyingPig tanto de Error como de Excepción, de modo que no sea adecuado como una subclase de ninguno, entonces no hay nada fundamentalmente erróneo en crearlo.
El mayor problema que puedo pensar es en el mundo pragmático, a veces hay razones justificables para atrapar java.lang.Exception. Su nuevo tipo de objeto arrojable pasará directamente a través de bloques de prueba que tenían todas las expectativas razonables de suprimir (o registrar, envolver, lo que sea) todos los posibles problemas no fatales.
Por otro lado, si está realizando tareas de mantenimiento en un sistema antiguo que suprime justificadamente java.lang.Exception, podría hacer trampa. (Suponiendo que se rechaza la apelación sincera de tiempo para arreglarlo correctamente).
Yo diría que es una muy mala idea. Se implementa una gran cantidad de código suponiendo que si detecta Error
y Exception
, ha detectado todas las excepciones posibles. Y la mayoría de los tutoriales y libros de texto le dirán lo mismo. Al crear una subclase directa de Throwable
, potencialmente está creando todo tipo de problemas de mantenimiento e interoperabilidad.
No puedo pensar en ninguna buena razón para extender Throwable
. Extend Exception
o RuntimeException
lugar.
EDITAR - En respuesta al escenario propuesto # 1 del OP.
Las excepciones son una forma muy costosa de lidiar con el control de flujo "normal". En algunos casos, estamos hablando de miles de instrucciones adicionales ejecutadas para crear, lanzar y atrapar una excepción. Si va a ignorar la sabiduría aceptada y usar excepciones para el control de flujo no excepcional, use un subtipo de Exception
. Intentar fingir algo es un "evento" y no una "excepción" al declararlo como un subtipo de Throwable
que no va a lograr nada.
Sin embargo, es un error confundir una excepción con un error, error, error o lo que sea. Y no hay nada de malo en representar un "evento excepcional que no sea un error, error, error o lo que sea" usando una subclase de Exception
. La clave es que el evento sea excepcional ; es decir, fuera de lo común, sucediendo muy infrecuentemente, ...
En resumen, un FlyingPig
puede no ser un error, pero no es motivo para no declararlo como un subtipo de Exception
.