El caso contra las excepciones comprobadas

456

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? He tenido numerosas conversaciones, leí cosas en blogs, leí lo que Bruce Eckel tenía que decir (la primera persona que vi habló 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 "no nos gustan las excepciones marcadas" y todavía no puedo verlo.

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

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

  • Error es para cosas que nunca deben ser atrapadas (VM tiene alergia al maní y alguien le arrojó un frasco de maní)
  • RuntimeException es para cosas que el programador hizo mal (el programador 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 ha alcanzado el límite de manejo de archivos para el proceso y no puede abrir más archivos)
  • Throwable es simplemente el padre de todos los tipos de excepción.

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

Otro argumento común que escucho es que las excepciones marcadas hacen que sea más difícil refactorizar el código.

Para el argumento "todo lo que voy a hacer es salir", digo que incluso si está saliendo necesita mostrar un mensaje de error razonable. Si solo cometes errores de manejo, tus 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 un IOException, elIOException debería transformarse en una excepción que sea más adecuada para lo que está sucediendo.

No tengo problemas para envolver Main con catch(Exception)(o en algunos casoscatch(Throwable) para asegurarme de que el programa pueda salir con gracia, pero siempre capto las excepciones específicas que necesito. Hacer eso me permite, al menos, mostrar un archivo apropiado mensaje de error.

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

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

Si la respuesta es catch, Exceptionentonces también está lidiando con los errores del programador de la misma manera que 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 solo captura 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 atraparla? Eso me parece muy peligroso.

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?

Editar: no estoy buscando consejos sobre cuándo usar ninguno 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é atrapan una excepción y luego vuelven a lanzar un RuntimeExceptionlanzamiento en lugar de agregar su método Quiero entender la motivación para no gustarme las excepciones marcadas.

TofuBeer
fuente
46
No creo que sea completamente subjetivo: es una característica del lenguaje que fue diseñada para tener un uso específico, en lugar de que todos decidan para qué son. Y no es especialmente argumentativo, aborda de antemano refutaciones específicas que las personas podrían haber inventado fácilmente.
Gareth
66
Venga. Visto como una característica del lenguaje, este tema ha sido y puede abordarse de manera objetiva.
Kurt Schelfthout
66
@cletus "respondiendo tu propia pregunta" si tuviera la respuesta, ¡no hubiera hecho la pregunta!
TofuBeer
8
Gran pregunta En C ++ no hay excepciones marcadas en absoluto, y en mi opinión, hace que la característica de excepción sea inutilizable. Terminas en una situación en la que debes atrapar cada llamada de función que realizas, porque simplemente no sabes si podría arrojar algo.
Dimitri C.
44
El argumento más sólido que conozco para las excepciones comprobadas es que originalmente no estaban allí en Java, y que cuando fueron introducidas descubrieron cientos de errores en el JDK. Esto es algo anterior a Java 1.0. Yo personalmente no estaría sin ellos, y estoy en desacuerdo violentamente con Bruce Eckel y otros en esto.
Marqués de Lorne

Respuestas:

277

Creo que leí la misma entrevista de Bruce Eckel que tú hiciste, y siempre me molestó. De hecho, el entrevistado hizo el argumento (si esta es realmente 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:

"Las excepciones marcadas son malas porque los programadores simplemente abusan de ellas al atraparlas siempre y descartarlas, lo que lleva a que se oculten e ignoren problemas que de otro modo se presentarían al usuario".

Por "presentado de otra manera al usuario" quiero decir que si usa una excepción de tiempo de ejecución, el programador perezoso simplemente lo ignorará (en lugar de atraparlo con un bloque catch vacío) y el usuario lo verá.

El resumen del resumen del argumento es que "los 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 anulaciones de operador 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 el uso adecuado de ellas permite que el programador API brinde un gran beneficio al programador cliente API.

Ahora, el programador de la API debe tener cuidado de no lanzar excepciones comprobadas por todas partes, o simplemente molestarán al programador del cliente. El programador de clientes muy flojo 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 archivo abierto. Cada lenguaje de programación en la historia de los lenguajes (al menos en los sistemas de archivos) tiene una API en algún lugar que le permite abrir un archivo. Y cada programador cliente que use esta API sabe que tiene que lidiar con el caso de que el archivo que está intentando abrir no existe. Permítanme reformular eso: cada 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 ayudarlos a saber que deben lidiar con eso solo comentando o pueden insistir en que el cliente lo haga ?

En C el idioma es algo así 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, comienzas con

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

y aprender 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) que puede usar con un protocolo claramente definido para cada uno.

Ese protocolo claramente definido generalmente se define mediante una firma de método. Aquí fopen requiere que le pase una cadena (o un char * en el caso de C). Si le das algo más, obtienes un error en tiempo de 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 intentas llamar al equivalente defopen() en algunos idiomas sin asignarlo a una variable, también obtendrá un error en tiempo de compilación (solo puede hacerlo con las funciones nulas).

El punto que estoy tratando de hacer es que: en un lenguaje de tipo estático, el programador de API alienta al cliente a usar la API correctamente al evitar que se compile su código de cliente si comete algún error obvio.

(En un lenguaje escrito dinámicamente, como Ruby, puede pasar cualquier cosa, digamos un flotante, como el nombre del archivo, y se compilará. ¿Por qué molestar al usuario con excepciones marcadas si ni siquiera va a controlar los argumentos del método? los argumentos que se hacen aquí se aplican solo a los idiomas de tipo estático).

Entonces, ¿qué pasa con las excepciones marcadas?

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 trampa? Aquí está la firma para ese método API:

public FileInputStream(String name)
                throws FileNotFoundException

Tenga en cuenta que FileNotFoundExceptiones una excepción marcada .

El programador de API le dice esto: "Puede usar este constructor para crear un nuevo FileInputStream pero usted

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

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

La clave es básicamente lo que la pregunta dice como "Cosas que están fuera del control del programador". Mi primer pensamiento fue que él / ella quiere decir cosas que están fuera de la API control de los programadores . 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 muy bien el punto. El programador de API sabe que puede darles un nombre de archivo que no existe en el momento en que se llama a la API, y que no podrán devolverle lo que desea, pero tendrá que lanzar una excepción. También saben que esto sucederá con bastante regularidad 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 tiempo de ejecución por razones que están 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 llames y es mejor que lo trates.

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 sentir la tentación de lanzar una excepción marcada y obligar al cliente a lidiar con ella:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

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

Este es un mal uso de las excepciones marcadas. El código del cliente estará lleno de llamadas para recuperar datos de fila, cada uno de los cuales tendrá que usar un try / catch, ¿y para qué? ¿Van a informar al usuario que se buscó la fila incorrecta? Probablemente no, porque cualquiera que sea la interfaz de usuario que rodea mi vista de tabla, no debería permitir que el usuario entre en un estado donde 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 manejarlo con una excepción de tiempo de ejecución como un IllegalArgumentException.

Con una excepción marcada getRowData, este es claramente un caso que llevará al programador perezoso de Hejlsberg simplemente agregando capturas vacías. Cuando eso suceda, los valores de fila ilegales no serán obvios incluso para la depuración del probador o del desarrollador del cliente, sino que conducirán a errores que son difíciles de identificar la fuente. Los cohetes Arianne explotarán después del lanzamiento.

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

Supongo que no es una ciencia exacta y supongo que subyace y quizás justifica en cierta medida el argumento de Hejlsberg. Pero no estoy 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 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 de control tanto de la API como del programador del cliente. Esto tiene que ver con qué tan abierto o cerrado es el sistema. En una IU restringida donde el programador del 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 donde cualquier número de usuarios / aplicaciones puede agregar y eliminar archivos (un sistema abierto), es concebible que el archivo que solicita el cliente se haya eliminado sin su conocimiento, por lo que se espera que lo manejen. .

  2. Ubicuidad:

    Las excepciones marcadas no deben usarse en una llamada API que el cliente realiza con frecuencia. Con frecuencia me refiero a muchos lugares en el código del cliente, no con frecuencia en el tiempo. Por lo tanto, un código de cliente no tiende a intentar abrir mucho el mismo archivo, pero mi vista de tabla se RowDataextiende desde diferentes métodos. En particular, voy a escribir mucho código como

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

    y será doloroso tener que envolver en try / catch cada vez.

  3. Informar al usuario:

    Deben usarse excepciones marcadas en los casos en que pueda imaginar que se presentará un mensaje de error útil al usuario final. Este es el "¿y qué harás cuando ocurra?" 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 hacer 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 sin culpa del usuario, realmente no hay información útil que pueda proporcionarle. 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 estar usando una excepción marcada.

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

Lo siento, no quise hacer esto tan largo y ondulado. Déjame terminar con dos sugerencias:

R: Programadores de API: use excepciones marcadas con moderación para preservar su utilidad. En caso de duda, use una excepción no marcada.

B: Programadores de clientes: habitúese a crear una excepción envuelta (google it) al principio de su desarrollo. JDK 1.4 y posterior proporcionan un constructor RuntimeExceptionpara esto, pero también puede crear fácilmente el suyo propio. Aquí está el constructor:

public RuntimeException(Throwable cause)

Luego, habitúese cuando tenga que manejar una excepción marcada y se sienta perezoso (o cree que el programador de API fue demasiado celoso al usar la excepción marcada en primer lugar), no se trague 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 úsela cuando se sienta flojo. De esta manera, si realmente necesita manejar la excepción marcada, se verá obligado a volver y tratarla después de ver el problema en tiempo de ejecución. Porque, créeme (y Anders Hejlsberg), nunca volverás a ese TODO en tu

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
Ruibarbo
fuente
110
Abrir archivos es en realidad un buen ejemplo de excepciones comprobadas totalmente contraproducentes. Debido a que la mayoría de las veces el código que abre el archivo NO puede hacer nada útil sobre la excepción, es mejor hacer varias capas en la pila de llamadas. La excepción marcada lo obliga a saturar sus firmas de método solo para que pueda hacer lo que hubiera hecho de todos modos: manejar la excepción en el lugar donde tenga más sentido hacerlo.
Michael Borgwardt
116
@Michael: Estuvo de acuerdo en que generalmente puede manejar estas excepciones de IO varios niveles más arriba, pero no escucho que niegue que, como cliente API, tenga que anticiparlas. Por esta razón, creo que las excepciones marcadas son apropiadas. Sí, vas a tener que declarar "tiros" en cada pila de llamadas de método, pero no estoy de acuerdo con que esto sea desorden. Es información declarativa útil en sus firmas de método. Estás diciendo: mis métodos de bajo nivel pueden encontrarse con un archivo perdido, pero te dejarán el manejo. No veo nada que perder y solo un código bueno y limpio que obtener
Ruibarbo
23
@ Ruibarbo: +1, respuesta muy interesante, muestra argumentos para ambos lados. Excelente. Acerca de su último comentario: tenga en cuenta que declarar lanzamientos en cada método en la pila de llamadas no siempre es posible, especialmente si está implementando una interfaz en el camino.
R. Martinho Fernandes el
8
Este es un argumento muy fuerte (y estoy de acuerdo con él en general), pero hay un caso en el que no funciona: devoluciones de llamada genéricas. Si tengo una biblioteca que llama a un código definido por el usuario, no hay forma (que yo sepa, en Java) para que esa biblioteca propague excepciones comprobadas del código de usuario que llama al código de usuario que lo llama. Este problema también se extiende a otras partes del sistema de tipos de muchos lenguajes tipados estáticamente que no admiten tipos genéricos adecuados. Esta respuesta se mejoraría con una mención de esto (y tal vez una respuesta).
Mankarse
77
Mal @ Ruibarbo. Las excepciones marcadas estaban destinadas a "contingencias" que podían y debían manejarse, pero siempre eran incompatibles con las mejores prácticas de "lanzar temprano, atrapar tarde". Abrir un archivo es un ejemplo escogido, que se puede manejar. La mayoría de las IOException (por ejemplo, escribir un byte), SQLException, RemoteException son imposibles de manejar de manera significativa. La falla o el reintento deben estar en el nivel de "negocio" o "solicitud", y las excepciones comprobadas, como se usan en las bibliotecas de Java, son un error que lo dificulta. literatejava.com/exceptions/…
Thomas W
184

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

La idea de las excepciones es que un error arrojado en algún lugar de la cadena de llamadas puede aparecer y ser manejado por un código más arriba, sin que el código intervenido tenga que preocuparse por ello. Las excepciones marcadas, por otro lado, requieren cada nivel de código entre el lanzador y el receptor para declarar que conocen todas las formas de excepción que pueden pasar por ellas. Esto es realmente poco diferente en la práctica si las excepciones marcadas fueran simplemente valores de retorno especiales que la persona que llama tenía que verificar. ej. [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 hacer 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 uso indebido de excepciones comprobadas para condiciones excepcionales reales que es muy posible que desee alcanzar varios niveles. ¿Por qué IOException no es una RuntimeException? En cualquier otro idioma, puedo permitir que ocurra una excepción de IO, y si no hago nada para manejarla, mi aplicación se detendrá y obtendré un útil seguimiento de la pila para ver. Esto es lo mejor que puede pasar.

Tal vez dos métodos del ejemplo que desea capturar todas las IOExceptions de todo el proceso de escritura para transmitir, 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 por sí mismos no hacen IO. Tales métodos no deberían necesitar saber sobre el manejo de excepciones; tener que agregar excepciones a sus firmas:

  1. aumenta innecesariamente el acoplamiento;
  2. hace que las firmas de la interfaz sean muy frágiles de 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 Excepción', 'captura (Excepción e) {}', o ajusta todo en una RuntimeException (lo que hace que la depuración sea más difícil).

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

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

Cuando tiene que abarrotar su código con una ridícula basura como esta, no es de extrañar que las excepciones verificadas reciban un montón de odio, a pesar de que en realidad esto es simplemente un diseño API pobre.

Otro efecto negativo particular es en 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 se devuelva 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 repetitiva para manejar excepciones.

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

bobince
fuente
14
En su ejemplo de código, sería mejor encadenar las excepciones para que se pueda encontrar la causa original al leer los registros: throw CanNeverHappenException (e);
Esko Luontola
55
Estoy en desacuerdo. Las excepciones, marcadas o no, son condiciones excepcionales. Ejemplo: un método que recupera un objeto a través de HTTP. El valor de retorno es el objeto o nada, todas las cosas que pueden salir mal son excepcionales. Tratarlos como valores de retorno como se hace en C solo conduce a la confusión y al diseño deficiente.
Señor Smith
15
@Mister: Lo que digo es que las excepciones marcadas como implementadas en Java se comportan, en la práctica, más como valores de retorno como en C que como las 'excepciones' tradicionales que podríamos reconocer de C ++ y otros lenguajes pre-Java. Y esa OMI, de hecho, esto conduce a la confusión y al mal diseño.
bobince
99
Acuerde que el uso indebido de las bibliotecas estándar de las excepciones marcadas definitivamente se agregó a la confusión y al mal comportamiento de captura. Y, a menudo, es solo debido a una documentación deficiente, por ejemplo, un método de desconexión como desconexión () que arroja IOException cuando "ocurre algún otro error de E / S". Bueno, me estaba desconectando! ¿Estoy filtrando un identificador u otro recurso? ¿Necesito volver a intentarlo? Sin saber por qué sucedió, no puedo deducir la acción que debo tomar, por lo que tengo que adivinar si debo tragarlo, volver a intentarlo o pagar la fianza.
charstar
14
+1 para "Valores de retorno alternativos de API". Una forma interesante de ver las excepciones marcadas.
Zsolt Török
75

En lugar de repetir todas las (muchas) razones contra las excepciones comprobadas, elegiré solo una. Perdí la cuenta de la cantidad de veces que escribí este bloque de código:

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

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

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

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

¿Por qué? Porque alguien tenía que lidiar con eso y era flojo. ¿Estaba mal? Por supuesto. ¿Pasa? Absolutamente. ¿Qué pasaría si esta fuera una excepción no verificada? La aplicación simplemente habría muerto (lo cual es preferible a tragar una excepción).

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

Ok, supongo que fueron tres razones.

cletus
fuente
44
Pero si una excepción se detecta correctamente, puede informar al usuario, realizar otras tareas (¿registrar?) Y salir de la aplicación de forma controlada. Estoy de acuerdo en que algunas partes de la API podrían haberse diseñado mejor. Y por la floja razón del programador, bueno, creo que como programador eres 100% responsable de tu código.
Señor Smith
3
tenga en cuenta que try-catch-rethrow le permite especificar un mensaje; por lo general, lo uso para agregar información sobre el contenido de las variables de estado. Un ejemplo frecuente es que IOExceptions agregue absolutePathName () del archivo en cuestión.
Thorbjørn Ravn Andersen
15
Creo que los IDEs como Eclipse tienen mucho que culpar por la cantidad de veces que has visto el bloque de captura vacío. Realmente, deberían volver a lanzar por defecto.
artbristol
12
"El 99% de las veces no puedo hacer nada al respecto" - mal, puede mostrarle al usuario un mensaje que dice "No se pudo conectar al servidor" o "El dispositivo IO falló", en lugar de dejar que la aplicación se bloquee debido a un pequeño problema de red. Ambos ejemplos son artes del trabajo de malos programadores. Deberías estar atacando a los programadores malos y no verificando las excepciones. Es como atacar a la insulina por no ayudarme con mi diabetes cuando la uso como aderezo para ensaladas.
AxiomaticNexus
2
@YasmaniLlanes No siempre puedes hacer estas cosas. A veces tienes una interfaz a la que adherirte. Y esto es especialmente cierto cuando diseñas buenas API mantenibles porque no puedes comenzar a lanzar efectos secundarios por todas partes. Tanto eso como la complejidad que presentará te morderán severamente a gran escala. Entonces, sí, el 99% del tiempo, no hay nada que hacer al respecto.
MasterMastic
50

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

Mi principal problema con excepciones comprobadas es que arruinan el polimorfismo. Es imposible hacer que jueguen bien con interfaces polimórficas.

Toma el buen viejo Java List interfaz de . Tenemos implementaciones comunes en memoria como ArrayListy LinkedList. También tenemos la clase esqueléticaAbstractList 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 WidgetListclase de ejemplo lee algunos objetos de tipo de tamaño fijo Widget(no mostrados) 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 el familiar List interfaz , puede recuperar elementos ( list.get(123)) o iterar una lista ( for (Widget w : list) ...) sin necesidad de saber sobre WidgetListsí mismo. Se puede pasar esta lista a cualquier método estándar que use listas genéricas, o envolverla en unCollections.synchronizedList . El código que lo utiliza no necesita saber ni preocuparse si los "Widgets" están formados en el acto, provienen de una matriz, o si se leen de un archivo, una base de datos, o de toda la red, o de un relé subespacial futuro. Todavía funcionará correctamente porque elList interfaz está implementada correctamente.

Excepto que no lo es. La clase anterior no se compila porque los métodos de acceso a archivos pueden generar una IOExceptionexcepción marcada que debe "capturar o especificar". usted puede especificarlo como lanzado ; el compilador no lo permitirá porque eso violaría el contrato delList interfaz. Y no hay una forma útil de WidgetListmanejar la excepción (como lo expondré más adelante).

Aparentemente, lo único que hay que hacer es atrapar y volver a lanzar excepciones marcadas como alguna excepción no marcada:

@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 un UncheckedIOException clase para exactamente este caso: para capturar y volver a lanzarIOException s a través de los límites del método polimórfico. ¡ demuestra mi punto!))

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

Encuentro que la Runnableinterfaz ubicua a 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 abarrotar el código al atraparlo y volverlo a lanzar como a RuntimeException.

En realidad, puede lanzar excepciones marcadas no declaradas si recurre a hacks. La JVM, en tiempo de ejecución, no se preocupa por las reglas de excepción comprobadas, 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;
    }
}

