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

Actualmente estoy escribiendo código nuevo y prestando mucha atención a cómo manejo las excepciones. Estoy tratando de ver el punto de vista de la multitud de "no nos gustan las excepciones comprobadas" y todavía no puedo verlo.

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

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

  • Errores para cosas que nunca deberían atraparse (VM tiene alergia al maní y alguien le tiró un frasco de maní)
  • RuntimeExceptiones para cosas que el programador hizo mal (el programador se salió del final de una matriz)
  • Exception(excepto RuntimeException) es para cosas que están fuera del control del programador (el disco se llena mientras se escribe en el sistema de archivos, se alcanzó el límite de manejo de archivos para el proceso y no puede abrir más archivos)
  • Throwablees 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 va a hacer es salir del programa.

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

Para el argumento "todo lo que voy a hacer es salir", digo que incluso si está saliendo, debe mostrar un mensaje de error razonable. Si solo está jugando con el manejo de errores, entonces sus usuarios no estarán muy contentos cuando el programa salga sin una indicación clara de por qué.

Para la multitud de "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 un IOException, IOExceptiondebería transformarse en una excepción que sea más adecuada para lo que está sucediendo.

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

La pregunta que la gente nunca responde es esta:

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

Si la respuesta es atrapada Exception, entonces también está lidiando con errores del programador de la misma manera que con las excepciones del sistema. Eso me parece equivocado.

Si detecta, Throwableentonces 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 captura solo las excepciones que sabe que se lanzan, ¿cómo sabe cuáles se lanzan? ¿Qué sucede cuando el programador X lanza una nueva excepción y se olvidó de capturarla? Eso me parece muy peligroso.

Diría que un programa que muestra un seguimiento de la pila está mal. ¿Las personas a las que no les gustan las excepciones marcadas no se sienten así?

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

No estoy buscando consejos sobre cuándo usar cualquiera de los modelos, lo que estoy buscando es por qué las personas se extienden desde RuntimeExceptionporque no les gusta extender desde Exceptiony/o por qué detectan una excepción y luego vuelven a lanzar en RuntimeExceptionlugar de agregar tiros a su método . Quiero entender la motivación por la que no me gustan las excepciones marcadas.

respuesta

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

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

Aunque soy fanático de Hejlsberg y su trabajo, este argumento siempre me ha parecido falso. Básicamente se reduce a:

"Checked exceptions are bad because programmers just abuse them by always catching them and dismissing them which leads to problems being hidden and ignored that would otherwise be presented to the user".

Por "presentado de otro modo al usuario" quiero decir que si usa una excepción de tiempo de ejecución, el programador perezoso simplemente la ignorará (en lugar de atraparla con un bloque de captura vacío) y el usuario la verá.

El resumen del argumento es que "los programadores no los usarán correctamente y no usarlos correctamente es peor que no tenerlos" .

Hay algo de verdad en este argumento y, de hecho, sospecho que la motivación de Gosling para no poner anulaciones de operadores en Java proviene de un argumento similar: confunden al programador porque a menudo se abusa de ellas.

Pero al final, lo encuentro 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 verificadas es algo malo y tiende a conducir a un manejo descuidado por parte de los usuarios, el uso adecuado de ellas permite que el programador de la API brinde un gran beneficio al programador del cliente de la API.

Ahora, el programador de la API debe tener cuidado de no arrojar excepciones comprobadas por todas partes, o simplemente molestarán al programador del cliente. El programador de clientes muy perezoso recurrirá a la captura (Exception) {}como advierte Hejlsberg y se perderán todos los beneficios y se producirá 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 apertura de archivos. Todos los lenguajes de programación en la historia de los lenguajes (al menos en los sistemas de archivos) tienen una API en algún lugar que le permite abrir un archivo. Y todos los programadores de clientes que usan esta API saben que tienen que lidiar con el caso de que el archivo que intentan abrir no exista. Permítanme reformular eso: todos los programadores de clientes que usan esta API deben saber que tienen que lidiar con este caso. Y ahí está el problema: ¿puede el programador de la API ayudarlos a saber que deben manejarlo comentando solo o pueden insistir en que el cliente lo haga?

En C el modismo dice algo como

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

donde fopenindica falla al devolver 0 y C (tontamente) te permite tratar 0 como un booleano y... Básicamente, aprendes este idioma y estás bien. Pero, ¿qué pasa si eres un novato y no aprendiste el idioma? Entonces, por supuesto, empiezas con

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

y aprende de la manera difícil.

Tenga en cuenta que aquí solo estamos hablando de 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) para que la use 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 pases una cadena (o un char* en el caso de C). Si le das algo más, obtienes un error de tiempo de compilación. No siguió el protocolo: no está utilizando la API correctamente.

En algunos idiomas (oscuros), el tipo de retorno también forma parte del protocolo. Si intenta llamar al equivalente de fopen()en algunos idiomas sin asignarlo a una variable, también obtendrá un error de tiempo de compilación (solo puede hacerlo con funciones nulas).

El punto que estoy tratando de hacer es que: en un lenguaje escrito estáticamente, el programador de la API alienta al cliente a usar la API correctamente al evitar que su código de cliente se compile si comete errores obvios.

(En un lenguaje de tipo dinámico, como Ruby, puede pasar cualquier cosa, digamos un flotante, como nombre de archivo, y se compilará. ¿Por qué molestar al usuario con excepciones comprobadas si ni siquiera va a controlar los argumentos del método? Los argumentos hechos aquí se aplican solo a lenguajes de tipado estático).

Entonces, ¿qué pasa con las excepciones comprobadas?

Bueno, aquí está 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 FileNotFoundExceptiones una excepción comprobada .

El programador de API le está diciendo esto: "Puede 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 dice la pregunta como "Cosas que están fuera del control del programador". Lo primero que pensé fue que él/ella quiere decir cosas que están fuera del control de los programadores de la API . Pero, de hecho, las excepciones verificadas cuando se usan correctamente deberían ser para cosas que están fuera del control tanto del programador del cliente como del programador de la API. Creo que esta es la clave para no abusar de las excepciones marcadas.

Creo que el archivo abierto ilustra muy bien el punto. El programador de API sabe que puede darles un nombre de archivo que resulta ser inexistente en el momento en que se llama a la API, y que no podrán devolverle lo que quería, sino que tendrán que generar una excepción. También saben que esto sucederá con bastante frecuencia y que el programador del cliente puede esperar que el nombre del archivo sea correcto en el momento en que escribió la llamada, pero también puede ser incorrecto en el tiempo de ejecución por razones que escapan 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 será mejor que se ocupe de él.

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

public RowData getRowData(int row) 

Ahora, como programador de API, sé que habrá casos en los que algún cliente pase un valor negativo para la fila o un valor de fila fuera de la tabla. Por lo tanto, podría tener la tentación de lanzar una excepción verificada y obligar al cliente a lidiar con ella:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(Realmente no lo llamaría "Comprobado", por supuesto).

Este es un mal uso de las excepciones comprobadas. El código del cliente estará lleno de llamadas para obtener datos de fila, cada una de las cuales tendrá que usar un intento/captura, ¿y para qué? ¿Van a informar al usuario que se buscó la fila equivocada? Probablemente no, porque sea cual sea la interfaz de usuario que rodea a mi vista de tabla, no debería permitir que el usuario entre en un estado en el que se solicita una fila ilegal. Entonces es un error por parte del programador del cliente.

El programador de API aún puede predecir que el cliente codificará dichos errores y debería manejarlos con una excepción de tiempo de ejecución como un IllegalArgumentException.

Con una excepción marcada en getRowData, este es claramente un caso que llevará al programador perezoso de Hejlsberg simplemente a agregar capturas vacías. Cuando eso sucede, los valores de fila ilegales no serán obvios ni siquiera para el evaluador o el desarrollador del cliente que realiza la depuración, sino que darán lugar a errores colaterales cuya fuente es difícil de identificar. Los cohetes Arianne explotarán después del lanzamiento.

