El caso contra excepciones marcadas

Desde hace varios años, no he podido obtener una respuesta decente a la siguiente pregunta: ¿por qué algunos desarrolladores están en contra de las excepciones marcadas? He tenido numerosas conversaciones, leído cosas en blogs, he leído lo que Bruce Eckel tenía que decir (la primera persona que vi hablar en contra de ellos).

Actualmente estoy escribiendo un código nuevo y prestando mucha atención a cómo trato las excepciones. Estoy tratando de ver el punto de vista de la multitud de “no nos gusta comprobar excepciones” y todavía no puedo verlo.

Cada conversación que tengo termina con la misma pregunta sin respuesta … déjame configurarla:

En general (de cómo se diseñó Java),

  • El error es por cosas que nunca deberían ser atrapadas (la máquina virtual tiene alergia al maní y alguien le arrojó un tarro de cacahuetes)
  • RuntimeException es para cosas que el progtwigdor hizo mal (el progtwigdor salió del final de una matriz)
  • Excepción (excepto RuntimeException) es para cosas que están fuera del control del progtwigdor (el disco se llena mientras se escribe en el sistema de archivos, se alcanzó el límite de manejo del archivo para el proceso y no puede abrir más archivos)
  • Throwable es simplemente el padre de todos los tipos de excepción.

Un argumento común que escucho es que si ocurre una excepción, todo lo que el desarrollador hará es salir del progtwig.

Otro argumento común que escucho es que las excepciones comprobadas dificultan la refactorización del código.

Para el argumento “todo lo que voy a hacer es salir”, digo que incluso si sale, debe mostrar un mensaje de error razonable. Si solo está pisando errores de manejo, sus usuarios no estarán demasiado contentos cuando el progtwig finalice sin una indicación clara de por qué.

Para el grupo “hace que sea difícil refactorizar”, eso indica que no se eligió el nivel adecuado de abstracción. En lugar de declarar que un método arroja una IOException, la IOException se debe transformar en una excepción que sea más adecuada para lo que está sucediendo.