¡Hurra! Usando esto, podemos lanzar una excepción marcada a cualquier profundidad de la pila sin declararla, sin envolverla RuntimeExceptiony sin saturar la traza 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 comprobadas es que el compilador se niega a permitirle atrapar una excepción comprobada si, en su opinión errónea, no podría haberse lanzado. (Las excepciones no marcadas no tienen esta regla). Para detectar la excepción arrojada 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, sigue siendo un poco más simple que el código para extraer una excepción marcada que estaba envuelta en un RuntimeException.

Afortunadamente, la throw t;declaración es legal aquí, aunque el tipo det está marcado, gracias a una regla agregada en Java 7 sobre volver a lanzar excepciones atrapadas.


Cuando las excepciones verificadas cumplen con el polimorfismo, el caso opuesto también es un problema: cuando un método se especifica como potencialmente lanzando una excepción marcada, pero una implementación anulada no lo hace. Por ejemplo, la clase abstracta OutputStream's writemétodos, todos ellos especifican throws IOException. ByteArrayOutputStreames una subclase que escribe en una matriz en memoria en lugar de una fuente de E / S verdadera. Sus writemétodos anulados no pueden causar IOExceptions, por lo que no tienen throwscláusula, y puede llamarlos sin preocuparse por el requisito de capturar o especificar.

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 resultados: archivos, bases de datos, la red, etc. Y matrices en memoria. Sin embargo, con una matriz en memoria, hay un requisito espurio para manejar una excepción que en realidad no puede suceder:

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

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