Bien, aquí está el problema: digo que la excepción verificada FileNotFoundExceptionno es solo algo bueno, sino una herramienta esencial en la caja de herramientas de los programadores de API para definir la API de la manera más útil para el programador del cliente. Pero CheckedInvalidRowNumberExceptiones un gran inconveniente que conduce a una mala programación y debe evitarse. Pero cómo notar la diferencia.

Supongo que no es una ciencia exacta y supongo que eso subyace y tal vez justifique hasta cierto punto el argumento de Hejlsberg. Pero no estoy contento de tirar al bebé con el agua del baño aquí, así que permítanme extraer algunas reglas aquí para distinguir las excepciones comprobadas buenas de las malas:

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

    Las excepciones marcadas solo deben usarse cuando el caso de error está fuera del control tanto de la API como del programador del cliente. Esto tiene que ver con cuán abierto o cerrado es el sistema. En una interfaz de usuario restringida donde el programador del cliente tiene control, digamos, 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 programación del cliente si intenta obtener 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 solicita se haya eliminado sin su conocimiento, por lo que debe esperarse que se ocupe de él. .

  2. Ubicuidad:

    Las excepciones marcadas no deben usarse en una llamada a la 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. Por lo tanto, un código de cliente no tiende a intentar abrir mucho el mismo archivo, pero mi vista de tabla se RowDatadescontrola por diferentes métodos. En particular, voy a escribir mucho código como

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

y será doloroso tener que envolver en intentar/atrapar cada vez.

  1. Informando al Usuario:

    Las excepciones marcadas deben usarse en los casos en los que pueda imaginar que se presenta un mensaje de error útil al usuario final. Este es el "¿y qué harás cuando suceda?" pregunta que planteé arriba. También se relaciona con el elemento 1. Dado que puede predecir que algo fuera de su sistema de API de cliente podría causar que el archivo no esté allí, puede informarle razonablemente al usuario al respecto:

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

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

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

    En resumen, si no cree que el programador de su cliente pueda explicar su excepción de una manera que ayude al usuario, entonces probablemente no debería usar una excepción verificada.

Así que esas son mis reglas. Algo artificial, y sin duda habrá excepciones (por favor, ayúdame a refinarlas si quieres). Pero mi argumento principal es que hay casos FileNotFoundExceptionen los que la excepción verificada es una parte tan importante y útil del contrato API como los tipos de parámetros. Por lo tanto, no debemos prescindir de él solo porque se usa mal.

Lo siento, no fue mi intención hacer esto tan largo y tonto. Permítanme terminar con dos sugerencias:

R: Programadores de API: utilicen las excepciones comprobadas con moderación para preservar su utilidad. En caso de duda, utilice una excepción sin marcar.

B: programadores de clientes: acostúmbrese a crear una excepción envuelta (búsquela en Google) al principio de su desarrollo. JDK 1.4 y versiones posteriores proporcionan un constructor RuntimeExceptionpara esto, pero también puede crear uno propio fácilmente. Aquí está el constructor:

public RuntimeException(Throwable cause)

Luego, acostúmbrese a que cada vez que tenga que manejar una excepción verificada y se sienta perezoso (o crea que el programador de API fue demasiado entusiasta al usar la excepción verificada en primer lugar), no se limite a tragarse la excepción, envuélvala y volver a tirarlo.

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

Ponga esto en una de las pequeñas plantillas de código de su IDE y utilícelo cuando se sienta perezoso. De esta manera, si realmente necesita manejar la excepción verificada, se verá obligado a regresar y tratarla después de ver el problema en tiempo de ejecución. Porque, créame (y créame a Anders Hejlsberg), nunca volverá a ese TODO en su

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

Lo que pasa con las excepciones verificadas es que no son realmente excepciones según la comprensión habitual del concepto. En su lugar, son valores de retorno alternativos de la API.

La idea general de las excepciones es que un error lanzado en algún lugar de la cadena de llamadas puede surgir 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 verificadas, por otro lado, requieren todos los niveles de código entre el lanzador y el receptor para declarar que conocen todas las formas de excepción que pueden atravesar. Esto es muy poco diferente en la práctica a si las excepciones verificadas fueran simplemente valores de retorno especiales que la persona que llama tuvo 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;
}

Dado que Java no puede generar valores de retorno alternativos o tuplas en línea simples como valores de retorno, las excepciones verificadas son una respuesta razonable.

El problema es que una gran cantidad de código, incluidas grandes franjas de la biblioteca estándar, hace un mal uso de las excepciones verificadas para condiciones excepcionales reales que es muy posible que desee alcanzar varios niveles. ¿Por qué IOException no es una RuntimeException? En todos los demás idiomas, puedo permitir que ocurra una excepción de E/S y, si no hago nada para manejarla, mi aplicación se detendrá y obtendré un seguimiento de pila práctico para mirar. Esto es lo mejor que puede pasar.

Tal vez dos métodos por encima del ejemplo en el que desea capturar todas las IOExceptions de todo el proceso de escritura en flujo, cancelar el proceso y saltar al código de informe de errores; en Java no puede hacer eso sin agregar 'throws IOException' en cada nivel de llamada, incluso los niveles que no hacen IO. Dichos métodos no deberían necesitar saber sobre el manejo de excepciones; teniendo que añadir excepciones a sus firmas:

  1. aumenta innecesariamente el acoplamiento;
  2. hace que las firmas de la 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 programador es derrotar al sistema haciendo algo horrible como 'lanza una excepción', 'atrapa (Exception e) {}', o envolviendo todo en una RuntimeException (lo que dificulta la depuración).

Y luego hay muchas excepciones de biblioteca simplemente ridículas como:

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

Cuando tienes que saturar tu código con basura ridícula como esta, no es de extrañar que las excepciones marcadas reciban un montón de odio, aunque en realidad esto es simplemente un diseño pobre de API.

Otro efecto negativo en particular es la inversión de control, donde el componente A proporciona una devolución de llamada al componente genérico B. El componente A quiere permitir que se produzca una excepción desde su devolución de llamada al lugar donde llamó al componente B, pero no puede. porque eso cambiaría la interfaz de devolución de llamada que está arreglada por B. A solo puede hacerlo envolviendo la excepción real en una RuntimeException, que es aún más repetitivo de manejo de excepciones para escribir.

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

En lugar de repetir todas las (muchas) razones contra las excepciones verificadas, elegiré solo una. He perdido la cuenta de la cantidad de veces que he escrito este bloque de código:

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

El 99% del tiempo no puedo hacer nada al respecto. Finalmente, los bloques hacen la limpieza necesaria (o al menos deberían hacerlo).

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é pasaría si en su lugar se tratara de una excepción no verificada? La aplicación simplemente habría muerto (lo cual es preferible a tragarse 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. Equivocado. Un usuario que pone "abc" en un campo numérico 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 las excepciones marcadas y tengo algo que agregar. ¡Por favor, perdóname por la duración!

Mi problema principal con las excepciones marcadas es que arruinan el polimorfismo. Es imposible hacer que funcionen bien con interfaces polimórficas.

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

Esta WidgetListclase de ejemplo lee algunos objetos de tipo de tamaño fijo Widget(no se muestra) de un archivo:

class WidgetList extends AbstractList<Widget> {
    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 interfaz familiar List, puede recuperar elementos ( list.get(123)) o iterar una lista ( for (Widget w : list) ...) sin necesidad de conocerla WidgetList. Se puede pasar esta lista a cualquier método estándar que use listas genéricas o envolverla en un archivo Collections.synchronizedList. El código que lo usa no necesita saber ni preocuparse si los "Widgets" se crean en el acto, provienen de una matriz o se leen de un archivo, una base de datos, o de toda la red, o de un futuro relé subespacial. Seguirá funcionando correctamente porque la Listinterfaz está correctamente implementada.

Excepto que no lo es. La clase anterior no se compila porque los métodos de acceso a archivos pueden arrojar una IOExceptionexcepción comprobada que debe "atrapar o especificar". No puede especificarlo como lanzado : el compilador no lo permitirá porque violaría el contrato de la Listinterfaz. Y no hay una forma útil que WidgetListpueda manejar la excepción (como expondré más adelante).

Aparentemente, lo único que se puede hacer es capturar y volver a lanzar las excepciones marcadas como una 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 UncheckedIOExceptionclase exactamente para este caso: para capturar y volver a lanzar IOExceptions a través de los límites del método polimórfico. ¡De alguna manera prueba mi punto!))