No tengo problemas para envolver Main con catch (Exception) (o en algunos casos catch (Throwable) para asegurarme de que el progtwig pueda salir correctamente, pero siempre encuentro las excepciones específicas que necesito. Hacer eso me permite, como mínimo, muestre un mensaje de error apropiado.

La pregunta a la que la gente nunca responde es esta:

Si lanzas las subclases de RuntimeException en lugar de las subclases de Exception, entonces, ¿cómo sabes lo que debes atrapar?

Si la respuesta es excepción de captura, también se trata de errores del progtwigdor de la misma manera que las excepciones del sistema. Eso me parece equivocado.

Si detecta Throwable, entonces está tratando las excepciones del sistema y los errores de VM (y similares) de la misma manera. Eso me parece equivocado.

Si la respuesta es que solo captas las excepciones que sabes que se lanzan, ¿cómo sabes cuáles son arrojadas? ¿Qué sucede cuando el progtwigdor X lanza una nueva excepción y se olvida de atraparla? Eso me parece muy peligroso.

Yo diría que un progtwig que muestra un rastro de stack está mal. ¿Las personas que no les gusta las excepciones comprobadas no se sienten así?

Entonces, si no le gustan las excepciones marcadas, ¿puede explicar por qué no Y responder la pregunta que no recibe respuesta, por favor?

Editar: no estoy buscando consejos sobre cuándo usar cualquier modelo, lo que busco es por qué las personas se extienden desde RuntimeException porque no les gusta extenderse desde Exception y / o por qué atrapan una excepción y luego vuelven a lanzar una RuntimeException en lugar de agregar lanzamientos a su método. Quiero entender la motivación para no gustarme de las excepciones marcadas.

Creo que leí la misma entrevista de Bruce Eckel que tú, y siempre me ha molestado. De hecho, el argumento fue hecho por el entrevistado (si este es de hecho el post del que está hablando) Anders Hejlsberg, el genio de MS detrás de .NET y C #.

http://www.artima.com/intv/handcuffs.html

A pesar de que soy de Hejlsberg y su trabajo, este argumento siempre me ha parecido falso. Básicamente se reduce a:

“Las excepciones comprobadas son malas porque los progtwigdores simplemente abusan de ellas al capturarlas y descartarlas siempre, lo que lleva a que se oculten e ignoren los problemas que de otro modo se presentarían al usuario”.

Al decir “de otro modo presentado al usuario” me refiero a que si usa una excepción de tiempo de ejecución, el progtwigdor perezoso simplemente lo ignorará (en lugar de atraparlo con un bloque catch vacío) y el usuario lo verá.

El resumen del resumen del argumento es que “los progtwigdores no los usarán correctamente y no usarlos correctamente es peor que no tenerlos” .

Hay algo de cierto en este argumento y, de hecho, sospecho que la motivación de Goslings para no anular operadores en Java proviene de un argumento similar: confunden al progtwigdor porque a menudo se abusa de ellos.

Pero al final, me parece un argumento falso de Hejlsberg y posiblemente uno post-hoc creado para explicar la falta en lugar de una decisión bien pensada.

Yo diría que si bien el uso excesivo de las excepciones comprobadas es malo y tiende a un manejo descuidado por parte de los usuarios, el uso adecuado de ellas permite que el progtwigdor API brinde grandes beneficios al progtwigdor cliente API.

Ahora el progtwigdor de API tiene que tener cuidado de no arrojar excepciones comprobadas por todos lados, o simplemente molestarán al progtwigdor cliente. El progtwigdor cliente muy perezoso recurrirá a catch (Exception) {} como advierte Hejlsberg, y se perderán todos los beneficios y se desencadenará el infierno. Pero en algunas circunstancias, simplemente no hay sustituto para una buena excepción comprobada.

Para mí, el ejemplo clásico es la API de abrir archivos. Cada lenguaje de progtwigción en la historia de los lenguajes (al menos en los sistemas de archivos) tiene una API en alguna parte que le permite abrir un archivo. Y cada progtwigdor cliente que usa esta API sabe que tienen que lidiar con el caso de que el archivo que intentan abrir no existe. Permítanme reformular eso: cada progtwigdor cliente que use esta API debería saber que tienen que lidiar con este caso. Y ahí está el problema: ¿puede el progtwigdor de API ayudarlos a saber que deben lidiar con ello mediante comentarios solos o pueden insistir en que el cliente se encargue de ello?

En C, el idioma es algo así como

  if (f = fopen("goodluckfindingthisfile")) { ... } else { // file not found ... 

donde fopen indica fracaso devolviendo 0 y C (tontamente) te permite tratar 0 como un booleano y … Básicamente, aprendes este modismo y estás bien. Pero qué pasa si eres un novato y no aprendiste el idioma. Entonces, por supuesto, comienzas con

  f = fopen("goodluckfindingthisfile"); f.read(); // BANG! 

y aprende de la manera difícil.

Tenga en cuenta que aquí solo hablamos de los lenguajes fuertemente tipados: hay una idea clara de lo que es una API en un lenguaje fuertemente tipado: es una mezcla heterogénea de funcionalidad (métodos) que puede usar con un protocolo claramente definido para cada uno.

Ese protocolo claramente definido generalmente se define mediante una firma de método. Aquí fopen requiere que le pase una cadena (o un char * en el caso de C). Si le das algo más obtienes un error en tiempo de comstackción. No siguió el protocolo, no está utilizando la API correctamente.

En algunos idiomas (oscuros), el tipo de devolución también forma parte del protocolo. Si intentas llamar al equivalente de fopen() en algunos idiomas sin asignarlo a una variable, también obtendrás un error en tiempo de comstackción (solo puedes hacer eso con las funciones nulas).

El punto que trato de plantear es que: en un lenguaje estáticamente tipado, el progtwigdor de API alienta al cliente a usar la API de manera adecuada al evitar que su código de cliente se compile si comete errores obvios.

(En un lenguaje de tipado dynamic, como Ruby, puede pasar cualquier cosa, por ejemplo, un flotador, como nombre de archivo, y se comstackrá. ¿Por qué molestar al usuario con excepciones comprobadas si ni siquiera va a controlar los argumentos del método? los argumentos aquí se aplican solo a los idiomas con tipado estático).

Entonces, ¿qué pasa con las excepciones marcadas?

Bueno, esta es una de las API de Java que puede usar para abrir un archivo.

 try { f = new FileInputStream("goodluckfindingthisfile"); } catch (FileNotFoundException e) { // deal with it. No really, deal with it! ... // this is me dealing with it } 

¿Ves esa captura? Aquí está la firma para ese método API:

 public FileInputStream(String name) throws FileNotFoundException 

Tenga en cuenta que FileNotFoundException es una excepción comprobada .

El progtwigdor de API te dice esto: “Puedes usar este constructor para crear un nuevo FileInputStream pero

a) debe pasar el nombre del archivo como una cadena
b) debe aceptar la posibilidad de que el archivo no se encuentre en tiempo de ejecución ”

Y ese es todo el punto en lo que a mí respecta.

La clave es básicamente lo que la pregunta establece como “Cosas que están fuera del control del progtwigdor”. Mi primer pensamiento fue que él / ella quiere decir cosas que están fuera del control de los progtwigdores API . Pero, de hecho, las excepciones comprobadas cuando se usan correctamente deberían ser realmente para cosas que están fuera del control tanto del progtwigdor cliente como del progtwigdor API. Creo que esta es la clave para no abusar de las excepciones marcadas.

Creo que el archivo abierto ilustra el punto muy bien. El progtwigdor de API sabe que puede darles un nombre de archivo que no existe en el momento en que se llama a la API, y que no podrán devolverle lo que usted quería, pero tendrá que lanzar una excepción. También saben que esto sucederá con bastante frecuencia y que el progtwigdor del cliente podría esperar que el nombre del archivo sea correcto en el momento en que escribió la llamada, pero podría estar equivocado en el tiempo de ejecución por razones que escapan también a su control.

Entonces, la API lo hace explícito: habrá casos en los que este archivo no exista en el momento en que me llame y tendrá que lidiar con él.

Esto sería más claro con un contra-caso. Imagina que estoy escribiendo una tabla API. Tengo el modelo de tabla en alguna parte con una API que incluye este método:

 public RowData getRowData(int row) 

Ahora, como progtwigdor de API, sé que habrá casos en que algunos clientes pasen un valor negativo para la fila o un valor de fila fuera de la tabla. Así que podría sentir la tentación de lanzar una excepción marcada y forzar al cliente a manejarla:

 public RowData getRowData(int row) throws CheckedInvalidRowNumberException 

(Realmente no lo llamaría “Checked”, por supuesto).

Este es un mal uso de las excepciones marcadas. El código del cliente va a estar lleno de llamadas para obtener datos de fila, cada uno de los cuales tendrá que usar un try / catch, ¿y para qué? ¿Van a informar al usuario que se buscó la fila incorrecta? Probablemente no, porque cualquiera que sea la interfaz de usuario que rodea mi vista de tabla, no debe permitir que el usuario entre en un estado donde se solicita una fila ilegal. Entonces, es un error por parte del progtwigdor cliente.

El progtwigdor de API todavía puede predecir que el cliente codificará dichos errores y debería manejarlo con una excepción de tiempo de ejecución como una IllegalArgumentException .

Con una excepción marcada en getRowData , este es claramente un caso que llevará al progtwigdor perezoso de Hejlsberg simplemente agregando capturas vacías. Cuando eso sucede, los valores de filas ilegales no serán obvios ni para el probador ni para la depuración del desarrollador del cliente, sino que generarán errores que son difíciles de precisar de dónde provienen. Los cohetes Arianne explotarán después del lanzamiento.

De acuerdo, este es el problema: digo que la excepción comprobada FileNotFoundException no es solo una buena cosa, sino una herramienta esencial en la caja de herramientas de los progtwigdores API para definir la API de la manera más útil para el progtwigdor cliente. Pero la CheckedInvalidRowNumberException es un gran inconveniente, que conduce a una mala progtwigción y debe evitarse. Pero cómo diferenciar.

Supongo que no es una ciencia exacta y supongo que subyace y quizás justifica en cierta medida el argumento de Hejlsberg. Pero no estoy feliz de arrojar al bebé con el agua del baño aquí, así que permítanme extraer algunas reglas aquí para distinguir las buenas excepciones comprobadas de las malas:

  1. Fuera del control del cliente o Cerrado vs Abierto:

    Las excepciones comprobadas solo deben usarse cuando el caso de error está fuera de control tanto de la API como del progtwigdor cliente. Esto tiene que ver con qué tan abierto o cerrado está el sistema. En una interfaz de usuario restringida donde el progtwigdor cliente tiene control, por ejemplo, sobre todos los botones, comandos de teclado, etc. que agregan y eliminan filas de la vista de tabla (un sistema cerrado), es un error de progtwigción del cliente si intenta recuperar datos de una fila inexistente. En un sistema operativo basado en archivos donde cualquier número de usuarios / aplicaciones puede agregar y eliminar archivos (un sistema abierto), es concebible que el archivo que el cliente está solicitando se haya eliminado sin su conocimiento, por lo que se espera que lo traten. .

  2. Ubicuidad:

    Las excepciones comprobadas no se deben usar en una llamada API que el cliente realiza con frecuencia. Con frecuencia me refiero a muchos lugares en el código del cliente, no con frecuencia en el tiempo. Así que un código de cliente no tiende a intentar abrir el mismo archivo mucho, pero mi vista de tabla obtiene RowData todos lados desde diferentes métodos. En particular, voy a escribir mucho código como

     if (model.getRowData().getCell(0).isEmpty()) 

    y será doloroso tener que envolver / probar cada vez.

  3. Informar al usuario:

    Las excepciones controladas se deben usar en los casos en que se pueda imaginar un mensaje de error útil que se presenta al usuario final. Este es el “¿y qué harás cuando ocurra?” pregunta que planteé arriba. También se relaciona con el ítem 1. Como puede predecir que algo fuera de su sistema API de cliente puede hacer que el archivo no esté allí, razonablemente puede decirle al usuario al respecto:

     "Error: could not find the file 'goodluckfindingthisfile'" 

    Debido a que su número de fila ilegal fue causado por un error interno y no es culpa del usuario, realmente no hay información útil que pueda darles. Si su aplicación no permite que las excepciones de tiempo de ejecución caigan en la consola, probablemente terminará dándoles un mensaje desagradable como:

     "Internal error occured: IllegalArgumentException in ...." 

    En resumen, si no cree que su progtwigdor cliente pueda explicar su excepción de una manera que ayude al usuario, entonces probablemente no deba usar una excepción marcada.

Entonces esas son mis reglas. Un poco artificial, y sin duda habrá excepciones (por favor, ayúdame a refinarlas si quieres). Pero mi argumento principal es que hay casos como FileNotFoundException donde la excepción comprobada es tan importante y útil como parte del contrato de la API como los tipos de parámetros. Por lo tanto, no deberíamos prescindir de él solo porque se use indebidamente.

Lo siento, no fue mi intención hacer que esto fuera tan largo y raro. Déjame terminar con dos sugerencias:

R: Progtwigdores de API: use excepciones comprobadas con moderación para preservar su utilidad. En caso de duda use una excepción sin marcar.

B: Progtwigdores de clientes: adquiera el hábito de crear una excepción envuelta (google it) desde el principio de su desarrollo. JDK 1.4 y posterior proporcionan un constructor en RuntimeException para esto, pero también puedes crear fácilmente el tuyo propio. Aquí está el constructor:

 public RuntimeException(Throwable cause) 

Luego hágase el hábito de manejar una excepción marcada y se siente flojo (o si cree que el progtwigdor de la API fue excesivamente entusiasta al usar la excepción marcada), no se trague la excepción, envuélvala y volver a lanzarlo.

 try { overzealousAPI(thisArgumentWontWork); } catch (OverzealousCheckedException exception) { throw new RuntimeException(exception); } 

Póngalo en una de las pequeñas plantillas de código de IDE y úselo cuando se sienta flojo. De esta forma, si realmente necesita manejar la excepción marcada, se verá obligado a volver y tratar con ella después de ver el problema en tiempo de ejecución. Porque créeme (y Anders Hejlsberg), nunca volverás a ese TODO en tu

 catch (Exception e) { /* TODO deal with this at some point (yeah right) */} 

Lo que pasa con las excepciones comprobadas es que no son realmente excepciones por la comprensión habitual del concepto. En cambio, son valores de retorno alternativos de API.

La idea general de las excepciones es que un error lanzado en algún punto de la cadena de llamadas puede burbujear y ser manejado por código en algún lugar más arriba, sin que el código intermedio tenga que preocuparse por ello. Las excepciones comprobadas, por otro lado, requieren cada nivel de código entre el lanzador y el catcher para declarar que conocen todas las formas de excepción que pueden atravesarlas. Esto es realmente poco diferente en la práctica, si las excepciones marcadas eran simplemente valores de retorno especiales que la persona que llamaba tenía que verificar. por ejemplo, [pseudocódigo]:

 public [int or IOException] writeToStream(OutputStream stream) { [void or IOException] a= stream.write(mybytes); if (a instanceof IOException) return a; return mybytes.length; } 

Como Java no puede hacer valores de retorno alternativos, o simples tuplas en línea como valores de retorno, las excepciones marcadas son una respuesta razonable.

El problema es que una gran cantidad de código, incluidas grandes franjas de la biblioteca estándar, uso indebido de excepciones comprobadas para condiciones reales excepcionales que es posible que desee atrapar varios niveles. ¿Por qué IOException no es una RuntimeException? En cualquier otro idioma, puedo permitir que ocurra una excepción IO, y si no hago nada para manejarlo, mi aplicación se detendrá y obtendré un útil rastro de stack para mirar. Esto es lo mejor que puede suceder.

Tal vez dos métodos desde el ejemplo que desea capturar todas las excepciones IO de todo el proceso de escritura a secuencia, aborten el proceso y salten al código de informe de error; en Java no puede hacer eso sin agregar ‘throws IOException’ en cada nivel de llamada, incluso en niveles que no tienen IO. Tales métodos no deberían necesitar saber sobre el manejo de excepciones; tener que agregar excepciones a sus firmas:

  1. aumenta innecesariamente el acoplamiento;
  2. hace que las firmas de interfaz sean muy frágiles para cambiar;
  3. hace que el código sea menos legible;
  4. es tan molesto que la reacción común del progtwigdor es vencer al sistema haciendo algo horrible como ‘throws Exception’, ‘catch (Exception e) {}’, o envolviendo todo en una RuntimeException (lo que hace que la depuración sea más difícil).

Y luego hay un montón de excepciones ridículas de la biblioteca como:

 try { httpconn.setRequestMethod("POST"); } catch (ProtocolException e) { throw new CanNeverHappenException("oh dear!"); } 

Cuando tienes que ordenar tu código con una basura tan ridícula como esta, no es de extrañar que las excepciones controladas reciban un montón de odio, aunque en realidad este es solo un diseño simple de API deficiente.

Otro efecto particularmente negativo es en Inversión de control, donde el componente A proporciona una callback al componente genérico B. El componente A quiere permitir que una excepción arroje desde su callback al lugar donde llamó al componente B, pero no puede devolverla. porque eso cambiaría la interfaz de callback que es fijada por B. A solo puede hacerlo al envolver la excepción real en una RuntimeException, que es aún más una regla repetitiva para escribir.

Las excepciones comprobadas tal como se implementaron en Java y su biblioteca estándar significan repetitivo, repetitivo, repetitivo. En un lenguaje ya prolijo, esto no es una victoria.

En lugar de repetir todas las (muchas) razones en contra de las excepciones marcadas, escogeré solo una. He perdido la cuenta de la cantidad de veces que escribí este bloque de código:

 try { // do stuff } catch (AnnoyingcheckedException e) { throw new RuntimeException(e); } 

El 99% de las veces no puedo hacer nada al respecto. Finalmente, los bloques realizan la limpieza necesaria (o al menos deberían).

También he perdido la cuenta de la cantidad de veces que he visto esto:

 try { // do stuff } catch (AnnoyingCheckedException e) { // do nothing } 

¿Por qué? Porque alguien tuvo que lidiar con eso y fue flojo. ¿Estaba mal? Por supuesto. ¿Sucede? Absolutamente. ¿Qué pasa si esta fuera una excepción sin marcar en su lugar? La aplicación acaba de morir (lo cual es preferible a tragar una excepción).

Y luego tenemos un código exasperante que usa excepciones como una forma de control de flujo, como lo hace java.text.Format . Bzzzt. Incorrecto. Un usuario que pone “abc” en un campo de número en un formulario no es una excepción.

Ok, supongo que fueron tres razones.

Sé que esta es una vieja pregunta, pero he pasado un tiempo luchando con excepciones comprobadas y tengo algo que agregar. Por favor perdóname por la duración de esto!

Mi carne de res principal con excepciones comprobadas es que arruinan el polymorphism. Es imposible hacer que jueguen bien con las interfaces polimórficas.

Tome la buena interfaz de la List Java. Tenemos implementaciones comunes en memoria como ArrayList y LinkedList . También tenemos la clase esquelética AbstractList que facilita el diseño de nuevos tipos de listas. Para una lista de solo lectura, debemos implementar solo dos métodos: size() y get(int index) .

Esta clase WidgetList ejemplo lee algunos objetos de tamaño fijo de tipo Widget (no se muestran) desde un archivo:

 class WidgetList extends AbstractList { private static final int SIZE_OF_WIDGET = 100; private final RandomAccessFile file; public WidgetList(RandomAccessFile file) { this.file = file; } @Override public int size() { return (int)(file.length() / SIZE_OF_WIDGET); } @Override public Widget get(int index) { file.seek((long)index * SIZE_OF_WIDGET); byte[] data = new byte[SIZE_OF_WIDGET]; file.read(data); return new Widget(data); } } 

Al exponer los widgets usando la conocida interfaz de List , puede recuperar elementos ( list.get(123) ) o iterar una lista ( for (Widget w : list) ... ) sin necesidad de conocer WidgetList . Uno puede pasar esta lista a cualquier método estándar que use listas genéricas o envolverlo en una Collections.synchronizedList . El código que lo usa no necesita saber ni importar si los “Widgets” están hechos en el lugar, provienen de una matriz, o se leen desde un archivo, o una base de datos, o desde la red, o desde un futuro relevador de subespacio. Todavía funcionará correctamente porque la interfaz de la List está implementada correctamente.

Excepto que no lo es. La clase anterior no se comstack porque los métodos de acceso a archivos pueden arrojar una IOException , una excepción comprobada que debe “capturar o especificar”. No puede especificarlo como lanzado : el comstackdor no lo permitirá porque eso violaría el contrato de la interfaz de la List . Y no hay forma útil de que WidgetList pueda manejar la excepción (como expondré más adelante).

Al parecer, lo único que se puede hacer es capturar y volver a lanzar las excepciones comprobadas como alguna excepción no verificada:

 @Override public int size() { try { return (int)(file.length() / SIZE_OF_WIDGET); } catch (IOException e) { throw new WidgetListException(e); } } public static class WidgetListException extends RuntimeException { public WidgetListException(Throwable cause) { super(cause); } } 

((Editar: Java 8 ha agregado una clase UncheckedIOException para exactamente este caso: para atrapar y volver a lanzar IOException a través de los límites del método polimórfico. ¡Demuestra mi punto!))

Entonces, las excepciones comprobadas simplemente no funcionan en casos como este. No puedes tirarlos. Lo mismo ocurre con un Map inteligente respaldado por una base de datos, o una implementación de java.util.Random conectado a una fuente de entropía cuántica a través de un puerto COM. Tan pronto como intenta hacer algo nuevo con la implementación de una interfaz polimórfica, el concepto de excepciones comprobadas falla. Pero las excepciones comprobadas son tan insidiosas que todavía no te dejarán en paz, porque aún tienes que atrapar y volver a lanzar cualquiera de los métodos de nivel inferior, abarrotando el código y abarrotando la traza de la stack.

Encuentro que la ubicua interfaz Runnable menudo está respaldada en esta esquina, si llama algo que arroja excepciones marcadas. No puede lanzar la excepción tal como está, por lo que todo lo que puede hacer es saturar el código capturando y reiniciando como una RuntimeException .

En realidad, puedes lanzar excepciones comprobadas no declaradas si recurres a hacks. A la JVM, en tiempo de ejecución, no le importan las reglas de excepción comprobadas, por lo que debemos engañar solo al comstackdor. La forma más fácil de hacerlo es abusar de los generics. Este es mi método para ello (se muestra el nombre de clase porque (antes de Java 8) se requiere en la syntax de llamada para el método genérico):

 class Util { /** * Throws any {@link Throwable} without needing to declare it in the * method's {@code throws} clause. * * 

When calling, it is suggested to prepend this method by the * {@code throw} keyword. This tells the compiler about the control flow, * about reachable and unreachable code. (For example, you don't need to * specify a method return value when throwing an exception.) To support * this, this method has a return type of {@link RuntimeException}, * although it never returns anything. * * @param t the {@code Throwable} to throw * @return nothing; this method never returns normally * @throws Throwable that was provided to the method * @throws NullPointerException if {@code t} is {@code null} */ public static RuntimeException sneakyThrow(Throwable t) { return Util.sneakyThrow1(t); } @SuppressWarnings("unchecked") private static RuntimeException sneakyThrow1( Throwable t) throws T { throw (T)t; } }

¡Viva! Usando esto podemos lanzar una excepción marcada a cualquier profundidad en la stack sin declararla, sin envolverla en una RuntimeException y sin saturar el seguimiento de la stack. Usando el ejemplo “WidgetList” otra vez:

 @Override public int size() { try { return (int)(file.length() / SIZE_OF_WIDGET); } catch (IOException e) { throw sneakyThrow(e); } } 

Desafortunadamente, el insulto final de las excepciones comprobadas es que el comstackdor se niega a permitirle atrapar una excepción marcada si, en su opinión errónea, no pudo haber sido lanzada. (Las excepciones no revisadas no tienen esta regla). Para detectar la excepción lanzada a escondidas, tenemos que hacer esto:

 try { ... } catch (Throwable t) { // catch everything if (t instanceof IOException) { // handle it ... } else { // didn't want to catch this one; let it go throw t; } } 

Eso es un poco incómodo, pero en el lado positivo, todavía es un poco más simple que el código para extraer una excepción marcada que estaba envuelta en una RuntimeException .

Afortunadamente, el throw t; La statement es legal aquí, aunque se comprueba el tipo de t , gracias a una regla agregada en Java 7 sobre reintroducción de excepciones detectadas.


Cuando las excepciones comprobadas se encuentran con el polymorphism, el caso opuesto también es un problema: cuando se especifica que un método arroja una excepción comprobada, una implementación anulada no lo hace. Por ejemplo, los métodos de write la clase abstracta OutputStream especifican throws IOException . ByteArrayOutputStream es una subclase que escribe en una matriz en memoria en lugar de una verdadera fuente de E / S. Sus métodos de write anulados no pueden causar IOException , por lo que no tienen cláusula throws , y puede llamarlos sin preocuparse por el requisito de capturar o especificar.

Excepto no siempre Supongamos que Widget tiene un método para guardarlo en una transmisión:

 public void writeTo(OutputStream out) throws IOException; 

Declarar que este método acepta un OutputStream simple es lo correcto, por lo que se puede usar polimórficamente con todo tipo de resultados: archivos, bases de datos, la red, etc. Y matrices en memoria. Sin embargo, con una matriz en memoria, existe un requisito espurio de manejar una excepción que en realidad no puede suceder:

 ByteArrayOutputStream out = new ByteArrayOutputStream(); try { someWidget.writeTo(out); } catch (IOException e) { // can't happen (although we shouldn't ignore it if it does) throw new RuntimeException(e); } 

Como de costumbre, las excepciones marcadas se interponen en el camino. Si sus variables se declaran como un tipo base que tiene más requisitos de excepción abiertos, debe agregar controladores para esas excepciones, incluso si sabe que no se producirán en su aplicación.

Pero espera, las excepciones comprobadas son realmente tan molestas, ¡que ni siquiera te dejarán hacer lo contrario! Imagine que actualmente IOException cualquier IOException producida por las llamadas de write en un OutputStream , pero desea cambiar el tipo declarado de la variable a ByteArrayOutputStream , el comstackdor le criticará por tratar de detectar una excepción comprobada que dice que no se puede lanzar.

Esa regla causa algunos problemas absurdos. Por ejemplo, ByteArrayOutputStream no anula uno de los tres métodos de write de OutputStream . Específicamente, write(byte[] data) es un método de conveniencia que escribe la matriz completa llamando a write(byte[] data, int offset, int length) con un desplazamiento de 0 y la longitud de la matriz. ByteArrayOutputStream anula el método de tres argumentos pero hereda el método de conveniencia de un solo argumento como está. El método heredado hace exactamente lo correcto, pero incluye una cláusula throws no deseados. Eso fue quizás un descuido en el diseño de ByteArrayOutputStream , pero nunca lo pueden arreglar porque rompería la compatibilidad de la fuente con cualquier código que capte la excepción, ¡la excepción que nunca, nunca, y nunca será lanzada!

Esa regla es molesta durante la edición y la depuración también. Por ejemplo, a veces voy a comentar una llamada de método temporalmente, y si podría haber arrojado una excepción marcada, el comstackdor ahora se quejará de la existencia de los bloques locales de try y catch . Así que también tengo que comentarlos, y ahora cuando edite el código dentro, el IDE sangrará al nivel incorrecto porque { y } están comentados. Gah! Es una pequeña queja, pero parece que las únicas excepciones comprobadas que se hacen son los problemas.


Ya casi termino. Mi frustración final con las excepciones comprobadas es que, en la mayoría de los sitios de llamadas , no hay nada útil que pueda hacer con ellos. Idealmente, cuando algo va mal, tendremos un controlador competente específico de la aplicación que pueda informar al usuario del problema y / o finalizar o reintentar la operación según corresponda. Solo un manejador en la parte superior de la stack puede hacer esto porque es el único que conoce el objective general.

En cambio, obtenemos el siguiente modismo, que es endiablado como una forma de cerrar el comstackdor:

 try { ... } catch (SomeStupidExceptionOmgWhoCares e) { e.printStackTrace(); } 

En una GUI o progtwig automatizado, el mensaje impreso no se verá. Peor aún, continúa con el rest del código después de la excepción. ¿La excepción no es realmente un error? Entonces no lo imprimas. Otherwise, something else is going to blow up in a moment, by which time the original exception object will be gone. This idiom is no better than BASIC’s On Error Resume Next or PHP’s error_reporting(0); .

Calling some kind of logger class is not much better:

 try { ... } catch (SomethingWeird e) { logger.log(e); } 

That is just as lazy as e.printStackTrace(); and still plows on with code in an indeterminate state. Plus, the choice of a particular logging system or other handler is application-specific, so this hurts code reuse.

¡Pero espera! There is an easy and universal way to find the application-specific handler. It’s higher up the call stack (or it is set as the Thread’s uncaught exception handler ). So in most places, all you need to do is throw the exception higher up the stack . Eg, throw e; . Checked exceptions just get in the way.

I’m sure checked exceptions sounded like a good idea when the language was designed, but in practice I’ve found them to be all bother and no benefit.

Well, it’s not about displaying a stacktrace or silently crashing. It’s about being able to communicate errors between layers.

The problem with checked exceptions is they encourage people to swallow important details (namely, the exception class). If you choose not to swallow that detail, then you have to keep adding throws declarations across your whole app. This means 1) that a new exception type will affect lots of function signatures, and 2) you can miss a specific instance of the exception you actually -want- to catch (say you open a secondary file for a function that writes data to a file. The secondary file is optional, so you can ignore its errors, but because the signature throws IOException , it’s easy to overlook this).

I’m actually dealing with this situation now in an application. We repackaged almost exceptions as AppSpecificException. This made signatures really clean and we didn’t have to worry about exploding throws in signatures.

Of course, now we need to specialize the error handling at the higher levels, implementing retry logic and such. Everything is AppSpecificException, though, so we can’t say “If an IOException is thrown, retry” or “If ClassNotFound is thrown, abort completely”. We don’t have a reliable way of getting to the real exception because things get repackaged again and again as they pass between our code and third-party code.

This is why I’m a big fan of the exception handling in python. You can catch only the things you want and/or can handle. Everything else bubbles up as if you rethrew it yourself (which you have done anyways).

I’ve found, time and time again, and throughout the project I mentioned, that exception handling falls into 3 categories:

  1. Catch and handle a specific exception. This is to implement retry logic, for example.
  2. Catch and rethrow other exceptions. All that happens here is usually logging, and its usually a trite message like “Unable to open $filename”. These are errors you can’t do anything about; only a higher levels knows enough to handle it.
  3. Catch everything and display an error message. This is usually at the very root of a dispatcher, and all it does it make sure it can communicate the error to the caller via a non-Exception mechanism (popup dialogue, marshaling an RPC Error object, etc).

Artima published an interview with one of the architects of .NET, Anders Hejlsberg, which acutely covers the arguments against checked exceptions. A short taster:

The throws clause, at least the way it’s implemented in Java, doesn’t necessarily force you to handle the exceptions, but if you don’t handle them, it forces you to acknowledge precisely which exceptions might pass through. It requires you to either catch declared exceptions or put them in your own throws clause. To work around this requirement, people do ridiculous things. For example, they decorate every method with, “throws Exception.” That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn’t help anybody.

SNR

Firstly, checked exceptions decrease the “signal-to-noise ratio” for the code. Anders Hejlsberg also talks about imperative vs declarative programming which is a similar concept. Anyway consider the following code snippets:

Update UI from non UI-thread in Java:

 try { // Run the update code on the Swing thread SwingUtilities.invokeAndWait(() -> { try { // Update UI value from the file system data FileUtility f = new FileUtility(); uiComponent.setValue(f.readSomething()); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (InterruptedException ex) { throw new IllegalStateException("Interrupted updating UI", ex); } catch (InvocationTargetException ex) { throw new IllegalStateException("Invocation target exception updating UI", ex); } 

Update UI from non UI-thread in C#:

 private void UpdateValue() { // Ensure the update happens on the UI thread if (InvokeRequired) { Invoke(new MethodInvoker(UpdateValue)); } else { // Update UI value from the file system data FileUtility f = new FileUtility(); uiComponent.Value = f.ReadSomething(); } } 

Which seems a lot clearer to me. When you start to do more and more UI work in Swing checked exceptions start to become really annoying and useless.

Jail Break

To implement even the most basic of implementations, such as Java’s List interface, checked exceptions as a tool for design by contract fall down. Consider a list that is backed by a database or a filesystem or any other implementation that throws a checked exception. The only possible implementation is to catch the checked exception and rethrow it as an unchecked exception:

 @Override public void clear() { try { backingImplementation.clear(); } catch (CheckedBackingImplException ex) { throw new IllegalStateException("Error clearing underlying list.", ex); } } 

And now you have to ask what is the point of all that code? The checked exceptions just add noise, the exception has been caught but not handled and design by contract (in terms of checked exceptions) has broken down.

Conclusión

  • Catching exceptions is different to handling them.
  • Checked exceptions add noise to the code.
  • Exception handling works well in C# without them.

I blogged about this previously .

I initially agreed with you, as I’ve always been in favour of checked exceptions, and began to think about why I don’t like not having checked exceptions in .Net. But then I realised that I don’t infact like checked exceptions.

To answer you question, yes, I like my programs to show stack traces, preferably really ugly ones. I want the application to explode into a horrible heap of the ugliest error messages you could ever want to see.

And the reason is because, if it does that, I have to fix it, and I have to fix it right away. I want to know immediately that there is a problem.

How many times do you actually handle exceptions? I’m not talking about catching exceptions — I’m talking about handling them? It’s too easy to write the following:

 try { thirdPartyMethod(); } catch(TPException e) { // this should never happen } 

And I know you can say that it’s bad practice, and that ‘the answer’ is to do something with the exception (let me guess, log it?), but in the Real World ™, most programmers just don’t do it.

So yes, I don’t want to catch exceptions if I don’t have to do so, and I want my program to blow up spectacularly when I screw up. Silently failing is the worst possible outcome.

En breve:

Exceptions are an API design question. — No more, no less.

The argument for checked exceptions:

To understand why checked exceptions might not be good thing, let’s turn the question around and ask: When or why are checked exceptions attractive, ie why would you want the compiler to enforce declaration of exceptions?

The answer is obvious: Sometimes you need to catch an exception, and that is only possible if the code being called offers a specific exception class for the error that you are interested in.

Hence, the argument for checked exceptions is that the compiler forces programmers to declare which exceptions are thrown, and hopefully the programmer will then also document specific exception classes and the errors that cause them.

In reality though, ever too often a package com.acme only throws an AcmeException rather than specific subclasses. Callers then need to handle, declare, or re-signal AcmeExceptions , but still cannot be certain whether an AcmeFileNotFoundError happened or an AcmePermissionDeniedError .

So if you’re only interested in an AcmeFileNotFoundError , the solution is to file a feature request with the ACME programmers and tell them to implement, declare, and document that subclass of AcmeException .

So why bother?

Hence, even with checked exceptions, the compiler cannot force programmers to throw useful exceptions. It is still just a question of the API’s quality.

As a result, languages without checked exceptions usually do not fare much worse. Programmers might be tempted to throw unspecific instances of a general Error class rather than an AcmeException , but if they care at all about their API quality, they will learn to introduce an AcmeFileNotFoundError after all.

Overall, the specification and documentation of exceptions is not much different from the specification and documentation of, say, ordinary methods. Those, too, are an API design question, and if a programmer forgot to implement or export a useful feature, the API needs to be improved so that you can work with it usefully.

If you follow this line of reasoning, it should be obvious that the “hassle” of declaring, catching, and re-throwing of exceptions that is so common in languages like Java often adds little value.

It is also worth noting that the Java VM does not have checked exceptions — only the Java compiler checks them, and class files with changed exception declarations are compatible at run time. Java VM security is not improved by checked exceptions, only coding style.

The article Effective Java Exceptions explains nicely when to use unchecked and when to use checked exceptions. Here are some quotes from that article to highlight the main points:

Contingency: An expected condition demanding an alternative response from a method that can be expressed in terms of the method’s intended purpose. The caller of the method expects these kinds of conditions and has a strategy for coping with them.

Fault: An unplanned condition that prevents a method from achieving its intended purpose that cannot be described without reference to the method’s internal implementation.

(SO doesn’t allow tables, so you might want to read the following from the original page …)

Contingency

  • Is considered to be: A part of the design
  • Is expected to happen: Regularly but rarely
  • Who cares about it: The upstream code that invokes the method
  • Examples: Alternative return modes
  • Best Mapping: A checked exception

Fault

  • Is considered to be: A nasty surprise
  • Is expected to happen: Never
  • Who cares about it: The people who need to fix the problem
  • Examples: Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers
  • Best Mapping: An unchecked exception

I have been working with several developers in the last three years in relatively complex applications. We have a code base that uses Checked Exceptions quite often with proper error handling, and some other that doesn’t.

So far, I have it found easier to work with the code base with Checked Exceptions. When I am using someone else’s API, it is nice that I can see exactly what kind of error conditions I can expect when I call the code and handle them properly, either by logging, displaying or ignoring (Yes, there is valid cases for ignoring exceptions, such as a ClassLoader implementation). That gives the code I am writing an opportunity to recover. All runtime exceptions I propagate up until they are cached and handled with some generic error handling code. When I find a checked exception that I don’t really want to handle at a specific level, or that I consider a programming logic error, then I wrap it into a RuntimeException and let it bubble up. Never, ever swallow an exception without a good reason (and good reasons for doing this are rather scarce)

When I work with the codebase that does not have checked exceptions, it makes it to me a little bit harder to know before hand what can I expect when calling the function, which can break some stuff terribly.

This is all of course a matter of preference and developer skill. Both ways of programming and error handling can be equally effective (or noneffective), so I wouldn’t say that there is The One Way.

All in all, I find it easier to work with Checked Exceptions, specially in large projects with lot of developers.

Exception categories

When talking about exceptions I always refer back to Eric Lippert’s Vexing exceptions blog article. He places exceptions into these categories:

  • Fatal – These exceptions are not your fault : you cannot prevent then, and you cannot sensibly handle them. For example, OutOfMemoryError or ThreadAbortException .
  • Boneheaded – These exceptions are your fault : you should have prevented them, and they represent bugs in your code. For example, ArrayIndexOutOfBoundsException , NullPointerException or any IllegalArgumentException .
  • Vexing – These exceptions are not exceptional , not your fault, you cannot prevent them, but you’ll have to deal with them. They are often the result of an unfortunate design decision, such as throwing NumberFormatException from Integer.parseInt instead of providing an Integer.tryParseInt method that returns a boolean false on parse failure.
  • Exogenous – These exceptions are usually exceptional , not your fault, you cannot (reasonably) prevent them, but you must handle them . For example, FileNotFoundException .

An API user:

  • must not handle fatal or boneheaded exceptions.
  • should handle vexing exceptions, but they should not occur in an ideal API.
  • must handle exogenous exceptions.

Checked exceptions

The fact that the API user must handle a particular exception is part of the method’s contract between the caller and the callee. The contract specifies, among other things: the number and types of arguments the callee expects, the type of return value the caller can expect, and the exceptions the caller is expected to handle .

Since vexing exceptions should not exist in an API, only these exogenous exceptions must be checked exceptions to be part of the method’s contract. Relatively few exceptions are exogenous , so any API should have relatively few checked exceptions.

A checked exception is an exception that must be handled . Handling an exception can be as simple as swallowing it. ¡Ahí! The exception is handled. Período. If the developer wants to handle it that way, fine. But he can’t ignore the exception, and has been warned.

API problems

But any API that has checked vexing and fatal exceptions (eg the JCL) will put unnecessary strain on the API users. Such exceptions have to be handled, but either the exception is so common that it should not have been an exception in the first place, or nothing can be done when handling it. And this causes Java developers to hate checked exceptions.

Also, many APIs don’t have a proper exception class hierarchy, causing all kinds of non-exogenous exception causes to be represented by a single checked exception class (eg IOException ). And this also causes Java developers to hate checked exceptions.

Conclusión

Exogenous exceptions are those that are not your fault, could not have been prevented, and which should be handled. These form a small subset of all the exceptions that can get thrown. APIs should only have checked exogenous exceptions , and all other exceptions unchecked. This will make better APIs, put less strain on the API user, and therefore reduce the need to catch all, swallow or rethrow unchecked exceptions.

So don’t hate Java and its checked exceptions. Instead, hate the APIs that overuse checked exceptions.

Ok… Checked exceptions are not ideal and have some caveat but they do serve a purpose. When creating an API there are specific cases of failures that are contractual of this API. When in the context of a strongly statically typed language such as Java if one does not use checked exceptions then one must rely on ad-hoc documentation and convention to convey the possibility of error. Doing so removes all benefit that the compiler can bring in handling error and you are left completely to the good will of programmers.

So, one removes Checked exception, such as was done in C#, how then can one programmatically and structurally convey the possibility of error ? How to inform the client code that such and such errors can occur and must be dealt with ?

I hear all sorts of horrors when dealing with checked exceptions, they are misused this is certain but so are unchecked exceptions. I say wait a few years when APIs are stacked many layers deep and you will be begging for the return of some kind of structured mean to convey failures.

Take the case when the exception was thrown somewhere at the bottom of the API layers and just bubbled up because nobody knew it was even possible for this error to occur, this even though it was a type of error that was very plausible when the calling code threw it (FileNotFoundException for example as opposed to VogonsTrashingEarthExcept… in which case it would not matter if we handle it or not since there is nothing left to handle it with).

Many have argued that not being able to load the file was almost always the end of the world for the process and it must die a horrible and painful death. So yeah.. sure … ok.. you build an API for something and it loads file at some point… I as the user of said API can only respond… “Who the hell are you to decide when my program should crash !” Sure Given the choice where exceptions are gobbled up and leave no trace or the EletroFlabbingChunkFluxManifoldChuggingException with a stack trace deeper than the Marianna trench I would take the latter without a cinch of hesitation, but does this mean that it is the desirable way to deal with exception ? Can we not be somewhere in the middle, where the exception would be recast and wrapped each time it traversed into a new level of abstraction so that it actually means something ?

Lastly, most of the argument I see is “I don’t want to deal with exceptions, many people do not want to deal with exceptions. Checked exceptions force me to deal with them thus I hate checked exception” To eliminate such mechanism altogether and relegate it to the chasm of goto hell is just silly and lacks jugement and vision.

If we eliminate checked exception we could also eliminate the return type for functions and always return a “anytype” variable… That would make life so much simpler now would it not ?

Indeed, checked exceptions on the one hand increase robustness and correctness of your program (you’re forced to make correct declarations of your interfaces -the exceptions a method throws are basically a special return type). On the other hand you face the problem that, since exceptions “bubble up”, very often you need to change a whole lot of methods (all the callers, and the callers of the callers, and so on) when you change the exceptions one method throws.

Checked exceptions in Java do not solve the latter problem; C# and VB.NET throw out the baby with the bathwater.

A nice approach that takes the middle road is described in this OOPSLA 2005 paper (or the related technical report .)

In short, it allows you to say: method g(x) throws like f(x) , which means that g throws all the exceptions f throws. Voila, checked exceptions without the cascading changes problem.

Although it is an academic paper, I’d encourage you to read (parts of) it, as it does a good job of explaining what the benefits and downsides of checked exceptions are.

Anders speaks about the pitfalls of checked exceptions and why he left them out of C# in episode 97 of Software Engineering radio.

To attempt to address just the unanswered question:

If you throw RuntimeException subclasses instead of Exception subclasses then how do you know what you are supposed to catch?

The question contains specious reasoning IMHO. Just because the API tells you what it throws doesn’t mean you deal with it in the same way in all cases. To put it another way, the exceptions you need to catch vary depending on the context in which you use the component throwing the exception.

Por ejemplo:

If I’m writing a connection tester for a database, or something to check the validity of a user entered XPath, then I’d probably want to catch and report on all checked and unchecked exceptions that are thrown by the operation.

If, however, I am writing a processing engine, I will likely treat an XPathException (checked) in the same way as an NPE: I would let it run up to the top of the worker thread, skip the rest of that batch, log the issue (or send it to a support department for diagnosis) and leave feedback for the user to contact support.

El problema

The worst problem I see with exception handling mechanism is that it introduces code duplication in a big scale ! Let’s be honest: In most of projects in 95% of the time all that developers really need to do with exception is to communicate it somehow to the user (and, in some cases, to the development team as well, eg by sending an e-mail with the stack trace). So usually the same line/block of code is used in every place the exception is handled.

Let’s assume that we do simple logging in each catch block for some type of checked exception:

 try{ methodDeclaringCheckedException(); }catch(CheckedException e){ logger.error(e); } 

If it’s a common exception there may be even several hundreds of such try-catch blocks in a larger codebase. Now let’s assume that we need to introduce popup dialog based exception handling instead of console logging or start to additionally send an e-mail to the development team.

Wait a moment… are we really going to edit all of that several hundreds of locations in the code?! You get my point :-).

La solución

What we did to adress that issue was introducing the concept of exception handlers (to which I’ll further refer as EH’s) to centralize exception handling. To every class that needs to hande exceptions an instance of exception handler is injected by our Dependency Injection framework. So the typical pattern of exception handling now looks like this:

 try{ methodDeclaringCheckedException(); }catch(CheckedException e){ exceptionHandler.handleError(e); } 

Now to customize our exception handling we only need to change the code in a single place (EH code).

Of course for more complex cases we can implement several subclasses of EHs and leverage features that our DI framework provides us. By changing our DI framework configuration we can easily switch EH implementation globally or provide specific implementations of EH to classes with special exception handling needs (for example using Guice @Named annotation).

That way we can differentiate exception handling behaviour in development and release version of application (eg. development – logging the error and halting the application, prod – logging the error with more details and letting the application continue its execution) with no effort.

Last one thing

Last but not least, it may seem that the same kind of centralisation can be obtained by just passing our exceptions “up” until they arrive to some top level exception handling class. But that leads to cluttering of code and signatures of our methods and introduces maintenance problems mentioned by others in this thread.

My writeup on c2.com is still mostly unchanged from its original form: CheckedExceptionsAreIncompatibleWithVisitorPattern

En resumen:

Visitor Pattern and its relatives are a class of interfaces where the indirect caller and interface implementation both know about an exception but the interface and direct caller form a library that cannot know.

The fundamental assumption of CheckedExceptions is all declared exceptions can be thrown from any point that calls a method with that declaration. The VisitorPattern reveals this assumption to be faulty.

The final result of checked exceptions in cases like these is a lot of otherwise useless code that essentially removes the compiler’s checked exception constraint at runtime.

As for the underlying problem:

My general idea is the top-level handler needs to interpret the exception and display an appropriate error message. I almost always see either IO exceptions, communication exceptions (for some reason APIs distinguish), or task-fatal errors (program bugs or severe problem on backing server), so this should not be too hard if we allow a stack trace for a severe server problem.

This article is the best piece of text on exception handling in Java I have ever read.

It favours unchecked over checked exceptions but this choice is explained very thouroughly and based on strong arguments.

I don’t want to cite too much of the article content here (it’s best to read it as a whole) but it covers most of arguments of unchecked exceptions advocates from this thread. Especially this argument (which seems to be quite popular) is covered:

Take the case when the exception was thrown somewhere at the bottom of the API layers and just bubbled up because nobody knew it was even possible for this error to occur, this even though it was a type of error that was very plausible when the calling code threw it (FileNotFoundException for example as opposed to VogonsTrashingEarthExcept… in which case it would not matter if we handle it or not since there is nothing left to handle it with).

The author “responses”:

It is absolutely incorrect to assume that all runtime exceptions should not be caught and allowed to propagate to the very “top” of the application. (…) For every exceptional condition that is required to be handled distinctly – by the system/business requirements – programmers must decide where to catch it and what to do once the condition is caught. This must be done strictly according to the actual needs of the application, not based on a compiler alert. All other errors must be allowed to freely propagate to the topmost handler where they would be logged and a graceful (perhaps, termination) action will be taken.

And the main thought or article is:

When it comes to error handling in software, the only safe and correct assumption that may ever be made is that a failure may occur in absolutely every subroutine or module that exists!

So if ” nobody knew it was even possible for this error to occur ” there is something wrong with that project. Such exception should be handled by at least the most generic exception handler (eg the one that handles all Exceptions not handled by more specific handlers) as author suggests.

So sad not many poeple seems to discover this great article :-(. I recommend wholeheartly everyone who hesitates which approach is better to take some time and read it.

Checked exceptions were, in their original form, an attempt to handle contingencies rather than failures. The laudable goal was to highlight specific predictable points (unable to connect, file not found, etc) & ensure developers handled these.

What was never included in the original concept, was to force a vast range of systemic & unrecoverable failures to be declared. These failures were never correct to be declared as checked exceptions.

Failures are generally possible in code, and EJB, web & Swing/AWT containers already cater for this by providing an outermost “failed request” exception-handler. The most basic correct strategy is to rollback the transaction & return an error.

One crucial point, is that runtime & checked exceptions are functionally equivalent. There is no handling or recovery which checked exceptions can do, that runtime exceptions can’t.

The biggest argument against “checked” exceptions is that most exceptions can’t be fixed. The simple fact is, we don’t own the code/ subsystem that broke. We can’t see the implementation, we’re not responsible for it, and can’t fix it.

If our application is not a DB.. we shouldn’t try and fix the DB. That would violate the principle of encapsulation .

Particularly problematic have been the areas of JDBC (SQLException) and RMI for EJB (RemoteException). Rather than identifying fixable contingencies as per the original “checked exception” concept, these forced pervasive systemic reliability issues, not actually fixable, to be widely declared.

The other severe flaw in the Java design, was that exception-handling should correctly placed at the highest possible “business” or “request” level. The principle here is “throw early, catch late”. Checked exceptions do little but get in the way of this.

We have an obvious issue in Java of requiring thousands of do-nothing try-catch blocks, with a significant proportion (40%+) being miscoded. Almost none of these implement any genuine handling or reliability, but impose major coding overhead.

Lastly, “checked exceptions” are pretty much incompatible with FP functional programming.

Their insistence on “handle immediately” is at odds with both “catch late” exception-handling best practice, and any FP structure which abstracts loops/ or flow of control.

Many people talk about “handling” checked exceptions, but are talking through their hats. Continuing after a failure with null, incomplete or incorrect data to pretend success is not handling anything. It’s engineering/ reliability malpractice of the lowest form.

Failing cleanly, is the most basic correct strategy for handling an exception. Rolling back the transaction, logging the error & reporting a “failure” response to the user are sound practice — and most importantly, prevent incorrect business data being committed to the database.

Other strategies for exception-handling are “retry”, “reconnect” or “skip”, at the business, subsystem, or request level. All of these are general reliability strategies, and work well/ better with runtime exceptions.

Lastly, it is far preferable to fail, than to run with incorrect data. Continuing will either cause secondary errors, distant from the original cause & harder to debug; or will eventually result in erroneous data being committed. People get fired for that.

Ver:
http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

A problem with checked exceptions is that exceptions are often attached to methods of an interface if even one implementation of that interface uses it.

Another problem with checked exceptions is that they tend to be misused. The perfect example of this is in java.sql.Connection ‘s close() method. It can throw a SQLException , even though you’ve already explicitly stated that you’re done with the Connection. What information could close() possibly convey that you’d care about?

Usually, when I close() a connection * , it looks something like this:

 try { conn.close(); } catch (SQLException ex) { // Do nothing } 

Also, don’t get me started on the various parse methods and NumberFormatException… .NET’s TryParse, which doesn’t throw exceptions, is so much easier to use it’s painful to have to go back to Java (we use both Java and C# where I work).

* As an additional comment, a PooledConnection’s Connection.close() doesn’t even close a connection, but you still have to catch the SQLException due to it being a checked exception.

The programmer needs to know all of the exceptions that a method may throw, in order to use it correctly. So, beating him over the head with just some of the exceptions does not necessarily help a careless programmer avoid errors.

The slim benefit is outweighed by the burdensome cost (especially in larger, less flexible code bases where constantly modifying the interface signatures is not practical).

Static analysis can be nice, but truly reliable static analysis often inflexibly demands strict work from the programmer. There is a cost-benefit calculation, and the bar needs to be set high for a check that leads to a compile time error. It would be more helpful if the IDE took on the role of communicating which exceptions a method may throw (including which are unavoidable). Although perhaps it would not be as reliable without forced exception declarations, most exceptions would still be declared in documentation, and the reliability of an IDE warning is not so crucial.

This isn’t an argument against the pure concept of checked exceptions, but the class hierarchy Java uses for them is a freak show. We always call the things simply “exceptions” – which is correct, because the language specification calls them that too – but how is an exception named and represented in the type system?

By the class Exception one imagines? Well no, because Exception s are exceptions, and likewise exceptions are Exception s, except for those exceptions that are not Exception s, because other exceptions are actually Error s, which are the other kind of exception, a kind of extra-exceptional exception that should never happen except when it does, and which you should never catch except sometimes you have to. Except that’s not all because you can also define other exceptions that are neither Exception s nor Error s but merely Throwable exceptions.

Which of these are the “checked” exceptions? Throwable s are checked exceptions, except if they’re also Error s, which are unchecked exceptions, and then there’s the Exception s, which are also Throwable s and are the main type of checked exception, except there’s one exception to that too, which is that if they are also RuntimeException s, because that’s the other kind of unchecked exception.

What are RuntimeException s for? Well just like the name implies, they’re exceptions, like all Exception s, and they happen at run-time, like all exceptions actually, except that RuntimeException s are exceptional compared to other run-time Exception s because they aren’t supposed to happen except when you make some silly error, although RuntimeException s are never Error s, so they’re for things that are exceptionally erroneous but which aren’t actually Error s. Except for RuntimeErrorException , which really is a RuntimeException for Error s. But aren’t all exceptions supposed to represent erroneous circumstances anyway? Sí, todos ellos. Except for ThreadDeath , an exceptionally unexceptional exception, as the documentation explains that it’s a “normal occurrence” and that that’s why they made it a type of Error .

Anyway, since we’re dividing all exceptions down the middle into Error s (which are for exceptional execution exceptions, so unchecked) and Exception s (which are for less exceptional execution errors, so checked except when they’re not), we now need two different kinds of each of several exceptions. So we need IllegalAccessError and IllegalAccessException , and InstantiationError and InstantiationException , and NoSuchFieldError and NoSuchFieldException , and NoSuchMethodError and NoSuchMethodException , and ZipError and ZipException .

Except that even when an exception is checked, there are always (fairly easy) ways to cheat the compiler and throw it without it being checked. If you do you that you may get an UndeclaredThrowableException , except in other cases, where it could throw up as an UnexpectedException , or an UnknownException (which is unrelated to UnknownError , which is only for “serious exceptions”), or an ExecutionException , or an InvocationTargetException , or an ExceptionInInitializerError .

Oh, and we mustn’t forget Java 8’s snazzy new UncheckedIOException , which is a RuntimeException exception designed to let you throw the exception checking concept out the window by wrapping checked IOException exceptions caused by I/O errors (which don’t cause IOError exceptions, although that exists too) that are exceptionally difficult to handle and so you need them to not be checked.

Thanks Java!

As folks have already stated, checked exceptions don’t exist in Java bytecode. They are simply a compiler mechanism, not unlike other syntax checks. I see checked exceptions a lot like I see the compiler complaining about a redundant conditional: if(true) { a; } b; . That’s helpful but I might have done this on purpose, so let me ignore your warnings.

The fact of the matter is, you aren’t going to be able to force every programmer to “do the right thing” if you enforce checked exceptions and everyone else is now collateral damage who just hates you for the rule you made.

Fix the bad programs out there! Don’t try to fix the language to not allow them! For most folks, “doing something about an exception” is really just telling the user about it. I can tell the user about an unchecked exception just as well, so keep your checked exception classes out of my API.

I think that this is an excellent question and not at all argumentative. I think that 3rd party libraries should (in general) throw unchecked exceptions. This means that you can isolate your dependencies on the library (ie you don’t have to either re-throw their exceptions or throw Exception – usually bad practice). Spring’s DAO layer is an excellent example of this.

On the other hand, exceptions from the core Java API should in general be checked if they could ever be handled. Take FileNotFoundException or (my favourite) InterruptedException . These conditions should almost always be handled specifically (ie your reaction to an InterruptedException is not the same as your reaction to an IllegalArgumentException ). The fact that your exceptions are checked forces developers to think about whether a condition is handle-able or not. (That said, I’ve rarely seen InterruptedException handled properly!)

One more thing – a RuntimeException is not always “where a developer got something wrong”. An illegal argument exception is thrown when you try and create an enum using valueOf and there’s no enum of that name. This is not necessarily a mistake by the developer!

Here ‘s one argument against checked exceptions (from joelonsoftware.com):

The reasoning is that I consider exceptions to be no better than “goto’s”, considered harmful since the 1960s, in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto’s:

  • They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn’t reveal potential bugs.
  • They create too many possible exit points for a function. To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don’t catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn’t think about.

The good proves that Checked Exception are not needed are:

  1. A lot of framework that does some work for Java. Like Spring that wraps JDBC exception to unchecked exceptions, throwing messages to the log
  2. Lot of languages that came after java, even on top on java platform – they do not use them
  3. Checked exceptions, it is kind prediction about how the client would use the code that throws an exception. But a developer who writes this code would never know about the system and business that client of code is working in. As an example Interfcace methods that force to throw checked exception. There are 100 implementation over the system, 50 or even 90 of implementations do not throw this exception, but the client still must to catch this exception if he user reference to that interface. Those 50 or 90 implementations tend to handle those exceptions inside themself, putting exception to the log (and this is good behavior for them). What we should do with that? I would better have some background logic that would do all that job – sending message to the log. And If I, as a client of code, would feel I need handle the exception – I will do it. I may forget about it, right – but if I use TDD, all my steps are covered and I know what I want.
  4. Another example when I’m working with I/O in java, it forces me to check all exception, if file does not exists? what I should do with that? If it does not exists, the system would not go to the next step. The client of this method, would not get expected content from that file – he can handle Runtime Exception, otherwise I should first check Checked Exception, put a message to log, then throw exception up out form the method. No…no – I would better do it automatically with RuntimeEception, that does it / lits up automatically. There is no any sense to handle it manually – I would be happy I saw an error message in the log (AOP can help with that.. something that fixes java). If, eventually, I deice that system should shows pop-up message to the end user – I will show it, not a problem.

I was happy if java would provide me with a choice what to use, when working with core libs, like I/O. Like provides two copies of same classes – one wrapped with RuntimeEception. Then we can compare what people would use . For now, though, many people would better go for some framework on top on java, or different language. Like Scala, JRuby whatever. Many just believe that SUN was right.

We’ve seen some references to C#’s chief architect.

Here’s an alternate point of view from a Java guy about when to use checked exceptions. He acknowledges many of the negatives others have mentioned: Effective Exceptions

Despite having read the whole page, I still can’t find a single reasonable argument against checked exceptions. Most people are instead talking about poor API design, either at some Java classes or at their own classes.

The only scenario where this feature may be annoying is prototiping. This could be solved by adding some mechanism to the language (for instance, some @supresscheckedexceptions annotation). But for regular programming, I think checked exceptions are a good thing.

One important thing nobody mentioned is how it interferes with interfaces and lambda expressions.

Let’s say you define a MyAppException extends Exception . It is the top level exception inherited by all exceptions thrown by your application. Every method declares throws MyAppException which is a bit of nuissance, but manageable. An exception handler logs exception and notifies the user somehow.

All looks OK until you want to implement some interface that is not yours. Obviously it doesn’t declare intention to throw MyApException , so compiler doesn’t allow you to throw the exception from there.

However, if your exception extends RuntimeException , there will be no problem with interfaces. You can voluntarily mention the exception in JavaDoc if you wish. But other than that it just silently bubbless through anything, to be caught in your exception handling layer.

    Intereting Posts