todas - tipos de excepciones en java netbeans
El caso contra las excepciones comprobadas. (30)
Desde hace algunos 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 controladas? He tenido 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 un código nuevo y prestando mucha atención a cómo trato con 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.
Todas las conversaciones que tengo terminan con la misma pregunta sin respuesta ... déjame configurarlo:
En general (de cómo se diseñó Java),
- El error es para cosas que nunca deben ser atrapadas (VM tiene una alergia al maní y alguien dejó caer un frasco de maní)
- RuntimeException es para cosas que el programador hizo mal (el programador salió del final de una matriz)
- La excepción (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 ha alcanzado el límite del identificador del 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 va a hacer es salir del programa.
Otro argumento común que escucho es que las excepciones comprobadas hacen que sea más difícil refactorizar el código.
Para el argumento de "todo lo que voy a hacer es salir", digo que incluso si está saliendo necesita mostrar un mensaje de error razonable. Si solo está tratando de manejar los errores, sus usuarios no estarán muy contentos cuando el programa salga sin una clara indicación de por qué.
Para la multitud "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 excepción IOException, la excepción IOException debería transformarse en una excepción que sea más adecuada para lo que está sucediendo.
No tengo un problema con el ajuste Principal con captura (Excepción) (o en algunos casos captura (Throwable) para garantizar que el programa pueda salir correctamente, pero siempre detecto las excepciones específicas que necesito. Hacer eso me permite por lo menos, mostrar un mensaje de error apropiado.
La pregunta a la que la gente nunca responde es esta:
Si lanza subclases RuntimeException en lugar de subclases de excepción, ¿cómo sabe lo que se supone que debe capturar?
Si la respuesta es la excepción de captura, también está tratando con los errores del programador 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 atrapa 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 olvida de detectarla? Eso me parece muy peligroso.
Yo diría que un programa que muestra un seguimiento de pila es incorrecto. ¿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 recibe respuesta, por favor?
Edit: No busco consejos sobre cuándo usar ninguno de los modelos, lo que busco es por qué las personas se extienden desde RuntimeException porque no les gusta extenderse desde Exception y / o por qué detectan una excepción y luego vuelven a emitir una RuntimeException en lugar de Añadir tiros a su método. Quiero entender la motivación para rechazar las excepciones marcadas.
Categorías de excepción
Cuando hablo de excepciones, siempre vuelvo a consultar el artículo del blog Vexing exceptions de Eric Lippert . Él coloca excepciones en estas categorías:
- Fatal : estas excepciones no son su culpa : no puede evitarlas y no puede manejarlas con sensatez. Por ejemplo,
OutOfMemoryError
oThreadAbortException
. - Boneheaded : estas excepciones son su culpa : debería haberlas evitado y representan errores en su código. Por ejemplo,
ArrayIndexOutOfBoundsException
,NullPointerException
o cualquierIllegalArgumentException
. - Vexing : estas excepciones no son excepcionales , no es tu culpa, no puedes evitarlas, pero tendrás que lidiar con ellas. A menudo son el resultado de una desafortunada decisión de diseño, como lanzar
NumberFormatException
desde enInteger.parseInt
lugar de proporcionar unInteger.tryParseInt
método que devuelva un falso booleano en el fallo de análisis. - Exógenas : estas excepciones generalmente son 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 de cabeza hueca .
- debe manejar excepciones desconcertantes , pero no deben ocurrir en una API ideal.
- Debe manejar excepciones exógenas .
Excepciones comprobadas
El hecho de que el usuario de la API debe manejar una excepción 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 recibe la llamada, el tipo de valor de retorno que la persona que llama puede esperar y las excepciones que se espera que la persona que llama maneje .
Debido a que las excepciones desconcertantes no deberían existir en una API, solo estas excepciones exógenas deben ser verificadas para ser parte del contrato del método. Relativamente pocas excepciones son exógenas , por lo que cualquier API debería tener relativamente pocas excepciones verificadas.
Una excepción marcada es una excepción que debe manejarse . Manejar una excepción puede ser tan simple como tragársela. ¡Ahí! Se maneja la excepción. Período. Si el desarrollador quiere manejarlo de esa manera, está bien. Pero no puede ignorar la excepción, y ha sido advertido.
Problemas de API
Sin embargo, cualquier API que haya comprobado exageraciones y excepciones fatales (por ejemplo, el JCL) supondrá una presión innecesaria para los usuarios de la API. Estas excepciones tienen que ser manejado, pero de cualquier excepción es tan común que no debería haber sido una excepción en el primer lugar, o nada se puede hacer en su manipulación. Y esto hace que los desarrolladores de Java odien las excepciones marcadas.
Además, muchas API no tienen una jerarquía de clases de excepción adecuada, lo que hace que todos los tipos de excepción no exógena se representen mediante una única clase de excepción marcada (por ejemplo IOException
). Y esto también hace que los desarrolladores de Java odien las excepciones marcadas.
Conclusión
Las excepciones exógenas son aquellas que no son su culpa, no podrían haberse evitado y deben ser manejadas. Estos forman un pequeño subconjunto de todas las excepciones que se pueden lanzar. Las API solo deberían haber verificado las excepciones exógenas , y todas las demás excepciones sin marcar. Esto hará que las API sean mejores, pondrá menos tensión en el usuario de la API y, por lo tanto, reducirá la necesidad de capturar todas las excepciones, las de tragar o las de no seleccionar.
Así que no odies a Java y sus excepciones comprobadas. En cambio, odie las API que sobreutilizan las excepciones comprobadas.
El problema
El peor problema que veo con el mecanismo de manejo de excepciones es que ¡ introduce la duplicación de códigos a gran escala ! Seamos honestos: en la mayoría de los proyectos, en el 95% del tiempo, todo lo que realmente necesitan hacer los desarrolladores con excepción es comunicarse de alguna manera al usuario (y, en algunos casos, al equipo de desarrollo, por ejemplo, enviando -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 comprobada:
try{
methodDeclaringCheckedException();
}catch(CheckedException e){
logger.error(e);
}
Si es una excepción común, puede haber incluso cientos de bloques de try-catch en una base de código más grande. Ahora supongamos que debemos introducir el control de excepciones basado en el 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 ?! Tienes mi punto :-).
La solución
Lo que hicimos para solucionar este problema fue introducir el concepto de controladores de excepciones (a los que me referiré como EH) para centralizar el manejo de excepciones. Para cada clase que necesita tener excepciones, nuestro marco de inyección de dependencias inyecta una instancia de controlador de excepciones . Así que 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 características que nos brinda nuestro marco DI. Al cambiar nuestra configuración de la estructura 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, utilizando la anotación Guice @Named).
De esa manera podemos diferenciar el comportamiento de 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, producir el error del registro con más detalles y dejar que la aplicación continúe su ejecución) sin esfuerzo.
Ultima cosa
Por último, pero no menos importante, puede parecer que se puede obtener el mismo tipo de centralización simplemente pasando nuestras excepciones "arriba" hasta que llegan a alguna clase de manejo de excepciones de nivel superior. Pero eso lleva a la saturación de códigos y firmas de nuestros métodos e introduce problemas de mantenimiento mencionados por otros en este hilo.
SNR
En primer lugar, las excepciones verificadas disminuyen la "relación señal / ruido" para el código. Anders Hejlsberg también habla sobre la programación imperativa vs declarativa, que es un concepto similar. De todos modos considera los siguientes fragmentos de código:
Actualizar la interfaz de usuario desde un subproceso que no es de la 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);
}
Actualizar la interfaz de usuario desde un subproceso que no es de la 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 que me parece mucho más claro. Cuando empiezas a hacer más y más trabajo de UI en Swing, las excepciones comprobadas empiezan a ser realmente molestas e inútiles.
Fuga de la cárcel
Para implementar incluso las implementaciones más básicas, como la interfaz de la Lista de Java, las excepciones comprobadas como una herramienta para el diseño por contrato caen. Considere una lista que esté 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 comprobadas solo agregan ruido, la excepción se detectó pero no se manejó y el diseño por contrato (en términos de excepciones comprobadas) se rompió.
Conclusión
- Atrapar excepciones es diferente a manejarlas.
- Las excepciones comprobadas agregan ruido al código.
- El manejo de excepciones funciona bien en C # sin ellos.
He blogeado sobre esto previously .
Creo que leí la misma entrevista de Bruce Eckel que hiciste, y siempre me molesta. De hecho, la discusión fue hecha por el entrevistado (si es esta la publicación de la que habla) Anders Hejlsberg, el genio de la EM detrás de .NET y C #.
Aunque soy fanático de Hejlsberg y su trabajo, este argumento siempre me ha parecido falso. Básicamente se reduce a:
"Las excepciones marcadas son malas porque los programadores simplemente abusan de ellas atrapándolas y descartándolas, lo que provoca que se oculten los problemas y se ignoren, que de otro modo se presentarían al usuario".
Por "presentado al usuario" me refiero a 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 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 Goslings para no poner las anulaciones de los operadores en Java proviene de un argumento similar: confunden al programador 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 marcadas es algo malo y tiende a conducir a un manejo descuidado por parte de los usuarios, pero su uso adecuado permite al programador de API brindar un gran beneficio al programador cliente de API.
Ahora el programador de API debe tener cuidado de no lanzar excepciones marcadas por todas partes, o simplemente molestarán al programador cliente. El programador cliente, 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 abrir archivo. Cada lenguaje de programación en el historial de lenguajes (al menos en sistemas de archivos) tiene una API en algún lugar que le permite abrir un archivo. Y cada programador cliente que utiliza esta API sabe que tiene que lidiar con el caso de que el archivo que intentan abrir no exista. Permítanme reformular eso: Todo programador cliente que use esta API debe saber que tiene que lidiar con este caso. Y ahí está el problema: ¿puede el programador de API ayudarles a saber que deben lidiar con eso a través de comentarios solos o pueden insistir en que el cliente se ocupe de eso?
En C el idioma va algo así como
if (f = fopen("goodluckfindingthisfile")) { ... }
else { // file not found ...
donde fopen
indica un error al devolver 0 y C (tontamente) te permite tratar a 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 funcionalidades (métodos) para usar con un protocolo claramente definido para cada uno.
Ese protocolo claramente definido se define típicamente por una firma de método. Aquí fopen requiere que le pases una cadena (o un carácter * en el caso de C). Si le das algo más, obtienes un error en tiempo de compilación. No siguió el protocolo, no está utilizando la API correctamente.
En algunos idiomas (oscuros), el tipo de retorno también es parte del protocolo. Si intenta llamar al equivalente de fopen()
en algunos idiomas sin asignarlo a una variable, también obtendrá un error en tiempo de compilación (solo puede hacerlo con funciones nulas).
El punto que trato de señalar es que: en un lenguaje estático, el programador de la API anima al cliente a usar la API correctamente al evitar que su código de cliente se compile si comete algún error obvio.
(En un lenguaje de tipo dinámico, como Ruby, puede pasar cualquier cosa, diga un flotante, como el nombre del archivo, y se compilará. ¿Por qué molestar al usuario con las excepciones verificadas si ni siquiera va a controlar los argumentos del método? los argumentos hechos aquí se aplican solo a lenguajes de tipo 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 FileNotFoundException
es 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 el tiempo de ejecución "
Y ese es 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". Mi primer pensamiento fue que él / ella quiere decir cosas que están fuera del control de los programadores de API . Pero, de hecho, las excepciones comprobadas cuando se usan correctamente deberían ser para cosas que están fuera del control del programador cliente y del programador 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 programador de API sabe que puede darles un nombre de archivo que resulta inexistente en el momento en que se llama a la API, y que no podrán devolverle lo que deseaba, pero tendrá que lanzar una excepción. También saben que esto sucederá con bastante frecuencia y que el programador cliente puede esperar que el nombre del archivo sea correcto en el momento en que escribieron la llamada, pero también podría estar equivocado en tiempo de ejecución por razones fuera de su control.
Entonces, la API lo hace explícito: habrá casos en los que este archivo no exista en el momento en que me llamas y es mucho mejor que lo hayas tratado.
Esto sería más claro con un caso contrario. Imagina que estoy escribiendo una tabla API. 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 pasa 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 marcada y forzar al cliente a lidiar con ella:
public RowData getRowData(int row) throws CheckedInvalidRowNumberException
(Realmente no lo llamaría "Revisado", por supuesto).
Este es un mal uso de las excepciones comprobadas. El código del cliente estará lleno de llamadas para obtener datos de la 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 debería permitir que el usuario ingrese a un estado en el que se solicita una fila ilegal. Así que es un error por parte del programador cliente.
El programador de la API aún puede predecir que el cliente codificará dichos errores y debe manejarlo con una excepción de tiempo de ejecución como una excepción 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 suceda, los valores de las filas ilegales no serán obvios ni siquiera para el probador o la depuración del desarrollador del cliente, sino que darán lugar a errores que son difíciles de identificar en el origen. Los cohetes Arianne explotarán después del lanzamiento.
De acuerdo, aquí está el problema: estoy diciendo que la excepción FileNotFoundException
no es solo una buena cosa, sino una herramienta esencial en la caja de herramientas de los programadores de la API para definir la API de la manera más útil para el programador cliente. Pero la CheckedInvalidRowNumberException
es un gran inconveniente, que conduce a una mala programación y debe evitarse. Pero cómo saber la diferencia.
Supongo que no es una ciencia exacta y creo que eso subyace y tal vez justifica en cierta medida el argumento de Hejlsberg. Pero no estoy contento de echar al bebé con el agua del baño aquí, así que permítame extraer algunas reglas aquí para distinguir las excepciones buenas y correctas:
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 programador cliente. Esto tiene que ver con qué tan abierto o cerrado está el sistema. En una interfaz de usuario restringida donde el programador 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 programación del cliente si intenta obtener datos de una fila inexistente. En un sistema operativo basado en archivos en el que cualquier número de usuarios / aplicaciones pueden agregar y eliminar archivos (un sistema abierto), es posible que el archivo que el cliente solicita se haya eliminado sin su conocimiento, por lo que debe esperarse que lo traten. .
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 frecuentemente en el tiempo. Por lo tanto, un código de cliente no tiende a tratar de abrir mucho el mismo archivo, pero mi vista de tabla obtiene
RowData
de diferentesRowData
. En particular, voy a escribir un montón de código comoif (model.getRowData().getCell(0).isEmpty())
y será doloroso tener que envolver en try / catch cada vez.
Informar al usuario:
Las excepciones verificadas se deben usar en los casos en 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. Ya que puede predecir que algo fuera de su sistema cliente-API puede hacer que el archivo no esté allí, razonablemente puede decirle al usuario:
"Error: could not find the file ''goodluckfindingthisfile''"
Dado 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 proporcionarles. Si su aplicación no permite que las excepciones de tiempo de ejecución caigan en la consola, probablemente terminará dándoles un mensaje feo como:
"Internal error occured: IllegalArgumentException in ...."
En resumen, si no cree que su programador cliente pueda explicar su excepción de una manera que ayude al usuario, entonces probablemente no debería usar una excepción marcada.
Así que esas son mis reglas. Algo artificial, y sin duda habrá excepciones (por favor, ayúdenme a refinarlas si lo desea). 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 API como los tipos de parámetros. Por lo tanto, no debemos prescindir de él solo porque se usa mal.
Lo siento, no quise hacer esto tan largo y desagradable. Déjame terminar con dos sugerencias:
A: programadores de API: use las excepciones marcadas con moderación para preservar su utilidad. En caso de duda, utilice una excepción sin marcar.
B: Programadores de clientes: adquiera el hábito de crear una excepción envuelta (busque en Google) al principio de su desarrollo. JDK 1.4 y versiones posteriores proporcionan un constructor en RuntimeException
para esto, pero también puede crear fácilmente el suyo propio. Aquí está el constructor:
public RuntimeException(Throwable cause)
Luego, adopte el hábito de tener que manejar una excepción marcada y se siente perezoso (o cree que el programador de API se mostró demasiado entusiasta al usar la excepción marcada en primer lugar), no solo trague la excepción, envuélvala y volver a hacerlo.
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 marcada, se verá obligado a regresar y lidiar con ella después de ver el problema en tiempo de ejecución. Porque, créame (y a Anders Hejlsberg), nunca volverá a ese TODO en su
catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
En lugar de repetir todas las (muchas) razones contra las excepciones marcadas, 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% de las veces no puedo hacer nada al respecto. Finalmente los bloques hacen cualquier 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 tenía que lidiar con eso y era perezoso. ¿Estuvo mal? Por supuesto. ¿Sucede? Absolutamente. ¿Qué pasaría si esto fuera una excepción sin marcar? La aplicación acabaría de morir (lo que 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 java.text.Format 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.
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 lugar de la cadena de llamadas puede desbordarse 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 que cada nivel de código entre el lanzador y el receptor para declarar que conocen todas las formas de excepción que pueden atravesarlas. En la práctica, esto es muy poco diferente de si las excepciones verificadas fueran simplemente valores de retorno especiales que el autor de la llamada 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 verificadas son una respuesta razonable.
El problema es que una gran cantidad de código, incluidas las grandes franjas de la biblioteca estándar, hace un uso incorrecto de las excepciones en condiciones reales excepcionales que es muy posible que desee alcanzar en varios niveles. ¿Por qué IOException no es una excepción RuntimeException? En todos los demás idiomas, puedo dejar que ocurra una excepción de IO, y si no hago nada para manejarlo, mi aplicación se detendrá y obtendré un útil rastreo de pila para ver. Esto es lo mejor que puede pasar.
Tal vez dos métodos del ejemplo en el que desea capturar todas las IOExceptions de todo el proceso de escritura a flujo, abortar el proceso y saltar al código de informe de errores; en Java, no puede hacer eso sin agregar ''arroja IOException'' en cada nivel de llamada, incluso niveles que no hacen IO por sí mismos. Tales métodos no deberían necesitar conocer el manejo de excepciones; Teniendo que añadir excepciones a sus firmas:
- aumenta innecesariamente el acoplamiento;
- hace que las firmas de interfaz sean muy frágiles de cambiar;
- hace que el código sea menos legible;
- es tan molesto que la reacción del programador común es derrotar el sistema haciendo algo horrible como ''lanzar Excepciones'', ''atrapar (Excepción e) {}'', o envolver 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 desordenar tu código con cosas tan absurdas como estas, no es de extrañar que las excepciones revisadas reciban un montón de odio, aunque en realidad este es solo un diseño de API pobre.
Otro efecto malo en particular es sobre 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 una excepción salga de su devolución de llamada al lugar donde llamó el componente B, pero no puede porque eso cambiaría la interfaz de devolución de llamada fijada por B. A solo puede hacerlo envolviendo la excepción real en una excepción RuntimeException, que es aún más repetitivo para el manejo de excepciones que escribir.
Se verificaron las excepciones implementadas en Java y su biblioteca estándar significan placa de calderas, placa de caldera, placa de caldera. En un lenguaje ya detallado, esto no es una victoria.
Sé que esta es una pregunta antigua, pero he pasado un tiempo luchando con las excepciones marcadas y tengo algo que agregar. Por favor, perdóname por la longitud de esto!
Mi principal problema con las excepciones comprobadas es que arruinan el polimorfismo. Es imposible hacer que jueguen bien con interfaces polimórficas.
Toma la buena interfaz de la List
Java. Tenemos implementaciones en memoria comunes como ArrayList
y LinkedList
. También tenemos la clase AbstractList
Skeletal que 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 clase de ejemplo WidgetList
lee algunos objetos de tamaño fijo de tipo 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 utilizando la interfaz de List
familiar, puede recuperar elementos ( list.get(123)
) o iterar una lista ( for (Widget w : list) ...
) sin necesidad de saber sobre WidgetList
. Uno puede pasar esta lista a cualquier método estándar que use listas genéricas, o envolverla en una lista Collections.synchronizedList
. El código que lo utiliza no necesita saber ni preocuparse si los "Widgets" están compuestos en el momento, provienen de una matriz, o se leen desde un archivo, una base de datos, o desde la red, o desde un relé del subespacio futuro. Seguirá funcionando correctamente porque la interfaz de la List
está implementada correctamente.
Excepto que no lo es. La clase anterior no se compila porque los métodos de acceso a archivos pueden IOException
una IOException
, una excepción comprobada que debe "capturar o especificar". No puedes especificarlo como lanzado : el compilador no te lo permitirá porque eso violaría el contrato de la interfaz de la List
. Y no hay ninguna manera útil de que WidgetList
pueda manejar la excepción (como explicaré más adelante).
Aparentemente, lo único que hay que hacer es atrapar y volver a emitir las excepciones verificadas 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);
}
}
((Edición: Java 8 ha agregado una clase UncheckedIOException
para exactamente este caso: para capturar y volver a generar IOException
a través de los límites de los métodos polimórficos. ¡Este tipo de prueba demuestra mi punto!))
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
conectada 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 comprobadas falla. Pero las excepciones comprobadas son tan insidiosas que aún no lo dejarán en paz, porque aún tiene que capturar y volver a generar cualquiera de los métodos de nivel inferior, saturando el código y saturando el seguimiento de la pila.
Encuentro que la ubicua interfaz Runnable
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 generar como una excepción RuntimeException
.
En realidad, puede lanzar excepciones comprobadas no declaradas si recurre 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 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;
}
}
¡Hurra! Usando esto, podemos lanzar una excepción marcada a cualquier profundidad de la pila sin declararla, sin envolverla en una excepción RuntimeException
, y sin saturar la traza de la pila. Usando el ejemplo "WidgetList" de nuevo:
@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 compilador se niega a permitirle detectar una excepción comprobada si, en su opinión errónea, no se pudo haber lanzado. (Las excepciones sin marcar no tienen esta regla). Para detectar 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, aún es un poco más simple que el código para extraer una excepción marcada que se incluyó en una excepción RuntimeException
.
Afortunadamente, el throw t;
La declaración es legal aquí, aunque el tipo de t
está marcado, gracias a una regla agregada en Java 7 sobre el reenvío de excepciones detectadas.
Cuando las excepciones verificadas cumplen con el polimorfismo, el caso opuesto también es un problema: cuando se especifica un método como potencialmente lanzar una excepción comprobada, pero una implementación anulada no lo hace. Por ejemplo, los métodos de write
la clase abstracta OutputStream
especifican todos los 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
s, por lo que no tienen una cláusula de throws
, y puede llamarlos sin preocuparse por el requisito de captura o especificación.
Excepto no siempre. Supongamos que Widget
tiene un método para guardarlo en una transmisión:
public void writeTo(OutputStream out) throws IOException;
Declarar este método para aceptar un OutputStream
simple es lo correcto, por lo que se puede utilizar de manera polimórfica 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 para manejar una excepción que 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 excepciones abiertas, debe agregar controladores para esas excepciones incluso si sabe que no ocurrirán en su aplicación.
Pero espere, las excepciones verificadas son realmente tan molestas, ¡que ni siquiera le permitirá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 un ByteArrayOutputStream
, el compilador lo regañará por intentar detectar una excepción marcada que dice que no se puede lanzar.
Esa regla causa algunos problemas absurdos. Por ejemplo, uno de los tres métodos de write
de OutputStream
no es reemplazado por ByteArrayOutputStream
. Específicamente, write(byte[] data)
es un método conveniente que escribe la matriz completa llamando a write(byte[] data, int offset, int length)
con una compensación 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 argumento tal como está. El método heredado hace exactamente lo correcto, pero incluye una cláusula de throws
no deseados. Tal vez fue un descuido en el diseño de ByteArrayOutputStream
, pero nunca pueden solucionarlo 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 se lanzará.
Esa regla es molesta durante la edición y la depuración también. Por ejemplo, a veces comentaré temporalmente una llamada a un método, y si hubiera podido lanzar una excepción verificada, el compilador ahora se quejará de la existencia de los bloques de try
y catch
locales. Así que tengo que comentarlos también, y ahora, al editar el código interno, el IDE sangrará al nivel incorrecto porque el {
y }
están comentados. Gah! Es una pequeña queja, pero parece que lo único que se comprueba con las excepciones es causar problemas.
Ya casi termino. Mi última frustración con las excepciones marcadas es que en la mayoría de los sitios de llamadas , no hay nada útil que puedas hacer con ellas. Idealmente, cuando algo sale mal, tendríamos un manejador específico de la aplicación competente que puede informar al usuario del problema y / o finalizar o volver a intentar la operación según corresponda. Solo un manejador en la parte superior de la pila puede hacer esto porque es el único que conoce el objetivo general.
En su lugar, obtenemos el siguiente idioma, que es rampante como una forma de cerrar el compilador:
try {
...
} catch (SomeStupidExceptionOmgWhoCares e) {
e.printStackTrace();
}
En una GUI o programa automatizado no se verá el mensaje impreso. 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 que el objeto de excepción original habrá desaparecido. Este idioma no es mejor que el de On Error Resume Next
de BASIC o el error_reporting(0);
On Error Resume Next
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 particular u otro controlador es específica de la aplicación, por lo que esto perjudica la reutilización del código.
¡Pero espera! Existe una manera fácil y universal de encontrar el controlador específico de la aplicación. Se encuentra en la parte superior de la pila de llamadas (o se establece como el controlador de excepciones no capturado del subproceso). Así que 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;
. Excepciones comprobadas solo estorbar.
Estoy seguro de que las excepciones marcadas sonaron como una buena idea cuando se diseñó el lenguaje, pero en la práctica he encontrado que todas son una molestia y ningún beneficio.
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 comprobadas, pero esta elección se explica muy bien y se basa en argumentos sólidos.
No quiero citar mucho del contenido del artículo aquí (es mejor leerlo en su totalidad) pero cubre la mayoría de los argumentos de los defensores de excepciones no verificados de este hilo. Especialmente este argumento (que parece ser bastante popular) está cubierto:
Tome el caso cuando se lanzó la excepción en algún lugar en la parte inferior de las capas de API y simplemente se aceleró porque nadie sabía que era posible que se produjera este error, aunque 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 hay nada con lo que manejarlo).
El autor "responde":
Es absolutamente incorrecto suponer que todas las excepciones de tiempo de ejecución no deben capturarse y permitir que se propaguen al "principio" de la aplicación. (...) Por cada condición excepcional que debe manejarse de forma distinta, según los requisitos del sistema / negocio, los programadores deben decidir dónde capturarlo y qué hacer una vez que se detecte la condición. Esto debe hacerse estrictamente de acuerdo con las necesidades reales de la aplicación, no en base a una alerta del compilador. Se debe permitir que todos los demás errores se propaguen libremente al controlador superior donde se registrarán y se tomará una acción elegante (quizás, terminación).
Y el pensamiento principal o artículo es:
Cuando se trata del manejo de errores en el software, ¡la única suposición segura y correcta que se puede hacer es que puede ocurrir una falla en absolutamente cada subrutina o módulo existente!
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 cuál es el mejor enfoque para tomarse un tiempo y leerlo.
Here un argumento en contra de las excepciones verificadas (de joelonsoftware.com):
El razonamiento es que considero que las excepciones no son mejores que las "goto", consideradas dañinas desde la década de 1960, ya que crean un salto brusco de un punto de código a otro. De hecho, son significativamente peores que los de Goto:
- Son invisibles en el código fuente. Al observar un bloque de código, incluidas las funciones que pueden o no generar excepciones, no hay forma de ver qué excepciones se pueden lanzar y desde dónde. Esto significa que incluso una inspección cuidadosa del código no revela posibles errores.
- Crean demasiados puntos de salida posibles para una función. Para escribir el código correcto, realmente tiene que pensar en cada ruta de código posible a través de su función. Cada vez que llama a una función que puede generar una excepción y no la captura en el momento, crea oportunidades para errores inesperados causados por funciones que terminaron abruptamente, dejando los datos en un estado incoherente u otras rutas de código que usted no realizó. pensar en.
En breve:
Las excepciones son una pregunta de diseño de API. -- Ni mas ni menos.
El argumento para las excepciones verificadas:
Para entender por qué las excepciones marcadas pueden no ser buenas, cambiemos la pregunta y preguntemos: ¿Cuándo o por qué son atractivas las excepciones, es decir, por qué querría que el compilador haga cumplir la declaración de excepciones?
La respuesta es obvia: a veces es necesario detectar una excepción, y eso solo es posible si el código al que se llama ofrece una clase de excepción específica para el error en el que está interesado.
Por lo tanto, el argumento para las excepciones comprobadas es que el compilador obliga a los programadores a declarar qué excepciones se lanzan, y es de esperar que el programador también documente las clases de excepciones específicas y los errores que las causan.
En realidad, sin embargo, siempre, con demasiada frecuencia, un paquete com.acme
solo lanza AcmeException
subclases en lugar de específicas. Las personas que llaman luego deben manejar, declarar o volver a hacer una señal AcmeExceptions
, pero aún así no pueden estar seguros de si AcmeFileNotFoundError
ocurrió o no AcmePermissionDeniedError
.
Por lo tanto, si solo está interesado en una AcmeFileNotFoundError
, la solución es presentar una solicitud de función a los programadores de ACME y pedirles que implementen, declaren y documenten esa subclase de AcmeException
.
¿Entonces, para qué molestarse?
Por lo tanto, incluso con las excepciones marcadas, el compilador no puede obligar a los programadores a lanzar excepciones útiles . Todavía es solo una cuestión de la calidad de la API.
Como resultado, los idiomas sin excepciones marcadas no suelen ir mucho peor. Los programadores pueden verse tentados a lanzar instancias inespecíficas de una Error
clase general en lugar de una AcmeException
, pero si les importa la calidad de su API, aprenderán a presentar una, AcmeFileNotFoundError
después de todo.
En general, la especificación y la documentación de las excepciones no es muy diferente de la especificación y la documentación de, digamos, los métodos ordinarios. Esos, también, son una pregunta de diseño de la API, y si un programador olvidó implementar o exportar una característica ú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 la máquina virtual Java no tiene excepciones comprobadas; solo el compilador Java las comprueba, y los archivos de clase con declaraciones de excepción modificadas son compatibles en el tiempo de ejecución. La seguridad de Java VM no se mejora con las excepciones marcadas, solo el estilo de codificación.
He leído mucho sobre el manejo de excepciones, aunque (la mayoría de las veces) realmente no puedo decir que estoy contento o triste por la existencia de excepciones verificadas, esta es mi opinión: excepciones verificadas en código de bajo nivel (IO, networking , 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, me parece que es realmente molesto / difícil integrar varias API / bibliotecas bajo el mismo techo sin envolver todo el tiempo con muchas excepciones verificadas, pero por otro lado, en algún momento es útil / mejor ser forzado a atrapar 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 está completamente basada en excepciones no controladas. 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 tuvo que atrapar o volver a lanzar excepciones que no sabe cómo manejar, o que ni siquiera le importa (no confundirse con usted debe ignorar las excepciones), especialmente en el lado del cliente donde una sola clic podría lanzar 10 posibles excepciones (marcadas).
La versión actual (3ª) solo utiliza excepciones no controladas, y tiene un controlador de excepciones global que es responsable de manejar cualquier cosa no detectada. La API proporciona una forma de registrar los controladores de excepciones, que decidirá si una excepción se considera un error (la mayoría de las veces es así), 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 no se desea. Por supuesto, para resolver todo el hilo personalizado debe manejar el método run () con un try {...} catch (all).
ejecución del 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, Callable, Trabajador), que se encarga de 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í, también lo es para otros ... :)
Hemos visto algunas referencias al arquitecto jefe de C #.
Aquí hay un punto de vista alternativo de un tipo de Java sobre cuándo usar excepciones marcadas. Reconoce muchos de los aspectos negativos que otros han mencionado: Excepciones efectivas
Las buenas pruebas de que no se necesitan Excepciones marcadas son:
- Un montón de framework que hace algún trabajo para Java. Al igual que Spring, que envuelve la excepción JDBC a las excepciones no verificadas, envía mensajes al registro.
- Muchos de los idiomas que vinieron después de Java, incluso en la parte superior de la plataforma Java, no los usan
- Excepciones comprobadas, es una especie de predicción sobre cómo el cliente usaría el código que produce una excepción. Pero un desarrollador que escribe este código nunca sabría sobre el sistema y el negocio en el que trabaja el cliente de código. Como ejemplo, los métodos de Interfcace que obligan a lanzar una excepción comprobada. Hay 100 implementaciones en el sistema, 50 o incluso 90 de implementaciones no lanzan esta excepción, pero el cliente todavía 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 ellas). ¿Qué deberíamos 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, sentiría que debo manejar la excepción, lo haré.Puede que me olvide de eso, cierto, pero si uso TDD, todos mis pasos están cubiertos y sé lo que quiero.
- Otro ejemplo, cuando trabajo con E / S en java, me obliga a verificar todas las excepciones, ¿si el archivo no existe? ¿Qué debo hacer con eso? Si no existe, el sistema no irá 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, es 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 corrige Java). Si, finalmente, creo que el sistema debería mostrar un mensaje emergente al usuario final, lo mostraré, no es un problema.
Me alegré de que Java me ofreciera una opción sobre qué utilizar, al trabajar con libs de núcleo, como I / O. 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 deberían buscar un marco en la parte superior de Java o un idioma diferente. Como Scala, JRuby lo que sea. Muchos solo creen que SUN tenía razón.
Para intentar abordar solo la pregunta sin respuesta:
Si lanza subclases RuntimeException en lugar de subclases de excepción, ¿cómo sabe lo que se supone que debe capturar?
La pregunta contiene razonamiento especioso en mi humilde opinión. El hecho de que la API le diga lo que arroja no significa que se trate de la misma manera en todos los casos. Para decirlo de otra manera, las excepciones que debe capturar varían según el contexto en el que use el componente que lanza la excepción.
Por ejemplo:
Si estoy escribiendo un comprobador de conexión para una base de datos, o algo para verificar la validez de un usuario ingresado en XPath, entonces probablemente querría detectar e informar sobre todas las excepciones marcadas y no marcadas que se producen por 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é que se ejecute hasta la parte superior del subproceso de trabajo, omitir el resto de ese lote, registrar El problema (o enviarlo a un departamento de soporte para el diagnóstico) y dejar comentarios para que el usuario se ponga en contacto con el soporte.
Un problema con las excepciones comprobadas 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 comprobadas es que tienden a ser mal utilizadas. El ejemplo perfecto de esto está en java.sql.Connection
el close()
método de. Puede lanzar una SQLException
, aunque ya haya declarado explícitamente que ha terminado con la conexión. ¿Qué información podría cerrar () posiblemente transmitir que te importaría?
Usualmente, cuando cierro () una conexión *
, se ve algo como esto:
try {
conn.close();
} catch (SQLException ex) {
// Do nothing
}
Además, no me refiero a los diversos métodos de análisis y la excepción NumberFormatException ... TryParse de .NET, que no genera 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 tiene que detectar la SQLException debido a que es una excepción marcada.
Una cosa importante que nadie mencionó es cómo interfiere con las interfaces y las expresiones lambda.
Digamos que usted define a MyAppException extends Exception
. Es la excepción de nivel superior heredada por todas las excepciones lanzadas por su aplicación. Cada método declara throws MyAppException
que es un poco de matices, pero manejable. Un controlador de excepciones registra una excepción y notifica al usuario de alguna manera.
Todo se ve bien hasta que quiera implementar alguna interfaz que no sea suya. Obviamente, no declara la intención de lanzar MyApException
, por lo que el compilador no le permite lanzar la excepción desde allí.
Sin embargo, si su excepción se extiende RuntimeException
, no habrá problemas con las interfaces. Puede mencionar voluntariamente la excepción en JavaDoc si lo desea. Pero, aparte de eso, simplemente hace burbujas en cualquier cosa, atrapado en su capa de manejo de excepciones.
Anders habla sobre las trampas de las excepciones verificadas y por qué los dejó fuera de C # en el episodio 97 de la radio de Ingeniería de Software.
Artima http://www.artima.com/intv/handcuffs.html con uno de los arquitectos de .NET, Anders Hejlsberg, que cubre los argumentos en contra de las excepciones comprobadas. Una degustación corta:
La cláusula de lanzamientos, al menos la forma en que se implementa en Java, no necesariamente lo obliga a manejar las excepciones, pero si no las maneja, lo obliga a reconocer con precisión qué excepciones pueden pasar. Requiere que atrapes las excepciones declaradas o las pongas en tu propia cláusula de lanzamientos. Para evitar este requisito, la gente hace cosas ridículas. Por ejemplo, decoran cada método con "arroja Excepción". Eso acaba de derrotar completamente la característica, y acaba de hacer que el programador escriba más tonterías. Eso no ayuda a nadie.
Bueno, no se trata de mostrar un seguimiento de pila o estrellarse silenciosamente. Se trata de poder comunicar errores entre capas.
El problema con las excepciones marcadas es que alientan a las personas a tragar detalles importantes (a saber, la clase de excepción). Si elige no tragar ese detalle, entonces debe seguir agregando declaraciones de lanzamientos en toda la aplicación. Esto significa 1) que un nuevo tipo de excepción afectará a muchas firmas de funciones, y 2) puede perder una instancia específica de la excepción que realmente desea capturar (digamos que abre un archivo secundario para una función que escribe datos en un archivo). 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).
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 explotar throws
en firmas.
Por supuesto, ahora debemos especializar el manejo de errores en los niveles superiores, 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, cancele completamente". 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.
Esta es la razón por la que soy un gran fan del manejo de excepciones en Python. Solo puedes atrapar las cosas que quieres y / o puedes manejar. Todo lo demás brota como si lo hubieras hecho tú mismo (lo que has hecho de todos modos).
He encontrado, una y otra vez, y en todo el proyecto que mencioné, el manejo de excepciones se divide en 3 categorías:
- Atrapa y maneja una excepción específica . Esto es para implementar la lógica de reintento, por ejemplo.
- Atrapa 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 de los que no puedes hacer nada; solo un nivel superior sabe lo suficiente para manejarlo.
- Atrapa todo y muestra un mensaje de error. Por lo general, esto se encuentra en la raíz 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 que no sea de excepción (diálogo emergente, cálculo de referencias de un objeto de error de RPC, etc.).
Como ya lo ha dicho la gente, las excepciones comprobadas no existen en el código de bytes de Java. Son simplemente un mecanismo de compilación, no a diferencia de otras comprobaciones de sintaxis. Veo las excepciones marcadas como si el compilador se quejara de una condición redundante:if(true) { a; } b;
. Eso es útil, pero podría haberlo hecho a propósito, así que permítame ignorar sus advertencias.
El hecho es que no podrá obligar a cada programador a "hacer lo correcto" si hace cumplir las excepciones verificadas y todos los demás son daños colaterales que solo lo odian por la regla que estableció.
¡Arregla los programas malos por ahí! ¡No intentes arreglar el idioma para no permitirlos! Para la mayoría de las personas, "hacer algo acerca de una excepción" es simplemente decirle al usuario sobre esto. También puedo decirle al usuario acerca de una excepción no verificada, así que mantenga sus clases de excepción verificadas fuera de mi API.
Creo que esta es una excelente pregunta y no es para 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 lanzar 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 de Java central en general deberían verificarse si alguna vez podrían manejarse. Toma FileNotFoundException
o (mi favorito) InterruptedException
. Estas condiciones casi siempre deben manejarse específicamente (es decir, su reacción a una InterruptedException
no es lo mismo que su reacción a una IllegalArgumentException
). El hecho de que se verifiquen sus excepciones obliga a los desarrolladores a pensar si una condición es manejable o no. (Dicho esto, rara vez he visto InterruptedException
manejado correctamente!)
Una cosa más: a RuntimeException
no es siempre "donde un desarrollador se equivocó". Se genera una excepción de argumento ilegal cuando intenta crear un enum
uso valueOf
y no hay ninguno enum
de ese nombre. ¡Esto no es necesariamente un error del desarrollador!
De hecho, las excepciones comprobadas por un lado aumentan la robustez y la corrección de su programa (se ve obligado a hacer declaraciones correctas de sus interfaces; las excepciones que arroja el método son básicamente un tipo de retorno especial). Por otro lado, se enfrenta al problema de que, dado que las excepciones "burbujean", muy a menudo necesita cambiar muchos métodos (todos los llamantes y los llamadores de los llamantes, y así sucesivamente) cuando cambia las excepciones. método de tiros.
Las excepciones comprobadas en Java no resuelven el último problema; C # y VB.NET tiran al bebé con el agua del baño.
Un buen enfoque que toma el camino intermedio se describe en este documento de OOPSLA 2005 (o en el informe técnico relacionado ).
En resumen, le permite decir:, lo method g(x) throws like f(x)
que significa que g lanza todas las excepciones que f lanza. Voila, comprobó las excepciones sin el problema de los cambios en cascada.
Aunque es un documento académico, lo aliento a que lo lea (en parte), ya que explica bien cuáles son los beneficios y las desventajas de las excepciones comprobadas.
El artículo Effective Java Exceptions explica muy bien cuándo usar sin marcar y cuándo usar las excepciones marcadas. Aquí hay algunas citas de ese artículo para resaltar los puntos principales:
Contingencia: una condición esperada que exige una respuesta alternativa de un método que puede expresarse en términos del propósito previsto del método. La persona que llama al método espera este tipo de condiciones y tiene una estrategia para hacerles frente.
Error: Una condición no planificada que impide que un método logre su propósito previsto que no se puede describir sin hacer referencia a la implementación interna del método.
(SO no permite tablas, por lo que es posible que desee leer lo siguiente de la página original ...)
Contingencia
- Se considera como: Una parte del diseño.
- Se espera que suceda: Regularmente pero rara vez
- A quién le importa: el código anterior que invoca el método.
- Ejemplos: Modos de retorno alternativos.
- Mejor mapeo: una excepción marcada
Culpa
- Se considera que es: Una desagradable sorpresa.
- Se espera que suceda: nunca
- A quién le importa: las personas que necesitan solucionar el problema.
- Ejemplos: errores de programación, mal funcionamiento del hardware, errores de configuración, archivos faltantes, servidores no disponibles
- Mejor mapeo: una excepción sin marcar
El programador necesita conocer todas las excepciones que un método puede lanzar para usarlo correctamente. Por lo tanto, golpearlo en la cabeza con solo algunas de las excepciones no necesariamente ayuda a un programador descuidado a evitar errores.
El beneficio reducido se ve compensado por el costo oneroso (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 realmente confiable a menudo exige de manera inflexible un trabajo estricto del programador. Hay un cálculo de costo-beneficio, y la barra debe establecerse en un nivel alto para una verificación que lleva a un error de tiempo de compilación. Sería más útil si el IDE asumiera el rol de comunicar qué excepciones puede lanzar un método (incluyendo cuáles son inevitables). Aunque quizás no sería tan confiable sin declaraciones de excepción forzadas, la mayoría de las excepciones aún se declararán en la documentación, y la confiabilidad de una advertencia IDE no es tan importante.
Este no es un argumento contra el concepto puro de excepciones comprobadas, pero la jerarquía de clases que Java utiliza para ellas es un espectáculo anormal. Siempre llamamos a las cosas simplemente "excepciones", lo cual es correcto porque la especificación del lenguaje también las llama , pero ¿cómo se nombra y representa una excepción en el sistema de tipos?
¿Por la clase Exception
uno se imagina? Bueno, no, porque Exception
s son excepciones y, de la misma manera, las excepciones son Exception
s, a excepción de las excepciones que no son Exception
s, porque otras excepciones son realmente Error
s, que son el otro tipo de excepción, un tipo de excepción excepcional que nunca debería suceder, excepto cuando lo hace, y que nunca deberías 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 Exception
s ni Error
s, sino simplemente Throwable
excepciones.
¿Cuáles de estas son las excepciones "comprobadas"? Throwable
s son excepciones verificadas, excepto si también son Error
s, que son excepciones no verificadas, y luego están las Exception
s, que también son Throwable
s y son el tipo principal de excepción verificada, excepto que también hay una excepción, que es si son también RuntimeException
s, porque ese es el otro tipo de excepción no comprobada.
¿Para qué es RuntimeException
s? Bueno, como su nombre lo indica, son excepciones, como todas las Exception
s, y ocurren en tiempo de ejecución, como todas las excepciones en realidad, excepto que RuntimeException
s son excepcionales en comparación con otras Exception
s tiempo de ejecución porque se supone que no ocurren, excepto cuando cometes un error tonto, aunque RuntimeException
s nunca son Error
s, entonces son para cosas que son excepcionalmente erróneas pero que en realidad no son Error
s. Excepto por RuntimeErrorException
, que en realidad es una RuntimeException
por Error
s. 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 excepcional, ya que la documentación explica que es una "ocurrencia normal" y que esos por qué lo hicieron un tipo deError
.
De todos modos, ya que estamos dividiendo todas las excepciones a la mitad en Error
s (que son excepciones de ejecución excepcionales, por lo tanto sin marcar) ys Exception
(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 IllegalAccessError
y IllegalAccessException
, y InstantiationError
y InstantiationException
, NoSuchFieldError
y NoSuchFieldException
, NoSuchMethodError
y NoSuchMethodException
, y, y, y, ZipError
y ZipException
.
Excepto que incluso cuando se comprueba 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 presentarse como un UnexpectedException
, o un UnknownException
(que no está relacionado con UnknownError
, que es sólo para "excepciones serias"), 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 RuntimeException
excepción diseñada para permitirle lanzar el concepto de comprobación de excepciones por la ventana mediante el ajuste de las IOException
excepciones comprobadas causadas por errores de E / S (que no causan IOError
excepciones, aunque existe también) que son excepcionalmente difíciles de manejar, por lo que necesita que no se revisen.
Gracias java
He estado trabajando con varios desarrolladores en los últimos tres años en aplicaciones relativamente complejas. Tenemos una base de código que utiliza Excepciones marcadas con bastante frecuencia con el manejo adecuado de errores, y algunas otras que no lo hacen.
Hasta ahora, me ha resultado más fácil trabajar con el código base con las excepciones comprobadas. Cuando estoy usando la API de otra persona, es bueno que pueda ver exactamente qué tipo de condiciones de error puedo esperar cuando llamo el código y lo manejo correctamente, ya sea al iniciar sesión, mostrarlo o ignorarlo (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 para recuperar. Todas las excepciones de tiempo de ejecución se propagan 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 marcada que realmente no quiero manejar a un nivel específico, o que considero un error de lógica de programación, luego la envuelvo en una excepción RuntimeException y la dejo al máximo. 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 las excepciones marcadas, 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.
Esto es, por supuesto, una cuestión de preferencia y habilidad de 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 Verificación de excepciones, especialmente en proyectos grandes con muchos desarrolladores.
Inicialmente estuve de acuerdo con usted, ya que siempre he estado a favor de las excepciones controladas, y comencé a pensar por qué no me gusta no haber verificado las excepciones en .Net Pero luego me di cuenta de que no tengo hechos como excepciones comprobadas.
Para responder tu pregunta, sí, me gusta que mis programas muestren trazas de pila, preferiblemente las realmente feas. Quiero que la aplicación explote en un horrible montón de los mensajes de error más feos que puedas desear ver.
Y la razón es porque, si lo hace, tengo que arreglarlo, y tengo que arreglarlo de inmediato. Quiero saber de inmediato que hay un problema.
¿Cuántas veces manejas realmente las excepciones? No estoy hablando de atrapar excepciones, ¿estoy hablando de manejarlas? Es muy fácil escribir lo siguiente:
try {
thirdPartyMethod();
} catch(TPException e) {
// this should never happen
}
Y sé que puede decir que es una mala práctica, y que ''la respuesta'' es hacer algo con la excepción (déjeme adivinar, registrarlo?), Pero en el mundo real (tm), la mayoría de los programadores no lo hacen. eso.
Así que sí, no quiero detectar excepciones si no tengo que hacerlo, y quiero que mi programa explote espectacularmente cuando me equivoque. Silenciosamente fallar es el peor resultado posible.
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 puede conectar, no se encontró el archivo, etc.) y asegurarse de que los desarrolladores los manejaron.
Lo que nunca se incluyó en el concepto original fue forzar la declaración de una amplia gama de fallas sistémicas e irrecuperables. Estas fallas nunca fueron correctas para ser declaradas como excepciones comprobadas.
Los fallos son generalmente posibles en el código, y los contenedores EJB, web y Swing / AWT ya se encargan de esto al proporcionar un controlador de excepciones 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 las excepciones en tiempo de ejecución y comprobadas son funcionalmente equivalentes. No hay manejo o recuperación que puedan hacer las excepciones verificadas, que las excepciones de tiempo de ejecución no pueden.
El mayor argumento en contra de las excepciones "comprobadas" es que la mayoría de las excepciones no se pueden arreglar. El hecho simple es que no somos dueños del código / subsistema que se rompió. 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 las contingencias solucionables según el concepto original de "excepción comprobada", estos problemas de confiabilidad sistémica generalizada forzada, que en realidad no se pueden arreglar, deben ser ampliamente declarados.
La otra falla grave en el diseño de Java fue 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 comprobadas hacen poco, pero se interponen en el camino de esto.
Tenemos un problema obvio en Java de que se requieren miles de bloques try-catch de no hacer nada, con una proporción significativa (40% +) con errores de codificación. Casi ninguno de estos implementa un manejo o confiabilidad genuinos, pero impone una gran sobrecarga de codificación.
Por último, las "excepciones comprobadas" son bastante incompatibles con la programación funcional de FP.
Su insistencia en el "manejo inmediato" está en desacuerdo con las mejores prácticas de manejo de excepciones de "captura tardía" y cualquier estructura de PF que resuma bucles o flujo de control.
Muchas personas hablan de "manejar" las excepciones verificadas, pero están hablando a través de sus sombreros. Continuar después de una falla con datos nulos, incompletos o incorrectos para pretender el éxito no es manejar nada. Es mala práctica de ingeniería / fiabilidad de la forma más baja.
Falla limpiamente, es la estrategia correcta más básica para manejar una excepción. Revertir la transacción, registrar el error y notificar al usuario una respuesta de "falla" es una buena práctica, y lo más importante, evitar que se ingresen datos de negocios incorrectos en la base de datos.
Otras estrategias para el manejo de excepciones son "reintentar", "reconectar" o "saltar", 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 más preferible 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.
Mi redacción en c2.com aún no ha cambiado en su forma original: CheckedExceptionsAreIncompatibleWithVisitorPattern
En resumen:
El Patrón de visitante y sus parientes son una clase de interfaces donde el llamante indirecto y la implementación de la interfaz conocen una excepción, pero la interfaz y la persona que llama directa forman una biblioteca que no puede saber.
El supuesto fundamental de CheckedExceptions es que todas las excepciones declaradas se pueden lanzar desde cualquier punto que llame a un método con esa declaración. El VisitorPattern revela que esta suposición es defectuosa.
El resultado final de las excepciones comprobadas en casos como estos es un montón de código inútil que esencialmente elimina la restricción de excepción comprobada del compilador en el tiempo de ejecución.
En cuanto al problema subyacente:
Mi idea general es que el controlador de nivel superior debe interpretar la excepción y mostrar un mensaje de error adecuado. Casi siempre veo excepciones de E / S, excepciones de comunicación (por alguna razón las API se distinguen) o errores fatales (errores de programa o problemas graves en el servidor de respaldo), por lo que no debería ser demasiado difícil si permitimos un seguimiento de la pila para un grave problema del servidor
Ok ... Las excepciones marcadas no son ideales y tienen alguna advertencia, pero tienen un propósito. Al crear una API, hay casos específicos de fallas que son contractuales de esta API. Cuando en el contexto de un lenguaje fuertemente tipado estáticamente, como Java, si uno no usa excepciones marcadas, entonces debe confiar en la documentación y convención ad hoc para transmitir la posibilidad de error. Al hacerlo, se eliminan todos los beneficios que el compilador puede traer en el manejo del error y queda completamente a la buena voluntad de los programadores.
Por lo tanto, uno elimina la excepción marcada, como se hizo en C #, ¿cómo se puede transmitir mediante programación y estructura la posibilidad de error? ¿Cómo informar al código del cliente que tales y tales errores pueden ocurrir y deben ser tratados?
Escucho todo tipo de horrores cuando se trata de excepciones marcadas, son mal utilizadas, esto es cierto, pero también lo son las excepciones no controladas. Digo, espera unos cuantos años cuando las API tengan muchas capas de profundidad y estarás pidiendo la devolución de algún tipo de medio estructurado para transmitir fallas.
Tome el caso cuando se lanzó la excepción en algún lugar en la parte inferior de las capas de API y simplemente se aceleró porque nadie sabía que era posible que se produjera este error, aunque 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 hay 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 morir como una muerte horrible y dolorosa. Así que sí ... claro ... ok ... usted crea una API para algo y carga un archivo en algún momento ... Yo, como usuario de dicha API, solo puedo responder ... "¿Quién diablos es usted para decidir cuándo mi programa debe bloquearse! " Claro, dada la elección de las excepciones y no dejar rastro o la EletroFlabbingChunkFluxManifoldChuggingException con un rastro de pila más profundo que la trinchera de Marianna, tomaría esta última sin la menor duda, pero esto significa que es la forma conveniente de lidiar con la excepción ? ¿No podemos estar en algún lugar en el medio, donde la excepción sería refundida y envuelta 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 lidiar con las excepciones, muchas personas no quieren lidiar con las excepciones. Las excepciones comprobadas me obligan a lidiar con ellas, por lo tanto odio la excepción comprobada" relegarlo al abismo del infierno de Goto es una tontería y carece de rigor y visión.
Si eliminamos la excepción marcada, también podríamos eliminar el tipo de retorno para las funciones y siempre devolver una variable de "cualquier tipo" ... Eso haría la vida mucho más simple ahora, ¿no es así?