Entonces, las excepciones verificadas simplemente no funcionan en casos como este. No puedes tirarlos. Lo mismo ocurre con una inteligente Maprespaldada por una base de datos, o una implementación java.util.Randomconectada a una fuente de entropía cuántica a través de un puerto COM. Tan pronto como intenta hacer algo novedoso con la implementación de una interfaz polimórfica, el concepto de excepciones verificadas falla. Pero las excepciones marcadas son tan insidiosas que aún no lo dejarán en paz, porque todavía tiene que capturar y volver a lanzar cualquiera de los métodos de nivel inferior, desordenando el código y desordenando el seguimiento de la pila.

Me parece que la interfaz omnipresente Runnablea menudo está respaldada en esta esquina, si llama a algo que arroja excepciones comprobadas. No puede lanzar la excepción tal como está, por lo que todo lo que puede hacer es saturar el código capturando y volviendo a lanzar como un archivo RuntimeException.

En realidad, puede lanzar excepciones comprobadas no declaradas si recurre a hacks. La JVM, en tiempo de ejecución, no se preocupa por las reglas de excepción verificadas, por lo que solo debemos engañar al compilador. La forma más fácil de hacer esto es abusar de los genéricos. Este es mi método para ello (se muestra el nombre de la clase porque (antes de Java 8) se requiere en la sintaxis 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.
     * 
     * <p>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.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

¡Viva! ¡Usando esto, podemos lanzar una excepción marcada en cualquier profundidad de la pila sin declararla, sin envolverla en un RuntimeException, y sin saturar el seguimiento de la pila! Usando el ejemplo "WidgetList" nuevamente:

@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 verificadas es que el compilador se niega a permitirle capturar una excepción verificada si, en su opinión errónea, no podría haber sido lanzada. (Las excepciones no verificadas no tienen esta regla). Para capturar la excepción lanzada furtivamente, 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 verificada que estaba envuelta en un archivo RuntimeException.

Afortunadamente, la throw t;declaración es legal aquí, aunque tse verifique el tipo de, gracias a una regla agregada en Java 7 sobre volver a generar excepciones detectadas.


Cuando las excepciones verificadas se encuentran con el polimorfismo, el caso opuesto también es un problema: cuando un método se especifica como potencialmente arrojando una excepción verificada, pero una implementación anulada no lo hace. OutputStreamPor ejemplo, todos los writemétodos de la clase abstracta especifican throws IOException. ByteArrayOutputStreames una subclase que escribe en una matriz en memoria en lugar de una verdadera fuente de E/S. Sus métodos anulados writeno pueden causar IOExceptions, por lo que no tienen ninguna throwscláusula y puede llamarlos sin preocuparse por el requisito de captura o especificación.

Excepto que no siempre. Supongamos que Widgettiene un método para guardarlo en una secuencia:

public void writeTo(OutputStream out) throws IOException;

Declarar que este método acepta un plano OutputStreames lo correcto, por lo que puede usarse polimórficamente con todo tipo de salidas: archivos, bases de datos, la red, etc. Y arreglos en memoria. Sin embargo, con una matriz en memoria, existe un requisito falso para manejar una excepción que en realidad no puede ocurrir:

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 comprobadas se interponen. Si sus variables se declaran como un tipo base que tiene requisitos de excepción más abiertos, debe agregar controladores para esas excepciones incluso si sabe que no ocurrirán en su aplicación.

Pero espere, las excepciones marcadas son realmente tan molestas que ni siquiera le permitirán hacer lo contrario. Imagínese que actualmente detecta alguna IOExceptionlanzada por writellamadas en un OutputStream, pero desea cambiar el tipo declarado de la variable a un ByteArrayOutputStream, el compilador lo regañará por intentar detectar una excepción verificada que dice que no se puede lanzar.

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

Esa regla también es molesta durante la edición y la depuración. Por ejemplo, a veces comentaré una llamada de método temporalmente y, si podría haber arrojado una excepción verificada, el compilador ahora se quejará de la existencia de los bloques locales tryy . catchAsí que también tengo que comentarlos, y ahora, al editar el código interno, el IDE sangrará en el nivel incorrecto porque los {y }están comentados. ¡Gah! Es una pequeña queja, pero parece que lo único que hacen las excepciones comprobadas es causar problemas.


casi termino Mi frustración final con las excepciones verificadas es que en la mayoría de los sitios de llamadas , no hay nada útil que pueda hacer con ellos. Idealmente, cuando algo sale mal, tendríamos un controlador específico de la aplicación competente que pueda informar al usuario del problema y/o finalizar o volver a intentar la operación según corresponda. Solo un manejador en lo alto de la pila puede hacer esto porque es el único que conoce el objetivo general.

En su lugar, obtenemos la siguiente expresión idiomática, que es rampante como una forma de cerrar el compilador:

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

En una GUI o programa automatizado, el mensaje impreso no se verá. Peor aún, continúa con el resto del código después de la excepción. ¿La excepción no es realmente un error? Entonces no lo imprimas. De lo contrario, algo más explotará en un momento, momento en el cual el objeto de excepción original habrá desaparecido. Esta expresión no es mejor que la de BASIC On Error Resume Nexto PHP error_reporting(0);.

Llamar a algún tipo de clase de registrador no es mucho mejor:

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

Eso es tan perezoso como e.printStackTrace();y aún continúa con el código en un estado indeterminado. Además, la elección de un sistema de registro en particular u otro controlador es específica de la aplicación, por lo que perjudica la reutilización del código.

¡Pero espera! Hay una manera fácil y universal de encontrar el controlador específico de la aplicación. Está más arriba en la pila de llamadas (o está configurado como el controlador de excepciones no detectadas del subproceso ). Entonces, en la mayoría de los lugares, todo lo que necesita hacer es lanzar la excepción más arriba en la pila . Por ejemplo, throw e;. Las excepciones marcadas simplemente se interponen en el camino.

Estoy seguro de que las excepciones marcadas sonaron como una buena idea cuando se diseñó el lenguaje, pero en la práctica he descubierto que son una molestia y ningún beneficio.

Bueno, no se trata de mostrar un stacktrace o fallar silenciosamente. Se trata de poder comunicar errores entre capas.

El problema con las excepciones marcadas es que alientan a las personas a aceptar detalles importantes (es decir, la clase de excepción). Si elige no tragarse ese detalle, debe seguir agregando declaraciones de lanzamientos en toda su aplicación. Esto significa 1) que un nuevo tipo de excepción afectará muchas firmas de funciones, y 2) puede perder una instancia específica de la excepción que realmente -quiere- atrapar (digamos que abre un archivo secundario para una función que escribe datos en un El archivo secundario es opcional, por lo que puede ignorar sus errores, pero debido a la firma throws IOException, es fácil pasarlo por alto).

De hecho, estoy lidiando con esta situación ahora en una aplicación. Reempaquetamos casi las excepciones como AppSpecificException. Esto hizo que las firmas fueran realmente limpias y no tuvimos que preocuparnos por la explosión throwsde firmas.

Por supuesto, ahora necesitamos especializarnos en el manejo de errores en los niveles más altos, implementando la lógica de reintento y demás. Sin embargo, todo es AppSpecificException, por lo que no podemos decir "Si se lanza una IOException, vuelva a intentarlo" o "Si se lanza ClassNotFound, anule por completo". No tenemos una forma confiable de llegar a la excepción real porque las cosas se vuelven a empaquetar una y otra vez a medida que pasan entre nuestro código y el código de terceros.

