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
(exceptoRuntimeException
) 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 deException
subclases, ¿cómo sabes lo que se supone que debes atrapar?
Si la respuesta es catch, Exception
entonces también está lidiando con los errores del programador de la misma manera que las excepciones del sistema. Eso me parece equivocado.
Si detecta, Throwable
entonces está tratando las excepciones del sistema y los errores de VM (y similares) de la misma manera. Eso me parece equivocado.
Si la respuesta es que 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 RuntimeException
porque no les gusta extender desde Exception
y / o por qué atrapan una excepción y luego vuelven a lanzar un RuntimeException
lanzamiento en lugar de agregar su método Quiero entender la motivación para no gustarme las excepciones marcadas.
fuente
Respuestas:
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 #.
Aunque soy fanático de Hejlsberg y su trabajo, este argumento siempre me ha parecido falso. Básicamente se reduce a:
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
donde
fopen
indica 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 cony 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 de
fopen()
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.
¿Ves esa trampa? Aquí está la firma para ese método API:
Tenga en cuenta que
FileNotFoundException
es 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:
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:
(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
FileNotFoundException
no 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. PeroCheckedInvalidRowNumberException
es 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:
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. .
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
RowData
extiende desde diferentes métodos. En particular, voy a escribir mucho código comoy será doloroso tener que envolver en try / catch cada vez.
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:
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:
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
FileNotFoundException
en 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
RuntimeException
para esto, pero también puede crear fácilmente el suyo propio. Aquí está el constructor: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.
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
fuente
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]:
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:
Y luego hay muchas excepciones de biblioteca simplemente ridículas como:
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.
fuente
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:
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:
¿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.
fuente
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 comoArrayList
yLinkedList
. 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()
yget(int index)
.Esta
WidgetList
clase de ejemplo lee algunos objetos de tipo de tamaño fijoWidget
(no mostrados) de un archivo: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 sobreWidgetList
sí 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
IOException
excepció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 deWidgetList
manejar 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:
((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
Map
respaldado 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
Runnable
interfaz 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 aRuntimeException
.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):
¡Hurra! Usando esto, podemos lanzar una excepción marcada a cualquier profundidad de la pila sin declararla, sin envolverla
RuntimeException
y sin saturar la traza de la pila. Usando el ejemplo "WidgetList" nuevamente: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:
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
'swrite
métodos, todos ellos especificanthrows IOException
.ByteArrayOutputStream
es una subclase que escribe en una matriz en memoria en lugar de una fuente de E / S verdadera. Suswrite
métodos anulados no pueden causarIOException
s, por lo que no tienenthrows
cláusula, y puede llamarlos sin preocuparse por el requisito de capturar o especificar.Excepto que no siempre. Supongamos que
Widget
tiene un método para guardarlo en una secuencia:Declarar que este método acepta un plano
OutputStream
es 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: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
IOException
lanzada porwrite
las llamadas en unaOutputStream
, pero deseas cambiar el tipo declarado de la variable a aByteArrayOutputStream
, 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
write
métodos deOutputStream
está no anulado porByteArrayOutputStream
. Específicamente,write(byte[] data)
es un método de conveniencia que escribe la matriz completa llamandowrite(byte[] data, int offset, int length)
con un desplazamiento de 0 y la longitud de la matriz.ByteArrayOutputStream
anula el método de tres argumentos pero hereda el método de conveniencia de un argumento tal como está. El método heredado hace exactamente lo correcto, pero incluye unathrows
cláusula no deseada . Tal vez fue un descuido en el diseño deByteArrayOutputStream
, 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
try
y loscatch
bloques. 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:
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 Next
o PHPerror_reporting(0);
.Llamar a algún tipo de clase de registrador no es mucho mejor:
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.
fuente
RuntimeException
. Tenga en cuenta que una rutina podría declararse simultáneamentethrows IOException
y, sin embargo, también especificar que cualquierIOException
llamada lanzada desde una llamada anidada debe considerarse inesperada y ajustada.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.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 explotar
throws
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:
fuente
throws
declaraciones.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:
Actualice la interfaz de usuario desde un subproceso que no sea UI en C #:
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:
¿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
Ya escribí un blog sobre esto anteriormente .
fuente
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:
fuente
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:
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.
fuente
RuntimeExceptions
que 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 comoIOException
. Si obtiene unIOException
, no hay nada que arreglar en su código; su programa no debería explotar solo porque hubo un problema de red.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:
(SO no permite tablas, por lo que es posible que desee leer lo siguiente de la página original ...)
fuente
args[]
? ¿Son una contingencia o una falla?File#openInputStream
regresarEither<InputStream, Problem>
, si eso es lo que quieres decir, entonces podemos estar de acuerdo.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.acme
solo arrojaAcmeException
subclases en lugar de subclases específicas. Las personas que llaman necesitan manejar, declarar o volver a señalizarAcmeExceptions
, pero aún no pueden estar seguros de siAcmeFileNotFoundError
sucedió o noAcmePermissionDeniedError
.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 subclaseAcmeException
.¿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
Error
clase general en lugar de unaAcmeException
, pero si les importa la calidad de su API, aprenderán a introducir unaAcmeFileNotFoundError
despué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.
fuente
AcmeException
lugar deAcmeFileNotFoundError
y 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.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.
fuente
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:
OutOfMemoryError
oThreadAbortException
.ArrayIndexOutOfBoundsException
,NullPointerException
o cualquierIllegalArgumentException
.NumberFormatException
desde enInteger.parseInt
lugar de proporcionar unInteger.tryParseInt
método que devuelve un booleano falso en caso de fallo de análisis.FileNotFoundException
.Un usuario de API:
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.
fuente
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?
fuente
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.
fuente
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
Exception
uno se imagina? Bueno, no, porqueException
s son excepciones, y del mismo modo, las excepciones sonException
s, a excepción de aquellas excepciones que no sonException
s, porque otras excepciones son realmenteError
s, 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 sonException
niError
s, sino merasThrowable
excepciones.¿Cuáles de estas son las excepciones "marcadas"?
Throwable
s son excepciones marcadas, excepto si también sonError
s, que son excepciones no marcadas, y luego están lasException
s, que también sonThrowable
sy son el tipo principal de excepción marcada, excepto que también hay una excepción, que es que si también sonRuntimeException
s, 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 losException
s, y suceden en tiempo de ejecución, como todas las excepciones en realidad, excepto queRuntimeException
s son excepcionales en comparación con otros tiempos de ejecuciónException
porque no se supone que sucedan excepto cuando cometes un error tonto, aunqueRuntimeException
s nunca sonError
s, entonces son para cosas que son excepcionalmente erróneas pero que en realidad no sonError
s. Excepto porRuntimeErrorException
, que realmente es unRuntimeException
paraError
s. ¿Pero no se supone que todas las excepciones representan circunstancias erróneas de todos modos? Si, todos ellos. Excepto porThreadDeath
una 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
Error
s (que son para excepciones de ejecución excepcionales, sin marcar) ysException
(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 necesitamosIllegalAccessError
yIllegalAccessException
, yInstantiationError
yInstantiationException
, yNoSuchFieldError
yNoSuchFieldException
, yNoSuchMethodError
yNoSuchMethodException
, yZipError
yZipException
.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 unUnexpectedException
, o unUnknownException
(que no está relacionadoUnknownError
, que es solo para "excepciones graves"), o unExecutionException
, o unInvocationTargetException
, o unExceptionInInitializerError
.Ah, y no debemos olvidar la novedad elegante de Java 8
UncheckedIOException
, que es unaRuntimeException
excepción diseñada para permitirle arrojar el concepto de verificación de excepciones por la ventana envolviendo lasIOException
excepciones verificadas causadas por errores de E / S (que no causanIOError
excepciones, aunque existe) también) que son excepcionalmente difíciles de manejar y, por lo tanto, necesita que no se verifiquen.Gracias Java!
fuente
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:
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í:
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.
fuente
catch
bloques 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.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.
fuente
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
fuente
Para intentar abordar solo la pregunta sin respuesta:
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.
fuente
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:
El autor "respuestas":
Y el pensamiento o artículo principal es:
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.
fuente
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/
fuente
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.
fuente
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.Connection
elclose()
método de. Puede arrojar unSQLException
, 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í: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.fuente
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.
fuente
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
FileNotFoundException
o (mi favorito)InterruptedException
. Estas condiciones casi siempre deben manejarse específicamente (es decir, su reacción a unInterruptedException
no es lo mismo que su reacción a unIllegalArgumentException
). 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 vistoInterruptedException
manejado correctamente!)Una cosa más: a
RuntimeException
no siempre es "cuando un desarrollador hizo algo mal". Se produce una excepción de argumento ilegal cuando intenta crear unenum
usovalueOf
y no hayenum
ese nombre. ¡Esto no es necesariamente un error del desarrollador!fuente
enum
nombres de miembros, simplemente porque usanenum
objetos 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 llamarMyEnum#valueOf
y atrapar al IAE. Otra forma es usar un precompletadoMap<String, MyEnum>
, pero estos son detalles de implementación.Aquí hay un argumento contra las excepciones comprobadas (de joelonsoftware.com):
fuente
goto
palabra clave. Con un bucle puede ver la llave de cierre o la palabra clavebreak
ocontinue
. Todos ellos saltan a un punto en el método actual. Pero no siempre puede ver elthrow
, porque a menudo no está en el método actual sino en otro método al que llama (posiblemente indirectamente)Las buenas pruebas de que la excepción comprobada no son necesarias son:
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.
fuente
RuntimeException
con una excepción interna adecuada). Es desafortunado que sea más conciso que el método externo seathrows
una 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.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 declarathrows MyAppException
cuá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.fuente
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
fuente
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 () {
}
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 ... :)
fuente