¡Pero espera, las excepciones marcadas son realmente tan molestas que ni siquiera te permitirán hacer lo contrario! Imagine que actualmente la captura de cualquier IOExceptionlanzada por writelas llamadas en unaOutputStream , pero deseas cambiar el tipo declarado de la variable a a ByteArrayOutputStream, el compilador te reprenderá por tratar de atrapar una excepción marcada que dice que no se puede lanzar.

Esa regla causa algunos problemas absurdos. Por ejemplo, uno de los tres writemétodos de OutputStreamestá no anulado por ByteArrayOutputStream. Específicamente, write(byte[] data)es un método de conveniencia que escribe la matriz completa llamando write(byte[] data, int offset, int length)con un desplazamiento de 0 y la longitud de la matriz. ByteArrayOutputStreamanula 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 throwscláusula no deseada . Tal vez fue un descuido en el diseño de ByteArrayOutputStream, 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 se lanzará!

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 marcada, el compilador ahora se quejará de la existencia del local tryy los catchbloques. Así que tengo que comentarlos también, y ahora al editar el código dentro, el IDE sangrará al nivel incorrecto porque el {y }se comentan. Gah! Es una pequeña queja, pero parece que lo único que hacen las excepciones comprobadas es causar problemas.


Ya casi termino. Mi frustración final con las excepciones marcadas 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 competente específico para la aplicación que puede informar al usuario del problema y / o finalizar o volver a intentar la operación según corresponda. Solo un controlador 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 desenfrenado 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 va a explotar en un momento, momento en el cual el objeto de excepción original se habrá ido. Este idioma no es mejor que el 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 vago como e.printStackTrace();y todavía sigue 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ífico de la aplicación, por lo que esto 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 capturado del Thread ). Entonces, en la mayoría de los lugares, todo lo que necesita hacer es lanzar la excepción más arriba en la pila . Ej throw e;. Las excepciones marcadas solo se interponen en el camino.

Estoy seguro de que las excepciones comprobadas sonaban como una buena idea cuando se diseñó el idioma, pero en la práctica he descubierto que son molestas y no ofrecen ningún beneficio.

Boann
fuente
Para su método de tamaño con WidgetList, almacenaría en caché el tamaño en una variable y lo establecería en el constructor. El constructor es libre de lanzar una excepción. Sin embargo, esto no funcionará si el archivo cambia mientras usa la WidgetList, lo que probablemente sería malo si lo hiciera.
TofuBeer
2
SomeStupidExceptionOmgWhoCares bien a alguien le importó lo suficiente como para lanzarlo. Entonces, o nunca debería haber sido arrojado (mal diseño) o realmente debería manejarlo. Lo mismo es cierto de la mala implementación de una clase anterior a la 1.0 (el flujo de salida de la matriz de bytes) donde el diseño era, lamentablemente, malo.
TofuBeer
2
El idioma apropiado habría sido una directiva que capturaría cualquier excepción específica lanzada por llamadas de subrutina anidadas y las volvería a arrojar envueltas en un RuntimeException. Tenga en cuenta que una rutina podría declararse simultáneamente throws IOExceptiony, sin embargo, también especificar que cualquier IOExceptionllamada lanzada desde una llamada anidada debe considerarse inesperada y ajustada.
supercat
15
Soy un desarrollador profesional de C # con algo de experiencia en Java que se topó con esta publicación. Estoy desconcertado de por qué alguien apoyaría este comportamiento extraño. En .NET si quiero atrapar un tipo específico de excepción, puedo atrapar eso. Si solo quiero dejar que se lance la pila, no hay nada que hacer. Desearía que Java no fuera tan peculiar. :)
aikeru
Con respecto a "a veces comentaré una llamada de método temporalmente", aprendí a usar if (false)para esto. Evita el problema de la cláusula de lanzamiento y la advertencia me ayuda a navegar más rápido. +++ Dicho esto, estoy de acuerdo con todo lo que escribiste. Las excepciones marcadas tienen algún valor, pero este valor es insignificante en comparación con su costo. Casi siempre se interponen en el camino.
maaartinus
45

Bueno, no se trata de mostrar un seguimiento de pila o de un bloqueo silencioso. Se trata de poder comunicar errores entre capas.

El problema con las excepciones marcadas es que alientan a las personas a tragar detalles importantes (es decir, la clase de excepción). Si elige no tragarse ese detalle, debe seguir agregando declaraciones de lanzamiento en toda su aplicación. Esto significa 1) que un nuevo tipo de excepción afectará a muchas firmas de funciones, y 2) puede omitir una instancia específica de la excepción que realmente desea (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).

En realidad estoy lidiando con esta situación ahora en una aplicación. Reenvasamos casi excepciones como AppSpecificException. Esto hizo que las firmas fueran realmente limpias y no tuvimos que preocuparnos por explotarthrows en las firmas.

Por supuesto, ahora necesitamos especializar el manejo de errores en los niveles más altos, implementar 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, aborte 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 fanático del manejo de excepciones en python. Puede atrapar solo las cosas que desea y / o puede manejar. Todo lo demás brota como si lo volvieras a ti mismo (lo que has hecho de todos modos).

He encontrado, una y otra vez, y durante todo el 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í generalmente es el registro, y generalmente es un mensaje trillado como "No se puede abrir $ filename". Estos son errores sobre los que no puede hacer nada; solo un nivel superior sabe lo suficiente como para manejarlo.
  3. Captura todo y muestra un mensaje de error. Esto suele estar en la raíz de un despachador, y todo lo que hace es asegurarse de que puede comunicar el error a la persona que llama a través de un mecanismo sin excepción (diálogo emergente, cálculo de un objeto de error RPC, etc.).