Es por eso que soy un gran admirador del manejo de excepciones en python. Solo puedes atrapar las cosas que quieres y/o puedes manejar. Todo lo demás burbujea como si lo volvieras a tirar tú mismo (lo que has hecho de todos modos).

Descubrí, una y otra vez, y a lo largo del proyecto que mencioné, que el manejo de excepciones se divide en 3 categorías:

  1. Captura y maneja una excepción específica . Esto es para implementar la lógica de reintento, por ejemplo.
  2. Captura y vuelve a lanzar otras excepciones. Todo lo que sucede aquí suele ser el registro, y suele ser un mensaje trillado como "No se puede abrir $ nombre de archivo". Estos son errores sobre los que no puede hacer nada; solo un nivel superior sabe lo suficiente para manejarlo.
  3. Captura todo y muestra un mensaje de error. Por lo general, esto se encuentra en la raíz misma de un despachador, y todo lo que hace es asegurarse de que pueda comunicar el error a la persona que llama a través de un mecanismo sin excepciones (diálogo emergente, ordenando un objeto de error RPC, etc.).

SNR

En primer lugar, las excepciones comprobadas reducen la "relación señal-ruido" del código. Anders Hejlsberg también habla sobre la programación imperativa frente a la declarativa, que es un concepto similar. De todos modos, considere los siguientes fragmentos de código:

Actualice la interfaz de usuario desde un subproceso que no sea de interfaz de usuario en 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);
}

Actualice la interfaz de usuario desde un subproceso que no sea de interfaz de usuario en 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();  
   }  
}  

Lo cual me parece mucho más claro. Cuando comienza a hacer más y más trabajo de interfaz de usuario en Swing, las excepciones comprobadas comienzan a volverse realmente molestas e inútiles.

fuga de la cárcel

Para implementar incluso las implementaciones más básicas, como la interfaz de lista de Java, las excepciones comprobadas como una herramienta para el diseño por contrato caen. Considere una lista respaldada por una base de datos o un sistema de archivos o cualquier otra implementación que arroje una excepción comprobada. La única implementación posible es capturar la excepción verificada y volver a generarla como una excepción no verificada:

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

Y ahora tienes que preguntar ¿cuál es el punto de todo ese código? Las excepciones marcadas solo agregan ruido, la excepción se capturó pero no se manejó y el diseño por contrato (en términos de excepciones verificadas) se descompuso.

Conclusión

  • Capturar excepciones es diferente a manejarlas.
  • Las excepciones marcadas agregan ruido al código.
  • El manejo de excepciones funciona bien en C# sin ellos.

Escribí en un blog sobre esto anteriormente .

Artima publicó una entrevista con uno de los arquitectos de .NET, Anders Hejlsberg, que cubre de manera aguda los argumentos en contra de las excepciones comprobadas. Una pequeña muestra:

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.

Inicialmente estuve de acuerdo contigo, ya que siempre he estado a favor de las excepciones verificadas, y comencé a pensar por qué no me gusta no tener excepciones verificadas en .Net. Pero luego me di cuenta de que, de hecho, no me gustan las excepciones marcadas.

Para responder a su pregunta, sí, me gusta que mis programas muestren rastros de pila, preferiblemente los realmente feos. Quiero que la aplicación explote en un montón horrible de los mensajes de error más feos que puedas desear ver.

Y la razón es porque, si hace eso, tengo que arreglarlo, y tengo que arreglarlo de inmediato. Quiero saber inmediatamente que hay un problema.

¿Cuántas veces realmente maneja excepciones? No estoy hablando de capturar excepciones, ¿estoy hablando de manejarlas? Es demasiado fácil escribir lo siguiente:

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

Y sé que puedes decir que es una mala práctica, y que 'la respuesta' es hacer algo con la excepción (déjame adivinar, ¿registrarla?), pero en el Mundo Real (tm), la mayoría de los programadores simplemente no hacen eso.

Así que sí, no quiero atrapar excepciones si no tengo que hacerlo, y quiero que mi programa explote espectacularmente cuando me equivoque. Fallar en silencio es el peor resultado posible.

El artículo Excepciones efectivas de Java explica muy bien cuándo usar excepciones no verificadas y cuándo usar excepciones verificadas. Aquí hay algunas citas de ese artículo para resaltar los puntos principales:

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 no permite tablas, por lo que es posible que desee leer lo siguiente de la página original ...)

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

He estado trabajando con varios desarrolladores en los últimos tres años en aplicaciones relativamente complejas. Tenemos una base de código que usa Excepciones comprobadas con bastante frecuencia con un manejo de errores adecuado, y algunas otras que no.

Hasta ahora, me ha resultado más fácil trabajar con el código base con Excepciones comprobadas. Cuando uso la API de otra persona, es bueno poder ver exactamente qué tipo de condiciones de error puedo esperar cuando llamo al código y las manejo correctamente, ya sea iniciando sesión, mostrando o ignorando (Sí, hay casos válidos para ignorar excepciones, como una implementación de ClassLoader). Eso le da al código que estoy escribiendo una oportunidad de recuperar. Todas las excepciones de tiempo de ejecución que propago hasta que se almacenan en caché y se manejan con algún código genérico de manejo de errores. Cuando encuentro una excepción comprobada que realmente no quiero manejar en un nivel específico, o que considero un error de lógica de programación, la envuelvo en una RuntimeException y la dejo burbujear. Nunca, nunca trague una excepción sin una buena razón (y las buenas razones para hacer esto son bastante escasas)

Cuando trabajo con el código base que no tiene excepciones verificadas, me resulta un poco más difícil saber de antemano qué puedo esperar al llamar a la función, lo que puede romper algunas cosas terriblemente.

Todo esto es, por supuesto, una cuestión de preferencia y habilidad del desarrollador. Ambas formas de programación y manejo de errores pueden ser igualmente efectivas (o no efectivas), por lo que no diría que existe The One Way.

En general, me resulta más fácil trabajar con Excepciones comprobadas, especialmente en proyectos grandes con muchos desarrolladores.

En breve:

Las excepciones son una cuestión de diseño de API. -- Ni mas ni menos.

El argumento para las excepciones comprobadas:

Para comprender por qué las excepciones comprobadas pueden no ser buenas, demos la vuelta a la pregunta y preguntemos: ¿Cuándo o por qué son atractivas las excepciones comprobadas, es decir, por qué querría que el compilador imponga la declaración de excepciones?

La respuesta es obvia: a veces necesita capturar una excepción, y eso solo es posible si el código que se llama ofrece una clase de excepción específica para el error que le interesa.

Por lo tanto, el argumento para las excepciones verificadas es que el compilador obliga a los programadores a declarar qué excepciones se lanzan y, con suerte , el programador también documentará las clases de excepción específicas y los errores que las causan.

Sin embargo, en realidad, con demasiada frecuencia, un paquete com.acmesolo arroja una subclase en AcmeExceptionlugar de subclases específicas. Luego, las personas que llaman deben manejar, declarar o volver a firmar AcmeExceptions, pero aún no pueden estar seguros de si AcmeFileNotFoundErrorsucedió o no AcmePermissionDeniedError.

Entonces, si solo está interesado en un AcmeFileNotFoundError, la solución es presentar una solicitud de función con los programadores de ACME y decirles que implementen, declaren y documenten esa subclase de AcmeException.

¿Entonces, para qué molestarse?

Por lo tanto, incluso con excepciones verificadas, el compilador no puede obligar a los programadores a generar excepciones útiles . Todavía es solo una cuestión de calidad de la API.

Como resultado, los idiomas sin excepciones marcadas generalmente no obtienen resultados mucho peores. Los programadores pueden tener la tentación de lanzar instancias no específicas de una Errorclase general en lugar de un AcmeException, pero si les importa la calidad de su API, aprenderán a introducir un AcmeFileNotFoundErrordespués de todo.

En general, la especificación y documentación de excepciones no es muy diferente de la especificación y documentación de, digamos, métodos ordinarios. Esas también son una cuestión de diseño de API, y si un programador olvidó implementar o exportar una función útil, la API debe mejorarse para que pueda trabajar con ella de manera útil.

