En Java, ¿cuándo debería crear una excepción marcada y cuándo debería ser una excepción de tiempo de ejecución?

Posible duplicado:
Cuándo elegir las excepciones marcadas y no marcadas

¿Cuándo debería crear una excepción marcada y cuándo debería hacer una excepción de tiempo de ejecución?

Por ejemplo, supongamos que creé la siguiente clase:

public class Account { private float balance; /* ... constructor, getter, and other fields and methods */ public void transferTo(Account other, float amount) { if (amount > balance) throw new NotEnoughBalanceException(); /* ... */ } } 

¿Cómo debería crear mi NotEnoughBalanceException ? ¿Debería extender Exception o RuntimeException ? ¿O debería usar IllegalArgumentException en IllegalArgumentException lugar?

Hay MUCHO desacuerdo sobre este tema. En mi último trabajo, nos topamos con algunos problemas reales con excepciones de Runtime que se olvidaban hasta que aparecían en la producción (en agedwards.com), por lo que resolvimos usar exclusiones marcadas exclusivamente.

En mi trabajo actual, descubro que hay muchos que son para excepciones de tiempo de ejecución en muchos casos o en todos.

Esto es lo que pienso: al usar CheckedExceptions, me veo forzado al momento de la comstackción para, al menos, reconocer la excepción en la persona que llama. Con las excepciones de Runtime, el comstackdor no me obliga, pero puedo escribir una prueba de unidad que me haga lidiar con eso. Como sigo creyendo que cuanto más temprano se detecta un error, más barato es arreglarlo, prefiero CheckedExceptions por este motivo.

Desde un punto de vista filosófico, una llamada a un método es un contrato hasta cierto punto entre el que llama y el que llama. Como el comstackdor impone los tipos de parámetros que se pasan, parece simétrico dejar que imponga los tipos a la salida. Es decir, valores de retorno o excepciones.

Mi experiencia me dice que obtengo una mejor calidad, es decir, código que SIMPLEMENTE FUNCIONA, cuando uso excepciones comprobadas. Las excepciones controladas pueden desordenar el código, pero existen técnicas para lidiar con esto. Me gusta traducir excepciones cuando paso un límite de capa. Por ejemplo, si estoy pasando por alto desde mi capa de persistencia, me gustaría convertir una excepción de SQL a una excepción de persistencia, ya que a la siguiente capa no le importaría que persista en una base de datos SQL, pero querría saber si algo no puede persistir Otra técnica que uso es crear una jerarquía simple de excepciones. Esto me permite escribir código limpio una capa más arriba, ya que puedo atrapar la superclase, y solo trato con las subclases individuales cuando realmente importa.

En general, creo que el consejo de Joshua Bloch en Java efectivo resume mejor la respuesta a su pregunta: Use las verificaciones comprobadas para las condiciones recuperables y las excepciones de tiempo de ejecución para los errores de progtwigción (Ítem 58 en la 2da edición).

Entonces, en este caso, si realmente desea usar excepciones, debería ser una marcada. (A menos que la documentación de transferTo() dejara muy claro que el método no debe invocarse sin verificar primero el equilibrio suficiente utilizando algún otro método de Account , pero esto parecería un poco incómodo).

Pero también tenga en cuenta los elementos 59: evite el uso innecesario de excepciones controladas y 57: use excepciones solo para condiciones excepcionales . Como han señalado otros, este caso puede no justificar una excepción. Considere devolver un documento false (o tal vez un objeto de estado con detalles sobre lo sucedido) si no hay suficiente crédito.

Cuándo usar excepciones marcadas? ¿Honestamente? En mi humilde opinión … nunca. Creo que han pasado unos 6 años desde la última vez que creé una excepción marcada.