Richard Levasseur
fuente
55
Podría haber hecho subclases específicas de AppSpecificException para permitir la separación mientras se mantienen las firmas del método simple.
Thorbjørn Ravn Andersen
1
También una adición muy importante al elemento 2, es que le permite AGREGAR INFORMACIÓN a la excepción capturada (por ejemplo, anidando en una RuntimeException). Es mucho, mucho mejor tener el nombre del archivo que no se encuentra en el seguimiento de la pila, que oculto en el fondo de un archivo de registro.
Thorbjørn Ravn Andersen
1
Básicamente su argumento es "Administrar excepciones es agotador, así que prefiero no lidiar con eso". A medida que surge la excepción, pierde sentido y la creación de contexto es prácticamente inútil. Como diseñador de una API, debe dejar claro desde el punto de vista contractual qué se puede esperar cuando las cosas salen mal, si mi programa falla porque no me informaron que esta o aquella excepción puede "surgir", entonces usted, como diseñador, falló y Como resultado de su falla, mi sistema no es tan estable como puede ser.
Newtopian
44
Eso no es lo que estoy diciendo en absoluto. Tu última oración en realidad está de acuerdo conmigo. Si todo está envuelto en AppSpecificException, entonces no aparece (y se pierde el significado / contexto), y, sí, el cliente API no está siendo informado; esto es exactamente lo que sucede con las excepciones marcadas (ya que están en Java) , porque la gente no quiere ocuparse de funciones con muchas throwsdeclaraciones.
Richard Levasseur
66
@Newtopian: las excepciones solo se pueden manejar en gran medida en el nivel de "negocio" o "solicitud". Tiene sentido fallar o volver a intentar con granularidad grande, no por cada pequeña falla potencial. Por esta razón, la mejor práctica de manejo de excepciones se resume como "lanzar temprano, atrapar tarde". Las excepciones marcadas hacen que sea más difícil administrar la confiabilidad en el nivel correcto y alientan un gran número de bloques de captura mal codificados. literatejava.com/exceptions/…
Thomas W
24

SNR

En primer lugar, las excepciones marcadas disminuyen la "relación señal / ruido" para el código. Anders Hejlsberg también habla sobre programación imperativa vs 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 UI 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 verificadas comienzan a ser realmente molestas e inútiles.

Jail Break

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

@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 ha sido detectada pero no manejada y el diseño por contrato (en términos de excepciones verificadas) se ha desglosado.

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.

Ya escribí un blog sobre esto anteriormente .

Luke Quinane
fuente
3
En el ejemplo, tanto Java como C # solo permiten que las excepciones se propaguen sin controlarlas (Java a través de IllegalStateException). La diferencia es que es posible que desee manejar una FileNotFoundException pero es poco probable que sea útil manejar InvocationTargetException o InterruptedException.
Luke Quinane el
3
Y en la forma C #, ¿cómo sé que puede ocurrir la excepción de E / S? Además, nunca lanzaría una excepción de la ejecución ... Considero que abusar del manejo de excepciones. Lo siento, pero por esa parte de tu código, todavía puedo ver tu lado.
TofuBeer 01 de
77
Estamos llegando allí :-) Entonces, con cada nueva versión de una API, ¿tiene que revisar todas sus llamadas y buscar nuevas excepciones que puedan ocurrir? Esto puede suceder fácilmente con las API internas de una empresa, ya que no tienen que preocuparse por la compatibilidad con versiones anteriores.
TofuBeer 02 de
3
¿Quiso decir disminuir la relación señal / ruido?
neo2862
3
@TofuBeer ¿No se ve obligado a actualizar su código después de que la interfaz de una API subyacente haya cambiado algo bueno? Si solo hubiera tenido excepciones no marcadas allí, habría terminado con un programa roto / incompleto sin saberlo.
Francois Bourgeois
23

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

La cláusula throws, al menos la forma en que se implementa en Java, no necesariamente te obliga a manejar las excepciones, pero si no las manejas, te obliga a reconocer con precisión qué excepciones podrían pasar. Requiere que atrape las excepciones declaradas o las ponga en su propia cláusula de lanzamientos. Para evitar este requisito, las personas hacen cosas ridículas. Por ejemplo, decoran cada método con "arroja la excepción". Eso simplemente derrota por completo la función, y acabas de hacer que el programador escriba más basura. Eso no ayuda a nadie.

Le Dude
fuente
2
De hecho, el arquitecto jefe.
Trampa
18
He leído eso, para mí su argumento se reduce a "hay malos programadores por ahí".
TofuBeer 05 de
66
TofuBeer, en absoluto. El punto es que muchas veces no sabes qué hacer con la excepción que arroja el método llamado, y ni siquiera se menciona el caso que realmente te interesa. Abre un archivo, obtiene una excepción IO, por ejemplo ... ese no es mi problema, así que lo vomito. Pero el método de llamada de nivel superior solo querrá detener el procesamiento e informar al usuario que hay un problema desconocido. La excepción marcada no ayudó en absoluto. Fue una de un millón de cosas extrañas que pueden suceder.
Dan Rosenstark
44
@yar, si no te gusta la excepción marcada, haz un "lanzar nueva RuntimeException (" no esperábamos esto al hacer Foo.bar () ", e)" y listo.
Thorbjørn Ravn Andersen
44
@ ThorbjørnRavnAndersen: Una debilidad fundamental del diseño en Java, que desafortunadamente copió .net, es que usa el tipo de excepción como el medio principal para decidir si se debe actuar, y el medio principal para indicar el tipo general de cosas. eso salió mal, cuando en realidad los dos problemas son en gran medida ortogonales. Lo que importa no es lo que salió mal, sino en qué estado se encuentran los objetos. Además, .net y Java asumen por defecto que actuar y resolver una excepción son generalmente lo mismo, cuando en realidad a menudo son diferentes.
supercat
20

Inicialmente estuve de acuerdo con usted, ya que siempre he estado a favor de las excepciones marcadas, y comencé a pensar por qué no me gusta no haber marcado las excepciones en .Net. Pero luego me di cuenta de que no actúo como excepciones marcadas.

Para responder a su pregunta, sí, me gusta que mis programas muestren rastros de pila, preferiblemente los más 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 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 detectar excepciones, estoy hablando de manejarlas. Es muy 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, ¿registrarlo?), Pero en el mundo real (tm), la mayoría de los programadores simplemente no lo hacen eso.

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

tsimon
fuente
Java lo alienta a hacer este tipo de cosas, para que no tenga que agregar todo tipo de excepción a cada firma de método.
yfeldblum 05 de
17
Es gracioso ... desde que abracé las excepciones marcadas correctamente y las usé apropiadamente, mis programas dejaron de explotar en una enorme pila de insatisfacción del cliente. Si durante el desarrollo tiene un seguimiento de pila grande feo y malo, entonces el cliente también los obtendrá. Me encanta ver su cara cuando ve ArrayIndexOutOfBoundsException con un rastro de pila de una milla de alto en su sistema bloqueado en lugar de una pequeña notificación de bandeja que dice que la configuración de color para el botón XYZ no se pudo analizar, por lo que se usó el valor predeterminado con el software tarareando felizmente a lo largo
Newtopian
1
Quizás lo que Java necesita es una declaración "cantHandle" que especifique que un método o un bloque de código try / catch no está preparado para manejar una excepción particular que ocurre dentro de él, y que cualquier excepción que ocurra a través de medios distintos a un explícito throw dentro de ese método (a diferencia de un método llamado) debe envolverse automáticamente y volverse a lanzar en una RuntimeException. En mi humilde opinión, las excepciones marcadas rara vez deberían propagarse por la pila de llamadas sin estar envuelto.
supercat
3
@Newtopian - Escribo servidor y software de alta confiabilidad y lo he estado haciendo durante 25 años. Mis programas nunca han explotado, y trabajo con alta disponibilidad, reintento y reconexión, sistemas financieros y militares basados ​​en la integración. Tengo una base objetiva absoluta para preferir excepciones de tiempo de ejecución. Las excepciones marcadas hacen que sea más difícil seguir las mejores prácticas correctas de "lanzar temprano, atrapar tarde". La confiabilidad correcta y el manejo de errores se encuentran en el nivel de "negocio", "conexión" o "solicitud". (O ocasionalmente al analizar datos). Las excepciones marcadas se interponen en el camino para hacerlo bien.
Thomas W
1
Las excepciones de las que estás hablando aquí son las RuntimeExceptionsque realmente no tienes que atrapar, y estoy de acuerdo en que debes dejar que el programa explote. Las excepciones que siempre debe atrapar y manejar son las excepciones marcadas como IOException. Si obtiene un IOException, no hay nada que arreglar en su código; su programa no debería explotar solo porque hubo un problema de red.
AxiomaticNexus
20

El artículo Excepciones efectivas de Java explica muy bien cuándo usar las excepciones 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 el método espera este tipo de condiciones y tiene una estrategia para hacerles frente.

Falla: una condición no planificada que impide que un método logre el propósito deseado que no puede describirse sin 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 que es: Una parte del diseño.
  • Se espera que suceda: regularmente pero raramente
  • A quién le importa: el código ascendente que invoca el método
  • Ejemplos: modos de retorno alternativos
  • Mejor mapeo: una excepción marcada

Culpa

  • Se considera que es: una sorpresa desagradable
  • 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 no marcada