Si sigue esta línea de razonamiento, debería ser obvio que la "molestia" de declarar, capturar y volver a lanzar excepciones que es tan común en lenguajes como Java a menudo agrega poco valor.

También vale la pena señalar que Java VM no tiene excepciones verificadas; solo el compilador de Java las verifica, y los archivos de clase con declaraciones de excepción modificadas son compatibles en tiempo de ejecución. La seguridad de Java VM no mejora con las excepciones comprobadas, solo con el estilo de codificación.

Categorías de excepción

Cuando hablo de excepciones, siempre me refiero al artículo del blog Excepciones irritantes de Eric Lippert . Coloca excepciones en estas categorías:

  • Fatal : estas excepciones no son su culpa : no puede prevenirlas y no puede manejarlas con sensatez. Por ejemplo, OutOfMemoryErroro ThreadAbortException.
  • Boneheaded : estas excepciones son su culpa : debería haberlas evitado y representan errores en su código. Por ejemplo, ArrayIndexOutOfBoundsException, NullPointerExceptiono cualquier IllegalArgumentException.
  • Inquietante : estas excepciones no son excepcionales , no es su culpa, no puede evitarlas, pero tendrá que lidiar con ellas. A menudo son el resultado de una decisión de diseño desafortunada, como arrojar NumberFormatExceptiondesde Integer.parseInten lugar de proporcionar un Integer.tryParseIntmétodo que devuelve un falso booleano en caso de error de análisis.
  • Exógenas : estas excepciones suelen ser excepcionales , no es su culpa, no puede (razonablemente) prevenirlas, pero debe manejarlas . Por ejemplo, FileNotFoundException.

Un usuario de la API:

  • no debe manejar excepciones fatales o estúpidas .
  • debería manejar excepciones molestas , pero no deberían ocurrir en una API ideal.
  • debe manejar excepciones exógenas .

Excepciones comprobadas

El hecho de que el usuario de la API deba manejar una excepción en particular es parte del contrato del método entre la persona que llama y la persona que llama. El contrato especifica, entre otras cosas: el número y los tipos de argumentos que espera la persona que llama, el tipo de valor de retorno que puede esperar la persona que llama y las excepciones que se espera que maneje la persona que llama .

Dado que las excepciones molestas no deberían existir en una API, solo estas excepciones exógenas deben verificarse como excepciones para que formen parte del contrato del método. Relativamente pocas excepciones son exógenas , por lo que cualquier API debería tener relativamente pocas excepciones comprobadas.

Una excepción comprobada es una excepción que debe manejarse . Manejar una excepción puede ser tan simple como tragarla. ¡Allá! Se maneja la excepción. Período. Si el desarrollador quiere manejarlo de esa manera, bien. Pero no puede ignorar la excepción y ha sido advertido.

problemas de API

Pero cualquier API que haya verificado excepciones molestas y fatales (por ejemplo, el JCL) ejercerá una presión innecesaria sobre los usuarios de la API. Dichas excepciones deben manejarse, pero la excepción es tan común que no debería haber sido una excepción en primer lugar, o no se puede hacer nada al manejarla. Y esto hace que los desarrolladores de Java odien las excepciones verificadas.

Además, muchas API no tienen una jerarquía de clase de excepción adecuada, lo que provoca que todo tipo de causas de excepción no exógenas se representen mediante una única clase de excepción marcada (p. ej IOException., ). Y esto también hace que los desarrolladores de Java odien las excepciones verificadas.

Conclusión

Las excepciones exógenas son aquellas que no son culpa suya, que no se pudieron haber evitado y que deben manejarse. Estos forman un pequeño subconjunto de todas las excepciones que pueden generarse. Las API solo deberían haber marcado las excepciones exógenas y todas las demás excepciones sin marcar. Esto creará mejores API, ejercerá menos presión sobre el usuario de la API y, por lo tanto, reducirá la necesidad de capturar todo, tragar o volver a generar excepciones no verificadas.

Así que no odies a Java y sus excepciones comprobadas. En cambio, odie las API que abusan de las excepciones comprobadas.

Ok... Las excepciones marcadas no son ideales y tienen algunas advertencias, pero tienen un propósito. A la hora de crear una API existen casos concretos de fallos que son contractuales de esta API. Cuando en el contexto de un lenguaje fuertemente tipado estáticamente como Java, si uno no usa excepciones verificadas, entonces uno debe confiar en la documentación y convención ad-hoc para transmitir la posibilidad de error. Si lo hace, elimina todos los beneficios que el compilador puede traer en el manejo de errores y se deja completamente a la buena voluntad de los programadores.

Entonces, uno elimina la excepción Checked, como se hizo en C #, ¿cómo puede uno transmitir programática y estructuralmente la posibilidad de error? ¿Cómo informar al código del cliente que tales y cuales errores pueden ocurrir y deben ser tratados?

Escucho todo tipo de horrores cuando trato con excepciones verificadas, se usan mal, esto es cierto, pero también lo son las excepciones no verificadas. Digo esperar unos años cuando las API se apilan en muchas capas y estarás rogando por el regreso de algún tipo de medio estructurado para transmitir fallas.

Tomemos el caso cuando la excepción se lanzó en algún lugar en la parte inferior de las capas de la API y simplemente surgió porque nadie sabía que era posible que ocurriera este error, esto a pesar de que era un tipo de error que era muy plausible cuando el código de llamada lo lanzó (FileNotFoundException, por ejemplo, a diferencia de VogonsTrashingEarthExcept... en cuyo caso no importaría si lo manejamos o no, ya que no queda nada con lo que manejarlo).

Muchos han argumentado que no poder cargar el archivo fue casi siempre el fin del mundo para el proceso y debe tener una muerte horrible y dolorosa. Así que sí... claro... está bien... creas una API para algo y carga el archivo en algún momento... Yo, como usuario de dicha API, solo puedo responder... "¿Quién diablos eres tú para decidir cuándo mi ¡El programa debería fallar!" Claro Dada la opción donde las excepciones se engullen y no dejan rastro o EletroFlabbingChunkFluxManifoldChuggingException con un rastro de pila más profundo que la trinchera de Marianna, tomaría este último sin dudarlo, pero ¿significa esto que es la forma deseable de lidiar con la excepción? ? ¿No podemos estar en algún lugar en el medio, donde la excepción se reformularía y envolvería cada vez que atravesara un nuevo nivel de abstracción para que realmente signifique algo?

Por último, la mayor parte del argumento que veo es "No quiero tratar con excepciones, muchas personas no quieren tratar con excepciones. Las excepciones marcadas me obligan a tratar con ellas, por lo que odio la excepción marcada". Para eliminar dicho mecanismo por completo y relegarlo al abismo de ir al infierno es simplemente una tontería y carece de juicio y visión.

Si eliminamos la excepción verificada, también podríamos eliminar el tipo de retorno para las funciones y siempre devolver una variable "cualquier tipo"... Eso haría la vida mucho más simple ahora, ¿no es así?

Este no es un argumento en contra del concepto puro de excepciones verificadas, pero la jerarquía de clases que Java usa para ellas es un espectáculo de fenómenos. Siempre llamamos a las cosas simplemente "excepciones", lo cual es correcto, porque la especificación del lenguaje también las llama así , pero ¿cómo se nombra y representa una excepción en el sistema de tipos?

¿ Por la clase Exceptionque se imagina? Pues no, porque Exceptionlas s son excepciones, y las excepciones son Exceptions, excepto aquellas excepciones que no Exception son s, porque otras excepciones son en realidad Errors, que son el otro tipo de excepción, una especie de excepción extra-excepcional que nunca debería ocurrir excepto cuando lo hace, y que nunca debes atrapar, excepto que a veces tienes que hacerlo. Excepto que eso no es todo porque también puede definir otras excepciones que no son ni Exceptions ni Errors sino meras Throwableexcepciones.