No puedes obligar a alguien a lidiar con un error. Podría decirse que hace que el código empeore, no mejor. No puedo decirte la cantidad de veces que me he encontrado con un código como este:

 try { ... } catch (IOException e) { // do nothing } 

Mientras que muchas veces he escrito un código como este:

 try { ... } catch (IOException e) { throw new RuntimeExceptione(e); } 

¿Por qué? Debido a que una condición (no necesariamente IOException, eso es solo un ejemplo) no fue recuperable pero me forzaron a lamentarme y a menudo me veo obligado a elegir entre hacer lo anterior y contaminar mi API solo para propagar una excepción marcada hasta el final a la parte superior donde es (a la derecha) fatal y se registrará.

Hay una razón por la cual las clases auxiliares DAO de Spring traducen la SQLException comprobada a la DataAccessException sin marcar.

Si tiene cosas como la falta de permisos de escritura en un disco, la falta de espacio en el disco u otras condiciones fatales, quiere hacer tanto ruido como sea posible y la forma de hacerlo es con … excepciones no verificadas (o incluso errores).

Además, las excepciones comprobadas rompen la encapsulación .

Esta idea que marcó excepciones para errores “recuperables” es realmente una ilusión.

Las excepciones comprobadas en Java fueron un experimento … un experimento fallido. Deberíamos cortar nuestras pérdidas, admitir que cometimos un error y seguir adelante. En mi humilde opinión .Net lo hizo bien solo teniendo excepciones sin marcar. Nuevamente, tenía la segunda ventaja de aprender de los errores de Java.

En mi humilde opinión, no debería ser una excepción en absoluto. Una excepción, en mi opinión, debe usarse cuando ocurren cosas excepcionales, y no como controles de flujo.

En su caso, no es para nada un estado excepcional que alguien intente transferir más dinero de lo que permite el saldo. Me imagino que estas cosas suceden muy a menudo en el mundo real. Entonces debes progtwigr contra estas situaciones. Una excepción podría ser que su statement if evalúa el saldo bueno, pero cuando el dinero realmente se resta de la cuenta, el saldo ya no es bueno, por alguna extraña razón.

Una excepción podría ser que, justo antes de llamar a transferTo() , verifique que la línea esté abierta al banco. Pero dentro de la transferTo() , el código nota que la línea ya no está abierta, aunque, por lógica, debería ser. ESO es una excepción. Si no se puede abrir la línea, eso no es una excepción, esa es una situación plausible.

Recapitulo de IMHO: Excepciones == magia negra extraña.

ser-constructivo-editar:

Entonces, para no ser demasiado contradictorio, el método en sí podría arrojar una excepción. Pero el uso del método debe controlarse: primero verifica el saldo (fuera del método transferTo() ), y si el saldo es bueno, solo entonces llame a transferTo() . Si transferTo() nota que el saldo, por alguna razón extraña, ya no es bueno, arrojas la excepción, que captas diligentemente.

En ese caso, tiene todos sus patos seguidos, y sabe que no hay nada más que pueda hacer (porque lo que era true volvió false , como si fuera solo), aparte de registrar la excepción, enviar una notificación a alguien y contarle el cliente educadamente que alguien no sacrificó sus vírgenes adecuadamente durante la última luna llena, y el problema se solucionará en el primer momento posible.

less-enterprisey-suggestion-edit:

Si está haciendo esto por su propio placer (y el caso parece ser el siguiente, vea los comentarios), le sugiero que devuelva un booleano. El uso sería algo como esto:

 // ... boolean success = transferTo(otherAccount, ONE_MILLION_DOLLARS_EXCLAMATION); if (!success) { UI.showMessage("Aww shucks. You're not that rich"); return; // or something... } else { profit(); } // ... 

Recientemente tuve un problema con las excepciones, el código arrojó NullPointerException y no tenía idea de por qué, después de algunas investigaciones, se descubrió que la excepción real había sido eliminada (estaba en un código nuevo, por lo que todavía se está haciendo) y el método acaba devolviéndolo nulo. Si marcó excepciones, debe comprender que los progtwigdores malos intentarán atraparlo e ignorar la excepción.

Mi regla es

  • if declaraciones para errores de lógica de negocios (como su código)
  • Cheched excepciones para errores de entorno donde la aplicación puede recuperar
  • excepción no comprobada para errores de entorno donde no hay recuperación

    1. Ejemplo de excepción comprobada: la red no funciona para una aplicación que puede funcionar sin conexión
    2. Ejemplo de excepción no procesada: la base de datos está inactiva en una aplicación web CRUD.

Hay mucha documentación sobre el tema. Puede encontrar mucho navegando por las páginas web de Hibernate, ya que cambiaron todas las excepciones de Hibernate 2.x de marcadas a desactivadas en la versión 3.x

Desde excepciones no verificadas: la controversia :

Si es razonable esperar que un cliente se recupere de una excepción, conviértalo en una excepción marcada. Si un cliente no puede hacer nada para recuperarse de la excepción, conviértalo en una excepción no verificada.

Tenga en cuenta que una excepción sin marcar es una derivada de RuntimeException y una excepción marcada es una derivada de Exception .

¿Por qué lanzar una RuntimeException si un cliente no puede hacer nada para recuperarse de la excepción? El artículo explica:

Las excepciones de tiempo de ejecución representan problemas que son el resultado de un problema de progtwigción y, como tal, no se puede esperar razonablemente que el código del cliente API se recupere de ellos o que los maneje de ninguna manera. Dichos problemas incluyen excepciones aritméticas, como dividir por cero; excepciones de puntero, como intentar acceder a un objeto a través de una referencia nula; e indexar excepciones, como intentar acceder a un elemento de matriz a través de un índice que es demasiado grande o demasiado pequeño.

Mi impresión es que la excepción comprobada es un contrato útil que debe usarse con moderación. El ejemplo clásico de donde creo que una excepción comprobada es una buena idea es una InterruptedException . Mi sensación es que quiero poder detener un hilo / proceso cuando quiero que se detenga, independientemente de cuánto tiempo haya especificado alguien para Thread.sleep ().

Por lo tanto, tratando de responder a su pregunta específica, ¿es algo de lo que realmente quiere asegurarse para que todos traten? En mi opinión, una situación en la que una Account no tiene suficiente dinero es un problema lo suficientemente grave como para tener que lidiar con ella.

En respuesta al comentario de Peter : aquí hay un ejemplo que usa InterruptedException como caso concreto de una excepción que debe manejarse y necesita tener un controlador predeterminado útil. Esto es lo que recomiendo encarecidamente, sin duda en mi trabajo real. Al menos deberías hacer esto:

 catch (InterruptedException ie) { Thread.currentThread().interrupt(); } 

Ese controlador se asegurará de que el código capte la excepción comprobada y haga exactamente lo que usted desea: detener este hilo. Es cierto que, si hay otro manejador de excepciones / eater upstream, no es imposible que maneje menos la excepción. Aun así, FindBugs puede ayudarlo a encontrarlos.

Ahora, la realidad se establece: no se puede forzar necesariamente a todos los que escriban un manejador de excepciones para que su excepción comprobada los maneje bien. Dicho eso, al menos podrás “Buscar usos” y saber dónde se usa y dar algunos consejos.

Forma abreviada: está infligiendo una carga a los usuarios de su método si usa una excepción marcada. Asegúrese de que haya una buena razón para ello, recomiende un método de manejo correcto y documente todo esto extensamente.

Una excepción marcada significa que los clientes de su clase se ven obligados a tratar con el comstackdor. Su código no puede comstackrse a menos que agreguen un bloque try / catch.

Los diseñadores de C # han decidido que se prefieren las excepciones sin marcar.

O puede seguir el estilo C y verificar los valores de retorno y no lanzar excepciones.

Las excepciones tienen un costo, por lo que no deben usarse para el flujo de control, como se señaló anteriormente. Pero lo único que les está yendo es que no pueden ser ignorados.

Si decide que, en este caso, para evitar excepciones, corre el riesgo de que un cliente de su clase ignore el valor devuelto o no verifique el saldo antes de intentar la transferencia.

Yo recomendaría una excepción sin marcar, y le daría un nombre descriptivo como InsufficientFundsException para dejar bien en claro lo que estaba pasando.

La línea no siempre es clara, pero para mí usualmente las excepciones de tiempo de ejecución = errores de progtwigción, excepciones comprobadas = errores externos. Aunque esta es una categorización muy aproximada. Como otros dicen, las excepciones comprobadas te obligan a manejar, o al menos pensar por una fracción de tiempo muy pequeña, al respecto.

En pocas palabras, utilice la excepción comprobada solo como parte de un contrato externo para una biblioteca, y solo si el cliente quiere / necesita atraparla. Recuerde, cuando usa la excepción marcada, se está forzando a sí mismo en la persona que llama. Con la excepción de tiempo de ejecución, si están bien documentados, le está dando a la persona que llama una opción.

Es un problema conocido que las excepciones comprobadas se usan en exceso en Java, pero eso no significa que sean todas malas. Es por eso que es parte integral de la filosofía Spring, por ejemplo ( http://www.springsource.org/about )

La ventaja de las excepciones comprobadas es que el comstackdor obliga al desarrollador a tratarlas antes. La desventaja, en mi opinión de todos modos, es que los desarrolladores tienden a ser perezosos e impacientes, y resuelven el código de manejo de excepciones con la intención de volver y arreglarlo más tarde. Lo cual, por supuesto, rara vez sucede.

Bruce Eckel, autor de Thinking in Java , tiene un buen ensayo sobre este tema.

No creo que el escenario (fondos insuficientes) justifique lanzar una Exception — simplemente no es lo suficientemente excepcional, y debe ser manejado por el flujo de control normal del progtwig. Sin embargo, si realmente tuviera que lanzar una excepción, elegiría una excepción marcada , extendiendo Exception , no RuntimeException que está desmarcada. Esto me obliga a manejar la excepción explícitamente (necesito declarar que se lanzará, o atraparla en algún lugar).

IllegalArgumentException es una subclase de RuntimeException , que lo convierte en una excepción no verificada. Solo consideraría lanzar esto si la persona que llama tiene alguna forma conveniente de determinar si los argumentos del método son legales o no. En su código particular, no está claro si la persona que llama tiene acceso al balance , o si todo el “verificar saldo y transferir fondos” es una operación atómica (si no es así, la persona que llama realmente no tiene una forma conveniente de validar los argumentos) .

EDITAR: Aclaró mi posición sobre lanzar IllegalArgumentException .

Yo prefiero usar excepciones marcadas como pueda.

Si usted es un Desarrollador de API (desarrollador de servicios de fondo), use excepciones marcadas; de lo contrario, use excepciones de tiempo de ejecución.

También tenga en cuenta que, usar excepciones de tiempo de ejecución en algunas situaciones se debe considerar un gran error, por ejemplo, si va a lanzar excepciones de tiempo de ejecución de los beans de sesión (ver: http://m-hewedy.blogspot.com/2010/01/ avoid-throwing-runtimeexception-from.html para obtener más información sobre el problema de usar excpetions de Runtime en beans de sesión).