Esko Luontola
fuente
Sé cuándo usarlos, quiero saber por qué las personas que no siguen ese consejo ... no siguen ese consejo :-)
TofuBeer 05 de
¿Qué son los errores de programación y cómo distinguirlos de los errores de uso ? ¿Es un error de programación si el usuario pasa los argumentos incorrectos al programa? Desde el punto de vista de Java, podría no ser un error de programación, pero desde el punto de vista del script de shell es un error de programación. Entonces, ¿en qué están los argumentos inválidos args[]? ¿Son una contingencia o una falla?
ceving
3
@TofuBeer: debido a que los diseñadores de la biblioteca de Java, eligieron poner todo tipo de fallas irrecuperables de bajo nivel como excepciones comprobadas cuando claramente deberían haber sido desmarcadas . FileNotFound es la única IOException que debe verificarse, por ejemplo. Con respecto a JDBC, solo conectarse a la base de datos, razonablemente puede considerarse una contingencia . Todas las demás excepciones SQLE deberían haber sido fallidas y no verificadas. El manejo de errores debe estar correctamente en el nivel de "negocio" o "solicitud": consulte la práctica recomendada "lanzar temprano, atrapar tarde". Las excepciones marcadas son una barrera para eso.
Thomas W
1
Hay una sola falla enorme en su argumento. La "contingencia" NO debe manejarse a través de excepciones, sino a través del código comercial y los valores de retorno del método. Las excepciones son, como dice la palabra, situaciones EXCEPCIONALES, por lo tanto, Fallos.
Matteo Mosca
@MatteoMosca Los códigos de retorno de error tienden a ignorarse y eso es suficiente para descalificarlos. En realidad, cualquier cosa inusual a menudo solo se puede manejar en algún lugar de la pila y ese es un caso de uso para excepciones. Me imagino algo así como File#openInputStreamregresar Either<InputStream, Problem>, si eso es lo que quieres decir, entonces podemos estar de acuerdo.
maaartinus
19

En breve:

Las excepciones son una pregunta de diseño de API. -- Ni mas ni menos.

El argumento para las excepciones marcadas:

Para comprender por qué las excepciones verificadas podrían no ser buenas, volteemos la pregunta y preguntemos: ¿cuándo o por qué son atractivas las excepciones verificadas, 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 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 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 excepción específicas y los errores que las causan.

Sin embargo, en realidad, con demasiada frecuencia un paquete com.acmesolo arroja AcmeExceptionsubclases en lugar de subclases específicas. Las personas que llaman necesitan manejar, declarar o volver a señalizar AcmeExceptions, pero aún no pueden estar seguros de si AcmeFileNotFoundErrorsucedió o no AcmePermissionDeniedError.

Entonces, si solo está interesado en una 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 AcmeException.

¿Entonces, para qué molestarse?

Por lo tanto, incluso con 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, a los idiomas sin excepciones comprobadas generalmente no les va mucho peor. Los programadores pueden verse tentados a lanzar instancias inespecíficas de una Errorclase general en lugar de una AcmeException, pero si les importa la calidad de su API, aprenderán a introducir una AcmeFileNotFoundErrordespués de todo.

En general, la especificación y documentación de las excepciones no es muy diferente de la especificación y documentación de, por ejemplo, los métodos ordinarios. Esas también son una pregunta de diseño de 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, atrapar 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 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 se mejora mediante excepciones marcadas, solo el estilo de codificación.

David Lichteblau
fuente
44
Su argumento argumenta contra sí mismo. Si "a veces necesita atrapar una excepción" y la calidad de la API a menudo es deficiente, sin excepciones comprobadas, no sabrá si el diseñador no documentó que un determinado método arroja una excepción que debe detectarse. Combina eso con tirar en AcmeExceptionlugar de AcmeFileNotFoundErrory buena suerte para descubrir qué hiciste mal y dónde debes atraparlo. Las excepciones marcadas proporcionan a los programadores un mínimo de protección contra el diseño incorrecto de la API.
Eva
1
El diseño de la biblioteca Java cometió serios errores. Las 'excepciones comprobadas' fueron para contingencias predecibles y recuperables, como un archivo no encontrado, falta de conexión. Nunca fueron diseñados o adecuados para fallas sistémicas de bajo nivel. Hubiera estado bien forzar la apertura de un archivo para verificarlo, pero no hay un reintento o una recuperación razonables por no escribir un solo byte / ejecutar una consulta SQL, etc. El reintento o la recuperación se manejan correctamente en el "negocio" o la "solicitud" "nivel, que verificó las excepciones que hacen innecesariamente difícil. literatejava.com/exceptions/…
Thomas W
17

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 marcadas con bastante frecuencia con un manejo adecuado de errores, y algunas otras que no lo hacen.

Hasta ahora, me ha resultado más fácil trabajar con la base de código con Excepciones marcadas. Cuando uso la API de otra persona, es bueno que pueda ver exactamente qué tipo de condiciones de error puedo esperar cuando llamo al código y lo 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 para recuperarse. 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 marcada que realmente no quiero manejar en un nivel específico, o que considero un error de lógica de programación, entonces 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 hacerlo son bastante escasas)

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

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 marcadas, especialmente en proyectos grandes con muchos desarrolladores.

Mario Ortegón
fuente
66
Yo hago Para mí son una parte esencial de un contrato. Sin tener que llegar a detallar en la documentación de la API, puedo conocer rápidamente los escenarios de error más probables.
Wayne Hartman
2
De acuerdo. Experimenté la necesidad de verificar excepciones en .Net una vez cuando intenté hacer llamadas de red. Sabiendo que podría ocurrir un problema en la red en cualquier momento, tuve que leer toda la documentación de las API para averiguar qué excepción es la que necesitaba detectar específicamente para ese escenario. Si C # hubiera verificado las excepciones, lo habría sabido de inmediato. Es probable que otros desarrolladores de C # simplemente dejen que la aplicación se bloquee por un simple error de red.
AxiomaticNexus
13

Categorías de excepción

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

  • Fatal : estas excepciones no son tu culpa : no puedes evitarlas y no puedes manejarlas con sensatez. Por ejemplo, OutOfMemoryErroro ThreadAbortException.
  • Boneheaded : estas excepciones son tu culpa : deberías haberlas evitado y representan errores en tu código. Por ejemplo, ArrayIndexOutOfBoundsException, NullPointerExceptiono cualquier IllegalArgumentException.
  • Molesto : 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 decisión de diseño desafortunada, como arrojar NumberFormatExceptiondesde en Integer.parseIntlugar de proporcionar un Integer.tryParseIntmétodo que devuelve un booleano falso en caso de fallo de análisis.
  • Exógena : estas excepciones suelen ser excepcionales , no es su culpa, no puede (razonablemente) prevenirlas, pero debe manejarlas . Por ejemplo, FileNotFoundException.

Un usuario de API:

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

Excepciones marcadas

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 llama, 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 .

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

Una excepción marcada es una excepción que debe manejarse . Manejar una excepción puede ser tan simple como tragarla. ¡Allí! La excepción es manejada. 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

Pero cualquier API que haya comprobado las molestias y las excepciones fatales (por ejemplo, el JCL) supondrá una presión innecesaria para los usuarios de la API. Dichas excepciones tienen que ser manejadas, pero o 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 marcadas.

Además, muchas API no tienen una jerarquía de clases de excepción adecuada, lo que hace que todo tipo de excepciones no exógenas se represente mediante una sola 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, que no podrían haberse evitado y que deben manejarse. Estos forman un pequeño subconjunto de todas las excepciones que se pueden generar. Las API solo deberían haber marcado las excepciones exógenas y todas las demás excepciones sin marcar. Esto mejorará las API, ejercerá menos presión sobre el usuario de la API y, por lo tanto, reducirá la necesidad de atrapar todas, tragar o volver a lanzar excepciones no verificadas.

Así que no odies Java y sus excepciones marcadas. En cambio, odie las API que usan en exceso las excepciones marcadas.

Daniel AA Pelsmaeker
fuente
Y abusar de ellos al no tener una jerarquía.
TofuBeer
1
FileNotFound y el establecimiento de una conexión JDBC / de red son contingencias y excepciones correctas para verificar, ya que son predecibles y (posibles) recuperables. La mayoría de las otras IOExceptions, SQLExceptions, RemoteException, etc. son fallas impredecibles e irrecuperables , y deberían haber sido excepciones en tiempo de ejecución. Debido al diseño erróneo de la biblioteca Java, todos hemos sido agrupados con este error y ahora estamos utilizando principalmente Spring & Hibernate (que acertaron en su diseño).
Thomas W
Por lo general, debe manejar excepciones descabelladas, aunque es posible que no desee llamarlo "manejo". Por ejemplo, en un servidor web, los registro y muestro 500 al usuario. Como la excepción es inesperada, hay todo lo que puedo hacer antes de la corrección de errores.
maaartinus
6

Ok ... Las excepciones marcadas no son ideales y tienen algunas advertencias, 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 la convención ad-hoc para transmitir la posibilidad de error. Al hacerlo, se eliminan todos los beneficios que el compilador puede aportar en el manejo de errores y queda totalmente a la buena voluntad de los programadores.

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