¿Cuáles de estas son las excepciones "marcadas"? ThrowableLas s son excepciones verificadas, excepto si también son Errors, que son excepciones no verificadas, y luego están las Exceptions, que también son Throwables y son el tipo principal de excepción verificada, excepto que también hay una excepción, que es que si son también RuntimeExceptions, porque ese es el otro tipo de excepción no comprobada.

¿ RuntimeExceptionPara qué sirven? Bueno, tal como su nombre lo indica, son excepciones, como todos Exceptionlos correos electrónicos, y suceden en tiempo de ejecución, como todas las excepciones en realidad, excepto que RuntimeExceptionlos correos electrónicos son excepcionales en comparación con otros correos electrónicos en tiempo de ejecución Exceptionporque se supone que no deben suceder excepto cuando comete algún error tonto, aunque RuntimeExceptionlas s nunca son Errors, por lo que son para cosas que son excepcionalmente erróneas pero que en realidad no son Errors. Excepto por RuntimeErrorException, que realmente es un RuntimeExceptionpara Errors. Pero, ¿no se supone que todas las excepciones representan circunstancias erróneas de todos modos? Sí, todos ellos. Excepto por ThreadDeath, una excepción excepcionalmente normal, ya que la documentación explica que es una "ocurrencia normal" y que esoError

De todos modos, dado que estamos dividiendo todas las excepciones por la mitad en Errors (que son para excepciones de ejecución excepcionales, por lo que no se verifican) y Exceptions (que son para errores de ejecución menos excepcionales, por lo que se verifican excepto cuando no lo son), ahora necesitamos dos diferentes tipos de cada una de varias excepciones. Así que necesitamos IllegalAccessErrory IllegalAccessException, y InstantiationErrory InstantiationException, y NoSuchFieldErrory NoSuchFieldException, y NoSuchMethodErrory NoSuchMethodExceptiony ZipErrory ZipException.

Excepto que incluso cuando se verifica una excepción, siempre hay formas (bastante fáciles) de engañar al compilador y lanzarlo sin que se verifique. Si lo hace, puede obtener un UndeclaredThrowableException, excepto en otros casos, donde podría aparecer como un UnexpectedException, o un UnknownException(que no está relacionado con UnknownError, que es solo para "excepciones graves"), o un ExecutionException, o un InvocationTargetException, o un ExceptionInInitializerError.

Ah, y no debemos olvidar el nuevo y elegante de Java 8 UncheckedIOException, que es una RuntimeExceptionexcepción diseñada para permitirle lanzar el concepto de verificación de excepciones por la ventana al envolver las IOExceptionexcepciones verificadas causadas por errores de E/S (que no causan IOErrorexcepciones, aunque eso existe también) que son excepcionalmente difíciles de manejar y, por lo tanto, es necesario que no se controlen.

¡Gracias Java!

Este artículo es el mejor texto sobre el manejo de excepciones en Java que he leído.

Favorece las excepciones no verificadas sobre las verificadas, pero esta elección se explica muy detalladamente y se basa en argumentos sólidos.

No quiero citar demasiado el contenido del artículo aquí (es mejor leerlo como un todo), pero cubre la mayoría de los argumentos de los defensores de excepciones no verificadas de este hilo. Especialmente se cubre este argumento (que parece ser bastante popular):

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).

El autor "responde":

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.

Y el pensamiento o artículo principal es:

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!

Entonces, si " nadie sabía que era posible que ocurriera este error ", hay algo mal con ese proyecto. Tal excepción debe ser manejada por al menos el controlador de excepciones más genérico (por ejemplo, el que maneja todas las Excepciones no manejadas por controladores más específicos) como sugiere el autor.

Tan triste que no mucha gente parece descubrir este gran artículo :-(. Recomiendo de todo corazón a todos los que dudan qué enfoque es mejor que se tomen un tiempo y lo lean.

De hecho, las excepciones verificadas, por un lado, aumentan la solidez y la corrección de su programa (está obligado a hacer declaraciones correctas de sus interfaces; las excepciones que arroja un método son básicamente un tipo de retorno especial). Por otro lado, se enfrenta al problema de que, dado que las excepciones "surgen", muy a menudo necesita cambiar muchos métodos (todas las personas que llaman, y las personas que llaman de las personas que llaman, etc.) cuando cambia las excepciones. tiros de método.

Las excepciones comprobadas en Java no resuelven el último problema; C# y VB.NET tiran al bebé con el agua del baño.

En este documento de OOPSLA 2005 (o el informe técnico relacionado ) se describe un buen enfoque que toma el camino intermedio .

En resumen, le permite decir: method g(x) throws like f(x), lo que significa que g lanza todas las excepciones que lanza f. Voila, comprobó las excepciones sin el problema de los cambios en cascada.

Aunque es un artículo académico, lo animo a que lo lea (partes de él), ya que hace un buen trabajo al explicar cuáles son los beneficios y las desventajas de las excepciones verificadas.

El problema

¡El peor problema que veo con el mecanismo de manejo de excepciones es que introduce la duplicación de código a gran escala ! Seamos honestos: en la mayoría de los proyectos, en el 95% del tiempo, todo lo que los desarrolladores realmente necesitan hacer con excepción es comunicarlo de alguna manera al usuario (y, en algunos casos, también al equipo de desarrollo, por ejemplo, enviando un correo electrónico). -mail con el seguimiento de la pila). Por lo general, se usa la misma línea/bloque de código en cada lugar donde se maneja la excepción.

Supongamos que hacemos un registro simple en cada bloque catch para algún tipo de excepción verificada:

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

Si se trata de una excepción común, puede haber incluso varios cientos de estos bloques de prueba y captura en una base de código más grande. Ahora supongamos que necesitamos introducir el manejo de excepciones basado en un cuadro de diálogo emergente en lugar del registro de la consola o comenzar a enviar un correo electrónico adicional al equipo de desarrollo.

Espera un momento... ¿realmente vamos a editar todos esos cientos de ubicaciones en el código? Me entiendes :-).

La solución

Lo que hicimos para solucionar ese problema fue introducir el concepto de controladores de excepciones (a los que me referiré más adelante como EH) para centralizar el manejo de excepciones. Para cada clase que necesita manejar excepciones, nuestro marco de Inyección de Dependencia inyecta una instancia de controlador de excepciones. Entonces, el patrón típico de manejo de excepciones ahora se ve así:

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

Ahora, para personalizar nuestro manejo de excepciones, solo necesitamos cambiar el código en un solo lugar (código EH).

Por supuesto, para casos más complejos, podemos implementar varias subclases de EH y aprovechar las funciones que nos brinda nuestro marco DI. Al cambiar nuestra configuración de marco DI, podemos cambiar fácilmente la implementación de EH globalmente o proporcionar implementaciones específicas de EH a clases con necesidades especiales de manejo de excepciones (por ejemplo, usando la anotación Guice @Named).

De esa manera, podemos diferenciar el comportamiento del manejo de excepciones en el desarrollo y la versión de lanzamiento de la aplicación (por ejemplo, desarrollo: registrar el error y detener la aplicación, prod: registrar el error con más detalles y permitir que la aplicación continúe su ejecución) sin esfuerzo.

última cosa

Por último, pero no menos importante, puede parecer que se puede obtener el mismo tipo de centralización simplemente pasando nuestras excepciones "hacia arriba" hasta que lleguen a alguna clase de manejo de excepciones de nivel superior. Pero eso conduce al desorden del código y las firmas de nuestros métodos e introduce problemas de mantenimiento mencionados por otros en este hilo.

Una cosa importante que nadie mencionó es cómo interfiere con las interfaces y las expresiones lambda.

Digamos que usted define un MyAppException extends Exception. Es la excepción de nivel superior heredada por todas las excepciones lanzadas por su aplicación. En algunos lugares, no desea reaccionar a las excepciones particulares, desea que la persona que llama lo resuelva, por lo que declara throws MyAppException.

Todo se ve bien hasta que desee usar la interfaz de otra persona. Obviamente, no declaran la intención de lanzar MyAppException, por lo que el compilador ni siquiera le permite llamar a sus métodos que declaran throws MyAppExceptionallí. Esto es especialmente doloroso con java.util.function.

Sin embargo, si su excepción se extiende RuntimeException, no habrá problema con las interfaces. Puede mencionar la excepción en JavaDoc si lo desea. Pero aparte de eso, simplemente burbujea silenciosamente a través de cualquier cosa. Por supuesto, eso significa que puede cancelar su aplicación. Pero en mucho software empresarial tiene una capa de manejo de excepciones y las excepciones no verificadas ahorran muchos problemas.

Para intentar abordar solo la pregunta sin respuesta:

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

La pregunta contiene un razonamiento engañoso en mi humilde opinión. El hecho de que la API le diga lo que arroja no significa que lo trate de la misma manera en todos los casos. Para decirlo de otra manera, las excepciones que necesita capturar varían según el contexto en el que use el componente que lanza la excepción.

Por ejemplo:

Si estoy escribiendo un probador de conexión para una base de datos, o algo para verificar la validez de un XPath ingresado por el usuario, entonces probablemente me gustaría capturar e informar sobre todas las excepciones verificadas y no verificadas que genera la operación.

Sin embargo, si estoy escribiendo un motor de procesamiento, probablemente trataré una XPathException (marcada) de la misma manera que una NPE: dejaría que se ejecutara hasta la parte superior del subproceso de trabajo, omitiría el resto de ese lote, registraría el problema (o envíelo a un departamento de soporte para el diagnóstico) y deje comentarios para que el usuario se comunique con el soporte.

Como la gente ya ha dicho, las excepciones comprobadas no existen en el código de bytes de Java. Son simplemente un mecanismo de compilación, similar a otras comprobaciones de sintaxis. Veo excepciones verificadas de forma muy parecida a como veo al compilador quejándose de un condicional redundante: if(true) { a; } b;. Eso es útil, pero podría haberlo hecho a propósito, así que permítanme ignorar sus advertencias.

El hecho es que no podrá obligar a todos los programadores a "hacer lo correcto" si hace cumplir las excepciones verificadas y todos los demás ahora son daños colaterales que simplemente lo odian por la regla que hizo.

¡Arregla los malos programas que hay! ¡No intentes arreglar el lenguaje para no permitirlos! Para la mayoría de las personas, "hacer algo con respecto a una excepción" en realidad es simplemente informar al usuario al respecto. También puedo informarle al usuario sobre una excepción no verificada, así que mantenga sus clases de excepción verificadas fuera de mi API.

Anders habla sobre los peligros de las excepciones comprobadas y por qué las dejó fuera de C# en el episodio 97 de Software Engineering radio.

Mi artículo sobre c2.com sigue sin cambios en su forma original: CheckedExceptionsAreIncompatibleWithVisitorPattern

En resumen:

Visitor Pattern y sus parientes son una clase de interfaces donde la persona que llama indirectamente y la implementación de la interfaz conocen una excepción, pero la interfaz y la persona que llama directamente forman una biblioteca que no puede saber.

La suposición fundamental de CheckedExceptions es que todas las excepciones declaradas pueden lanzarse desde cualquier punto que llame a un método con esa declaración. VisitorPattern revela que esta suposición es defectuosa.

El resultado final de las excepciones verificadas en casos como estos es una gran cantidad de código inútil que esencialmente elimina la restricción de excepción verificada del compilador en tiempo de ejecución.

En cuanto al problema de fondo:

Mi idea general es que el controlador de nivel superior debe interpretar la excepción y mostrar un mensaje de error apropiado. Casi siempre veo excepciones de IO, excepciones de comunicación (por alguna razón, las API distinguen) o errores fatales de tareas (errores de programa o problemas graves en el servidor de respaldo), por lo que esto no debería ser demasiado difícil si permitimos un seguimiento de pila para un grave problema del servidor

Las excepciones comprobadas fueron, en su forma original, un intento de manejar contingencias en lugar de fallas. El objetivo loable era resaltar puntos predecibles específicos (no se pudo conectar, archivo no encontrado, etc.) y asegurarse de que los desarrolladores los manejaran.

Lo que nunca se incluyó en el concepto original fue obligar a declarar una amplia gama de fallas sistémicas e irrecuperables. Estas fallas nunca fueron correctas para ser declaradas como excepciones verificadas.

Las fallas generalmente son posibles en el código, y los contenedores EJB, web y Swing/AWT ya se encargan de esto al proporcionar un controlador de excepción de "solicitud fallida" más externo. La estrategia correcta más básica es revertir la transacción y devolver un error.

Un punto crucial es que el tiempo de ejecución y las excepciones verificadas son funcionalmente equivalentes. No hay manejo o recuperación que puedan hacer las excepciones comprobadas, que no puedan hacer las excepciones de tiempo de ejecución.

El mayor argumento en contra de las excepciones "verificadas" es que la mayoría de las excepciones no se pueden corregir. El simple hecho es que no somos dueños del código/subsistema que se estropeó. No podemos ver la implementación, no somos responsables de ella y no podemos arreglarla.

Si nuestra aplicación no es una base de datos... no deberíamos intentar arreglar la base de datos. Eso violaría el principio de encapsulación .

Particularmente problemáticas han sido las áreas de JDBC (SQLException) y RMI para EJB (RemoteException). En lugar de identificar contingencias reparables según el concepto original de "excepción comprobada", estos problemas generalizados de confiabilidad sistémica, que en realidad no son reparables, obligaron a declararse ampliamente.

La otra falla grave en el diseño de Java era que el manejo de excepciones debería colocarse correctamente en el nivel más alto posible de "negocio" o "solicitud". El principio aquí es "lanzar temprano, atrapar tarde". Las excepciones marcadas hacen poco más que interponerse en el camino de esto.

Tenemos un problema obvio en Java de requerir miles de bloques de prueba y captura que no hacen nada, con una proporción significativa (40%+) mal codificada. Casi ninguno de estos implementa un manejo o confiabilidad genuinos, pero impone una gran sobrecarga de codificación.

Por último, las "excepciones comprobadas" son prácticamente incompatibles con la programación funcional de FP.

Su insistencia en "manejar de inmediato" está en desacuerdo con las mejores prácticas de manejo de excepciones de "atrapar tarde" y cualquier estructura de FP que abstrae bucles o flujo de control.

Muchas personas hablan sobre el "manejo" de las excepciones comprobadas, pero lo hacen a través de sus sombreros. Seguir después de un fallo con datos nulos, incompletos o incorrectos para aparentar éxito no es manejar nada. Es negligencia de ingeniería/confiabilidad de la forma más baja.

Fallar limpiamente es la estrategia correcta más básica para manejar una excepción. Revertir la transacción, registrar el error e informar una respuesta de "fallo" al usuario son buenas prácticas y, lo que es más importante, evitan que se envíen datos comerciales incorrectos a la base de datos.

Otras estrategias para el manejo de excepciones son "reintentar", "reconectar" u "omitir", a nivel de negocio, subsistema o solicitud. Todas estas son estrategias generales de confiabilidad y funcionan bien/mejor con excepciones de tiempo de ejecución.

Por último, es mucho mejor fallar que ejecutar con datos incorrectos. Continuar causará errores secundarios, distantes de la causa original y más difíciles de depurar; o eventualmente resultará en la confirmación de datos erróneos. La gente es despedida por eso.

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

Creo que esta es una pregunta excelente y nada argumentativa. Creo que las bibliotecas de terceros deberían (en general) lanzar excepciones no verificadas . Esto significa que puede aislar sus dependencias en la biblioteca (es decir, no tiene que volver a lanzar sus excepciones o lanzarlas Exception, generalmente una mala práctica). La capa DAO de Spring es un excelente ejemplo de esto.

Por otro lado, las excepciones de la API central de Java en general deben verificarse si alguna vez pueden manejarse. Toma FileNotFoundExceptiono (mi favorito) InterruptedException. Estas condiciones casi siempre deben manejarse específicamente (es decir, su reacción a un InterruptedExceptionno es la misma que su reacción a un IllegalArgumentException). El hecho de que sus excepciones estén marcadas obliga a los desarrolladores a pensar si una condición es manejable o no. (Dicho esto, ¡rara vez he visto que InterruptedExceptionse manejen correctamente!)