Escucho todo tipo de horrores cuando se trata de excepciones marcadas, se usan incorrectamente, esto es cierto, pero también son excepciones no verificadas. Digo que espere unos años cuando las API están apiladas en muchas capas profundas y estará pidiendo el regreso de algún tipo de medio estructurado para transmitir fallas.

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

Muchos han argumentado que no ser capaz de cargar el archivo fue casi siempre el fin del mundo para el proceso y debe morir de manera horrible y dolorosa. Entonces sí ... claro ... está bien ... construyes una API para algo y carga el archivo en algún momento ... Yo, como usuario de dicha API, solo puedo responder ... "¿Quién demonios eres para decidir cuándo mi el programa debería bloquearse! " Claro, dada la opción donde las excepciones se engullen y no dejan rastro o la EletroFlabbingChunkFluxManifoldChuggingException con una traza de pila más profunda que la trinchera Marianna, tomaría esta última sin dudarlo, pero esto significa 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 se envolvería cada vez que atravesara un nuevo nivel de abstracción para que realmente significara algo?

Por último, la mayor parte del argumento que veo es "No quiero lidiar con excepciones, muchas personas no quieren lidiar con excepciones. Las excepciones marcadas me obligan a tratar con ellas, por lo que odio la excepción marcada" Para eliminar por completo ese mecanismo y relegarlo al abismo del infierno goto es una tontería y carece de jugement 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 "anytype" ... Eso haría la vida mucho más simple ahora, ¿no?

Newtopian
fuente
2
Las excepciones marcadas serían útiles si hubiera un medio declarativo de decir que no se espera que ninguna de las llamadas a métodos dentro de un bloque arroje algunas (o ninguna) excepción marcada, y cualquier excepción de este tipo debería envolverse y volverse a lanzar automáticamente. Podrían ser aún más útiles si las llamadas a los métodos que se declararon como excepciones marcadas se intercambiaran por la velocidad / retorno de la llamada por la velocidad de manejo de excepciones (de modo que las excepciones esperadas pudieran manejarse casi tan rápido como el flujo normal del programa). Sin embargo, ninguna de las dos situaciones se aplica actualmente.
supercat
6

De hecho, las excepciones marcadas 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 un método son básicamente un tipo de retorno especial). Por otro lado, enfrenta el problema de que, dado que las excepciones "se disparan", muy a menudo necesita cambiar muchos métodos (todas las personas que llaman y las personas que llaman, etc.) cuando cambia las excepciones. método lanza.

Las excepciones marcadas 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 del medio se describe en este documento de OOPSLA 2005 (o el informe técnico relacionado ).

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

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

Kurt Schelfthout
fuente
5

Este no es un argumento en contra del concepto puro de excepciones marcadas, pero la jerarquía de clases que Java usa para ellos es un espectáculo extraño. 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 que Exceptionuno se imagina? Bueno, no, porque Exceptions son excepciones, y del mismo modo, las excepciones son Exceptions, a excepción de aquellas excepciones que no son Exceptions, porque otras excepciones son realmente Errors, que son el otro tipo de excepción, un tipo 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 Exceptionni Errors, sino meras Throwableexcepciones.

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

¿Para qué sirven RuntimeException? Bueno, como su nombre lo indica, son excepciones, como todos los Exceptions, y suceden en tiempo de ejecución, como todas las excepciones en realidad, excepto que RuntimeExceptions son excepcionales en comparación con otros tiempos de ejecución Exceptionporque no se supone que sucedan excepto cuando cometes un error tonto, aunque RuntimeExceptions nunca son Errors, entonces 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? Si, todos ellos. Excepto por ThreadDeathuna excepción excepcionalmente excepcional, ya que la documentación explica que es una "ocurrencia normal" y que eso 'Error

De todos modos, dado que estamos dividiendo todas las excepciones en el medio en Errors (que son para excepciones de ejecución excepcionales, sin marcar) ys Exception(que son para errores de ejecución menos excepcionales, así que se verifican, excepto cuando no lo están), ahora necesitamos dos diferentes tipos de cada una de varias excepciones. Entonces necesitamos IllegalAccessErrory IllegalAccessException, y InstantiationErrory InstantiationException, y NoSuchFieldErrory NoSuchFieldException, y NoSuchMethodErrory NoSuchMethodException, y ZipErrory ZipException.

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

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

Gracias Java!

Boann
fuente
2
Por lo que puedo decir, esta respuesta solo dice "Las excepciones de Java son un desastre" de una manera llena de ironía, posiblemente divertida. Lo que parece no hacer es explicar por qué los programadores tienden a evitar tratar de entender cómo se supone que funcionan estas cosas. Además, en los casos de la vida real (al menos con los que tuve la oportunidad de tratar), si los programadores no intentan deliberadamente hacerles la vida más difícil, las excepciones no son tan complicadas como usted ha descrito.
CptBartender
5

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% de las veces, 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 marcada:

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

Si es una excepción común, puede haber incluso varios cientos de tales bloques try-catch en una base de código más grande. Ahora supongamos que necesitamos introducir el manejo de excepciones basado en el 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? Entiendes mi punto :-).

La solución

Lo que hicimos para abordar ese problema fue introducir el concepto de manejadores de excepciones (a los que me referiré más como EH) para centralizar el manejo de excepciones. Para cada clase que necesita entregar excepciones, nuestro marco de inyección de dependencias 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 características que nuestro marco DI nos proporciona. Al cambiar nuestra configuración de marco DI, podemos cambiar fácilmente la implementación de EH a nivel mundial 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 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, registrar el error con más detalles y dejar 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 a la acumulación de código y firmas de nuestros métodos e introduce problemas de mantenimiento mencionados por otros en este hilo.

Piotr Sobczyk
fuente
66
Se inventan excepciones para hacer algo útil con ellos. Escribirlos en un archivo de registro o representar una ventana bonita no es útil, porque el problema original no se resuelve con esto. Hacer algo útil requiere probar una estrategia de solución diferente. Ejemplos: si no puedo obtener mis datos del servidor AI, pruébelo en el servidor B. O si el algoritmo A produce un desbordamiento de pila, pruebo el algoritmo B, que es mucho más lento pero podría tener éxito.
ceving
2
@ceving Sí, todo es bueno y cierto en teoría. Pero ahora volvamos a practicar la palabra. Responda honestamente con qué frecuencia lo hace en su proyecto de palabras reales. ¿Qué parte de los catchbloques en este proyecto real hace algo realmente "útil" con las excepciones? El 10% estaría bien. Los problemas habituales que generan excepciones son como intentar leer la configuración del archivo que no existe, OutOfMemoryErrors, NullPointerExceptions, errores de integridad de restricciones de la base de datos, etc., etc. ¿Realmente está tratando de recuperarse con gracia de todos ellos? No te creo :) A menudo simplemente no hay forma de recuperarse.
Piotr Sobczyk
3
@PiotrSobczyk: si un programa toma alguna acción como resultado de una solicitud de suer, y la operación falla de alguna manera que no ha dañado nada en el estado del sistema, notificar al usuario que la operación no se pudo completar es perfectamente útil forma de manejar la situación. La mayor falla de las excepciones en C # y .net es que no hay una manera consistente de determinar si algo en el estado del sistema podría haberse dañado.
supercat
44
Correcto, @PiotrSobczyk. Con frecuencia, la única acción correcta que se debe tomar en respuesta a una excepción es revertir la transacción y devolver una respuesta de error. Las ideas de "resolver excepciones" implican conocimiento y autoridad que no tenemos (y no deberíamos), y violan la encapsulación. Si nuestra aplicación no es una base de datos, no deberíamos intentar arreglar la base de datos. Fallar limpiamente y evitar escribir datos erróneos, ceder, es lo suficientemente útil .
Thomas W
@PiotrSobczyk Ayer, traté con una excepción de "no se pudo leer el objeto" (que solo se producirá porque la base de datos subyacente se actualizó antes que el software, lo que nunca debería suceder, pero es una posibilidad debido a un error humano) al fallar a un versión histórica de la base de datos garantizada para apuntar a una versión anterior del objeto.
Eponymous
4

Anders habla sobre las trampas de las excepciones comprobadas y por qué las dejó fuera de C # en el episodio 97 de la radio de Ingeniería de Software.

Chuck Conway
fuente
4

Mi escritura en c2.com aún no ha cambiado 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 saberlo.

La suposición 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 excepciones comprobadas en casos como estos es una gran cantidad de código inútil que esencialmente elimina la restricción de excepción comprobada del compilador en tiempo de ejecución.

En cuanto al problema subyacente:

Mi idea general es que el controlador de nivel superior necesita interpretar la excepción y mostrar un mensaje de error apropiado. 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 esto no debería ser demasiado difícil si permitimos un seguimiento de la pila para un error grave problema del servidor