Una cosa más: a RuntimeExceptionno siempre es "donde un desarrollador se equivocó en algo". Se lanza una excepción de argumento ilegal cuando intenta crear un enumuso valueOfy no hay enumese nombre. ¡Esto no es necesariamente un error del desarrollador!

Un problema con las excepciones verificadas es que las excepciones a menudo se adjuntan a los métodos de una interfaz si incluso una implementación de esa interfaz lo usa.

Otro problema con las excepciones verificadas es que tienden a ser mal utilizadas. El ejemplo perfecto de esto está en java.sql.Connectionel close()método de . Puede arrojar un SQLException, aunque ya haya declarado explícitamente que ha terminado con la conexión. ¿Qué información podría transmitir close() que le interesaría?

Por lo general, cuando cierro() una conexión *, se ve así:

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

Además, no me hagas empezar con los diversos métodos de análisis y NumberFormatException... TryParse de .NET, que no arroja excepciones, es mucho más fácil de usar, es doloroso tener que volver a Java (usamos Java y C# donde trabajo).

*Como comentario adicional, Connection.close() de PooledConnection ni siquiera cierra una conexión, pero aún debe capturar la SQLException debido a que es una excepción verificada.

El programador necesita conocer todas las excepciones que un método puede arrojar para usarlo correctamente. Entonces, golpearlo en la cabeza con solo algunas excepciones no necesariamente ayuda a un programador descuidado a evitar errores.

El pequeño beneficio se ve superado por el oneroso costo (especialmente en bases de código más grandes y menos flexibles donde no es práctico modificar constantemente las firmas de la interfaz).

El análisis estático puede ser bueno, pero el análisis estático verdaderamente confiable a menudo exige un trabajo estricto por parte del programador. Hay un cálculo de costo-beneficio, y la barra debe establecerse alta para una verificación que conduzca a un error de tiempo de compilación. Sería más útil si el IDE asumiera la función de comunicar qué excepciones puede generar un método (incluidas las que son inevitables). Aunque tal vez no sería tan confiable sin declaraciones de excepción forzadas, la mayoría de las excepciones aún se declararían en la documentación, y la confiabilidad de una advertencia de IDE no es tan crucial.

Las buenas pruebas de que las Excepciones marcadas no son necesarias son:

  1. Una gran cantidad de marco que hace algo de trabajo para Java. Como Spring que envuelve la excepción JDBC en excepciones no verificadas, arrojando mensajes al registro
  2. Muchos idiomas que vinieron después de Java, incluso en la parte superior de la plataforma Java, no los usan
  3. Excepciones marcadas, es una especie de predicción sobre cómo el cliente usaría el código que arroja una excepción. Pero un desarrollador que escribe este código nunca sabría sobre el sistema y el negocio en el que está trabajando el cliente del código. Como ejemplo, los métodos de interfaz que obligan a lanzar una excepción verificada. Hay 100 implementaciones en el sistema, 50 o incluso 90 de las implementaciones no arrojan esta excepción, pero el cliente aún debe detectar esta excepción si el usuario hace referencia a esa interfaz. Esas 50 o 90 implementaciones tienden a manejar esas excepciones dentro de sí mismas, poniendo una excepción en el registro (y esto es un buen comportamiento para ellos). ¿Qué debemos hacer con eso? Será mejor que tenga alguna lógica de fondo que haga todo ese trabajo: enviar un mensaje al registro. Y si yo, como cliente de código, siento que necesito manejar la excepción, lo haré.
  4. Otro ejemplo cuando estoy trabajando con E/S en Java, ¿me obliga a verificar todas las excepciones, si el archivo no existe? que debo hacer con eso? Si no existe, el sistema no pasaría al siguiente paso. El cliente de este método no obtendría el contenido esperado de ese archivo; puede manejar la Excepción de tiempo de ejecución; de lo contrario, primero debería verificar la Excepción verificada, poner un mensaje en el registro y luego lanzar una excepción desde el método. No... no. Será mejor que lo haga automáticamente con RuntimeEception, que lo hace/se enciende automáticamente. No tiene ningún sentido manejarlo manualmente; me alegraría ver un mensaje de error en el registro (AOP puede ayudar con eso ... algo que soluciona Java). Si, finalmente, decido que el sistema debería mostrar un mensaje emergente al usuario final, lo mostraré, no hay problema.

Estaba feliz si Java me proporcionaría una opción de qué usar, cuando trabajo con bibliotecas centrales, como E/S. Like proporciona dos copias de las mismas clases, una envuelta con RuntimeEception. Entonces podemos comparar lo que la gente usaría . Por ahora, sin embargo, muchas personas preferirían optar por algún marco superior en Java o en un idioma diferente. Como Scala, JRuby lo que sea. Muchos simplemente creen que SUN tenía razón.

Hemos visto algunas referencias al arquitecto jefe de C#.

Aquí hay un punto de vista alternativo de un chico de Java sobre cuándo usar excepciones marcadas. Reconoce muchos de los aspectos negativos que otros han mencionado: Excepciones efectivas

A pesar de haber leído toda la página, todavía no puedo encontrar un solo argumento razonable contra las excepciones verificadas. En cambio, la mayoría de la gente habla de un diseño deficiente de la API, ya sea en algunas clases de Java o en sus propias clases.

El único escenario en el que esta característica puede ser molesta es la creación de prototipos. Esto podría resolverse agregando algún mecanismo al lenguaje (por ejemplo, alguna anotación @supresscheckedexceptions). Pero para la programación regular, creo que las excepciones verificadas son algo bueno.

He leído mucho sobre el manejo de excepciones, incluso si (la mayoría de las veces) realmente no puedo decir si estoy feliz o triste por la existencia de excepciones verificadas, esta es mi opinión: excepciones verificadas en código de bajo nivel (IO, redes , SO, etc.) y excepciones no verificadas en API de alto nivel/nivel de aplicación.

Incluso si no es tan fácil trazar una línea entre ellos, encuentro que es realmente molesto/difícil integrar varias API/bibliotecas bajo el mismo techo sin envolver todo el tiempo muchas excepciones verificadas pero, por otro lado, en algún momento es útil/es mejor verse obligado a detectar alguna excepción y proporcionar una diferente que tenga más sentido en el contexto actual.

El proyecto en el que estoy trabajando toma muchas bibliotecas y las integra bajo la misma API, API que se basa completamente en excepciones no verificadas. Este marco proporciona una API de alto nivel que al principio estaba llena de excepciones verificadas y solo tenía varias sin verificar. excepciones (excepción de inicialización, excepción de configuración, etc.) y debo decir que no fue muy amigable . La mayoría de las veces, tenía que capturar o volver a lanzar excepciones que no sabe cómo manejar, o que ni siquiera le importan (no debe confundirse con que debe ignorar las excepciones), especialmente en el lado del cliente donde un solo click podría lanzar 10 excepciones posibles (marcadas).

La versión actual (la tercera) usa solo excepciones no verificadas y tiene un controlador de excepciones global que es responsable de manejar todo lo que no se detecte. La API proporciona una forma de registrar controladores de excepciones, que decidirán si una excepción se considera un error (la mayoría de las veces este es el caso), lo que significa registrar y notificar a alguien, o puede significar otra cosa, como esta excepción, AbortException, lo que significa romper el hilo de ejecución actual y no registrar ningún error porque se desea que no lo haga. Por supuesto, para poder resolver todos los subprocesos personalizados, debe manejar el método run() con un intento {...} catch(all).

ejecución de vacío público () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

Esto no es necesario si usa WorkerService para programar trabajos (Ejecutable, Llamable, Trabajador), que maneja todo por usted.

Por supuesto, esta es solo mi opinión, y puede que no sea la correcta, pero me parece un buen enfoque. Veré después de lanzar el proyecto si lo que creo que es bueno para mí, es bueno para otros también... :)