Joshua
fuente
1
Debería tener algo como DAGNodeException en la interfaz, luego capturar la IOException y convertirla en una DAGNodeException: la llamada pública nula (DAGNode arg) arroja DAGNodeException;
TofuBeer el
2
@TofuBeer, ese es exactamente mi punto. Encuentro que las excepciones de ajuste y desenvolvimiento constantemente son peores que eliminar las excepciones marcadas.
Joshua el
2
Bueno, entonces no estamos de acuerdo por completo ... pero su artículo aún no responde a la pregunta subyacente real de cómo impide que su aplicación muestre un seguimiento de la pila al usuario cuando se produce una excepción de tiempo de ejecución.
TofuBeer el
1
@TofuBeer - ¡Cuando falla, decirle al usuario que falló es correcto! ¿Cuál es su alternativa, aparte de "ocultar" la falla con datos 'nulos' o incompletos / incorrectos? Pretender que tuvo éxito es una mentira, que solo empeora las cosas. Con 25 años de experiencia en sistemas de alta confiabilidad, la lógica de reintento solo debe usarse con cuidado y cuando sea apropiado. También esperaría que un visitante vuelva a fallar, sin importar cuántas veces lo vuelva a intentar. A menos que esté volando un avión, cambiar a una segunda versión del mismo algoritmo es poco práctico e inverosímil (y puede fallar de todos modos).
Thomas W
4

Para intentar abordar solo la pregunta sin respuesta:

Si arroja las subclases RuntimeException en lugar de las subclases Exception, ¿cómo sabe lo que debe atrapar?

La pregunta contiene razonamiento engañoso en mi humilde opinión. El hecho de que la API te diga lo que arroja no significa que lo trates de la misma manera en todos los casos. En otras palabras, las excepciones que necesita detectar varían según el contexto en el que utiliza el componente que genera 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 usuario que ingresó a XPath, entonces probablemente quiera capturar e informar sobre todas las excepciones marcadas y no marcadas que arroja la operación.

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

Dave Elton
fuente
2
Exactamente. Fácil y directo, como debe ser el manejo de excepciones. Como dice Dave, el manejo correcto de excepciones normalmente se realiza a un alto nivel . "Lanzar temprano, atrapar tarde" es el principio. Las excepciones marcadas lo hacen difícil.
Thomas W
4

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

Favorece las excepciones marcadas sin control, pero esta elección se explica muy minuciosamente y se basa en argumentos sólidos.

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

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

El autor "respuestas":

Es absolutamente incorrecto suponer que no se deben detectar todas las excepciones de tiempo de ejecución y permitir que se propaguen a la "parte superior" de la aplicación. (...) Para cada condición excepcional que se requiera que se maneje de manera distinta, según los requisitos del sistema / negocio, los programadores deben decidir dónde capturarla y qué hacer una vez que se detecta la condición. Esto debe hacerse estrictamente de acuerdo con las necesidades reales de la aplicación, no en función de una alerta del compilador. Se debe permitir que todos los demás errores se propaguen libremente al controlador superior donde se registrarían y se tomará una acción graciosa (tal vez, terminación).

Y el pensamiento o artículo principal es:

Cuando se trata de 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 todas las subrutinas o módulos que existen!

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 manejador de excepciones más genérico (por ejemplo, el que maneja todas las Excepciones no manejadas por manejadores 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 sobre qué enfoque es mejor tomarse un tiempo y leerlo.

Piotr Sobczyk
fuente
4

Las excepciones marcadas 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, archivo no encontrado, etc.) y garantizar que los desarrolladores los manejen.

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

Las fallas generalmente son posibles en el código, y los contenedores EJB, web y Swing / AWT ya lo atienden 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 el tiempo de ejecución y las excepciones comprobadas son funcionalmente equivalentes. No hay manejo ni recuperación que las excepciones marcadas puedan hacer, que las excepciones de tiempo de ejecución no pueden.

El mayor argumento en contra de las excepciones "controladas" es que la mayoría de las excepciones no se pueden solucionar. El hecho simple es que no poseemos el código / subsistema que se rompió. No podemos ver la implementación, no somos responsables y no podemos solucionarlo.

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áticos 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 de confiabilidad sistémica generalizada forzada, que en realidad no son reparables, deben declararse ampliamente.

El otro defecto 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 "tirar temprano, atrapar tarde". Las excepciones marcadas hacen poco pero se interponen en el camino de esto.

Tenemos un problema obvio en Java de requerir miles de bloques try-catch de no hacer nada, con una proporción significativa (40% +) mal codificada. Casi ninguno de estos implementa un manejo o confiabilidad genuinos, pero impone una sobrecarga de codificación importante.

Por último, las "excepciones comprobadas" son bastante incompatibles con la programación funcional FP.

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

Muchas personas hablan sobre "manejar" las excepciones marcadas, pero están hablando a través de sus sombreros. Continuar después de una falla con datos nulos, incompletos o incorrectos para fingir éxito no es manejar nada. Es una mala práctica 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 "falla" al usuario es una buena práctica, y lo más importante, evitar que se comprometan datos comerciales incorrectos en la base de datos.

Otras estrategias para el manejo de excepciones son "reintentar", "volver a conectar" 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 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.

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

Thomas W
fuente
1
Mi punto es fallar adecuadamente como estrategia general. Las excepciones no marcadas ayudan a que, ya que no obligan a los bloques de captura a interponerse. La captura y el registro de errores se pueden dejar a unos pocos controladores externos, en lugar de codificarlos erróneamente miles de veces en toda la base de código (que en realidad es lo que oculta los errores) . Para fallas arbitrarias, las excepciones no verificadas son absolutamente correctas. Las contingencias (resultados predecibles como fondos insuficientes) son las únicas excepciones que legítimamente vale la pena hacer.
Thomas W
1
Mi respuesta ya mencionada anteriormente aborda esto. Primero y ante todo, 1) el controlador de fallas más externo debe atrapar todo. Más allá de eso, solo para sitios específicos identificados, 2) Las contingencias esperadas específicas se pueden capturar y manejar, en el sitio inmediato donde se arrojan. Eso significa que no se encontró el archivo, fondos insuficientes, etc., en el momento en que podrían recuperarse, no más. El principio de encapsulación significa que las capas externas no pueden / no deben ser responsables de comprender / recuperarse de fallas en el interior. Tercero, 3) Todo lo demás debe arrojarse hacia afuera, sin marcar si es posible.
Thomas W
1
El controlador más externo detecta la excepción, la registra y devuelve una respuesta "fallida" o muestra un cuadro de diálogo de error. Muy simple, no es difícil de definir en absoluto. El punto es que cada excepción no recuperable de forma inmediata y local, es una falla irrecuperable debido al principio de encapsulación. Si el código que debe saber no puede recuperarlo, entonces la solicitud en general falla de manera limpia y correcta. Esta es la forma correcta de hacerlo correctamente.
Thomas W
1
Incorrecto. El trabajo del controlador externo es fallar limpiamente y registrar errores en el límite de 'solicitud'. La solicitud rota falla correctamente, se informa una excepción, el subproceso puede continuar atendiendo la siguiente solicitud. Tal controlador externo es una característica estándar en los contenedores Tomcat, AWT, Spring, EJB y el hilo 'principal' de Java.
Thomas W
1
¿Por qué es peligroso informar "errores genuinos" en el límite de solicitud o en el controlador externo? Con frecuencia trabajo en integración y confiabilidad de sistemas, donde la correcta ingeniería de confiabilidad es realmente importante, y utilizo enfoques de "excepción no verificada" para hacerlo. No estoy realmente seguro de lo que realmente está debatiendo: parece que es posible que desee pasar 3 meses en la forma de excepción no verificada, tener una idea de ello, y luego tal vez podamos discutir más. Gracias.
Thomas W
4

Como la gente ya ha dicho, las excepciones marcadas no existen en Java bytecode. Son simplemente un mecanismo de compilación, no muy diferente de otras comprobaciones de sintaxis. Veo las excepciones comprobadas mucho como veo el compilador quejándose de un condicional redundante: if(true) { a; } b;. Eso es útil, pero podría haberlo hecho a propósito, así que déjame ignorar tus advertencias.

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

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

Martín
fuente
Correcto, solo quería enfatizar la diferencia entre el código inalcanzable (que genera un error) y los condicionales con un resultado predecible. Eliminaré este comentario más tarde.
Holger
3

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

Otro problema con las excepciones marcadas 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 cerrar () posiblemente transmitir 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 haga comenzar 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 (utilizamos Java y C # donde trabajo).

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

Powerlord
fuente
2
Bien, cualquiera de los conductores puede ... la pregunta es más bien "¿por qué debería importarle al programador?" ya que ha terminado de acceder a la base de datos de todos modos. Los documentos incluso le advierten que siempre debe confirmar () o revertir () la transacción actual antes de llamar a close ().
Powerlord
Mucha gente piensa que cerrar un archivo no puede lanzar una excepción ... stackoverflow.com/questions/588546/ ... ¿está 100% seguro de que no hay casos que importen?
TofuBeer
Estoy 100% seguro de que no hay casos que sería la materia y que la persona que llama no sería puesto en un try / catch.
Martin
1
Excelente ejemplo con conexiones de cierre, Martin! Solo puedo reformularlo: si solo declaramos explícitamente que hemos terminado con una conexión, ¿por qué debería molestarse en lo que sucede cuando lo estamos cerrando? Hay más casos como este que al programador realmente no le importa si se produce una excepción y tiene toda la razón al respecto.
Piotr Sobczyk
1
@PiotrSobczyk: algunos controladores SQL emitirán un chillido si uno cierra una conexión después de iniciar una transacción pero no la confirma ni la revierte. En mi humilde opinión, graznar es mejor que ignorar silenciosamente el problema, al menos en los casos en que graznar no hará que se pierdan otras excepciones.
supercat
3

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

El escaso beneficio se ve compensado 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 agradable, pero el análisis estático verdaderamente confiable a menudo exige inflexiblemente un trabajo estricto del programador. Hay un cálculo de costo-beneficio, y la barra debe establecerse alta para una verificación que conduce a un error de tiempo de compilación. Sería más útil si el IDE asumiera el papel de comunicar qué excepciones puede arrojar 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 IDE no es tan crucial.

Aleksandr Dubinsky
fuente
2

Creo que esta es una excelente pregunta y no es en absoluto 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 del núcleo de la API de Java en general deberían verificarse si alguna vez podrían manejarse. Toma FileNotFoundExceptiono (mi favorito) InterruptedException. Estas condiciones casi siempre deben manejarse específicamente (es decir, su reacción a un InterruptedExceptionno es lo mismo que su reacción a un 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 InterruptedExceptionmanejado correctamente!)

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

oxbow_lakes
fuente
1
Sí, es un error del desarrollador. Claramente no usaron el nombre correcto, por lo que tienen que regresar y arreglar su código.
AxiomaticNexus
@AxiomaticNexus Ningún desarrollador cuerdo usa enumnombres de miembros, simplemente porque usan enumobjetos en su lugar. Entonces, un nombre incorrecto solo puede venir del exterior, ya sea un archivo de importación o lo que sea. Una posible forma de tratar con esos nombres es llamar MyEnum#valueOfy atrapar al IAE. Otra forma es usar un precompletado Map<String, MyEnum>, pero estos son detalles de implementación.
maaartinus
@maaartinus Hay casos en los que se usan nombres de miembros enum sin que la cadena provenga del exterior. Por ejemplo, cuando desea recorrer todos los miembros dinámicamente para hacer algo con cada uno. Además, si la cadena proviene del exterior o no es irrelevante. El desarrollador tiene toda la información que necesita para saber si pasar la cadena x a "MyEnum # valueOf" generará un error antes de pasarlo. Pasar la cadena x a "MyEnum # valueOf" de todos modos cuando hubiera causado un error, claramente sería un error por parte del desarrollador.
AxiomaticNexus
2

Aquí hay un argumento contra las excepciones comprobadas (de joelonsoftware.com):

El razonamiento es que considero que las excepciones no son mejores que "goto's", 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 arrojar 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 detecta en el acto, crea oportunidades para errores sorpresa causados ​​por funciones que terminaron abruptamente, dejando datos en un estado inconsistente u otras rutas de código que usted no pensar en.
finnw
fuente
3
+1 ¿Es posible que desee resumir el argumento en su respuesta? Son como gotos invisibles y salidas tempranas para sus rutinas, dispersos por todo el programa.
MarkJ
13
Eso es más un argumento contra las Excepciones en general.
Ionuț G. Stan
10
¿has leído el artículo? En primer lugar, habla de excepciones en general, en segundo lugar, la sección "Son invisibles en el código fuente" se aplica específicamente a la excepción NO COMPROBADA. Este es todo el punto de la excepción comprobada ... para que SABES qué código arroja qué dónde
Newtopian
2
@Eva No son lo mismo. Con una declaración goto puedes ver la gotopalabra clave. Con un bucle puede ver la llave de cierre o la palabra clave breako continue. Todos ellos saltan a un punto en el método actual. Pero no siempre puede ver el throw, porque a menudo no está en el método actual sino en otro método al que llama (posiblemente indirectamente)
Finnw
55
@finnw Las funciones son en sí mismas una forma de goto. Usualmente no sabes qué funciones están llamando las funciones que estás llamando. Si programó sin funciones, no tendría un problema con excepciones invisibles. Lo que significa que el problema no está específicamente vinculado a las excepciones, y no es un argumento válido contra las excepciones en general. Se podría decir que los códigos de error son más rápidos, se podría decir que las mónadas son más limpias, pero el argumento goto es una tontería.
Eva
2

Las buenas pruebas de que la excepción comprobada no son necesarias son:

  1. Una gran cantidad de framework que funciona para Java. Al igual que Spring, que envuelve la excepción JDBC a las excepciones no marcadas, 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 predicción amable 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 trabaja el cliente de código. Como ejemplo, los métodos de Interfcace que obligan a lanzar una excepción marcada. Hay 100 implementaciones en el sistema, 50 o incluso 90 de las implementaciones no arrojan esta excepción, pero el cliente aún debe atrapar esta excepción si el usuario hace referencia a esa interfaz. Esas 50 o 90 implementaciones tienden a manejar esas excepciones dentro de sí mismos, poniendo excepción al registro (y este es un buen comportamiento para ellos). ¿Qué deberíamos hacer con eso? Será mejor que tenga algo de lógica de fondo que haga todo ese trabajo: enviar un mensaje al registro. Y si yo, como cliente de código, sintiera 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? ¿Qué debo hacer con eso? Si no existe, el sistema no irí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 Excepción marcada, poner un mensaje para iniciar sesión y luego lanzar una excepción desde el método. No ... no, será mejor que lo haga automáticamente con RuntimeEception, eso lo hace automáticamente. No tiene ningún sentido manejarlo manualmente. Me alegraría haber visto un mensaje de error en el registro (AOP puede ayudar con eso ... algo que corrige Java). Si, eventualmente, decido que el sistema debería mostrar un mensaje emergente al usuario final, lo mostraré, no es un problema.

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

ses
fuente
1
En lugar de tener dos versiones de clases, debe haber una manera concisa de especificar que ninguna de las llamadas a métodos realizadas por un bloque de código no arroje excepciones de ciertos tipos, y que dichas excepciones deben envolverse por algún medio específico y volver a lanzar (de forma predeterminada, crear una nueva RuntimeExceptioncon una excepción interna adecuada). Es desafortunado que sea más conciso que el método externo sea throwsuna excepción del método interno, que que envuelva las excepciones del método interno, cuando el último curso de acción con mayor frecuencia sería correcto.
supercat
2

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

Digamos que 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 MyAppExceptioncuál es un poco incómodo, pero manejable. Un controlador de excepciones registra la excepción y notifica al usuario de alguna manera.

Todo se ve bien hasta que desee implementar alguna interfaz que no sea la 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. Si lo desea, puede mencionar voluntariamente la excepción en JavaDoc. Pero aparte de eso, simplemente burbujea silenciosamente a través de cualquier cosa, para quedar atrapado en su capa de manejo de excepciones.

Vlasec
fuente
1

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. Él reconoce muchos de los aspectos negativos que otros han mencionado: Excepciones efectivas

Jacob Toronto
fuente
2
El problema con las excepciones comprobadas en Java proviene de un problema más profundo, que es que demasiada información se encapsula en el TIPO de la excepción, en lugar de en las propiedades de una instancia. Sería útil tener excepciones verificadas, si ser "verificado" era un atributo de los sitios de lanzamiento / captura, y si uno pudiera declarar declarativamente si una excepción verificada que escapa a un bloque de código debe permanecer como una excepción verificada, o ser vista por cualquier bloque adjunto como una excepción no verificada; asimismo, los bloques catch deberían poder especificar que solo desean excepciones marcadas.
supercat
1
Suponga que se especifica una rutina de búsqueda de diccionario para lanzar algún tipo particular de excepción si se intenta acceder a una clave inexistente. Puede ser razonable que el código del cliente detecte dicha excepción. Sin embargo, si algún método utilizado por la rutina de búsqueda arroja ese mismo tipo de excepción de una manera que la rutina de búsqueda no espera, el código del cliente probablemente no debería detectarlo. Tener la verificación como propiedad de instancias de excepción , sitios de lanzamiento y sitios de captura evitaría tales problemas. El cliente detectaría excepciones 'marcadas' de ese tipo, esquivando las inesperadas.
supercat
0

He leído mucho sobre el manejo de excepciones, incluso si (la mayoría de las veces) realmente no puedo decir que estoy contento o triste por la existencia de excepciones marcadas, esta es mi opinión: excepciones marcadas 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 marcadas, pero por otro lado, a veces Es útil / 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 marcar 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 debe confundirse con usted, debe ignorar las excepciones), especialmente en el lado del cliente donde un solo clic podría arrojar 10 posibles excepciones (marcadas).

La versión actual (tercera) usa solo excepciones no verificadas, y tiene un controlador de excepciones global que es responsable de manejar cualquier cosa no detectada. 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 algo más, como esta excepción, AbortException, lo que significa romper el hilo de ejecución actual y no registrar ningún error porque se desea no hacerlo. Por supuesto, para resolver todo el hilo personalizado debe manejar el método run () con un try {...} catch (all).

public void run () {

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

}

Esto no es necesario si utiliza WorkerService para programar trabajos (Ejecutables, invocables, Trabajadores), 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í, también es bueno para otros ... :)

adrian.tarau
fuente