Usamos SonarQube para analizar nuestro código Java y tiene esta regla (establecida en crítica):
Los métodos públicos deberían arrojar como máximo una excepción marcada
El uso de excepciones marcadas obliga a las personas que llaman al método a tratar los errores, ya sea propagándolos o manejándolos. Esto hace que esas excepciones formen parte de la API del método.
Para mantener razonable la complejidad para las personas que llaman, los métodos no deberían arrojar más de un tipo de excepción marcada ".
Otro bit en Sonar tiene esto :
Los métodos públicos deberían arrojar como máximo una excepción marcada
El uso de excepciones marcadas obliga a las personas que llaman al método a tratar los errores, ya sea propagándolos o manejándolos. Esto hace que esas excepciones formen parte de la API del método.
Para mantener razonable la complejidad para las personas que llaman, los métodos no deben arrojar más de un tipo de excepción marcada.
El siguiente código:
public void delete() throws IOException, SQLException { // Non-Compliant /* ... */ }
debe refactorizarse en:
public void delete() throws SomeApplicationLevelException { // Compliant /* ... */ }
Los métodos de anulación no están marcados por esta regla y se les permite lanzar varias excepciones marcadas.
Nunca he encontrado esta regla / recomendación en mis lecturas sobre el manejo de excepciones y he tratado de encontrar algunos estándares, discusiones, etc. sobre el tema. Lo único que he encontrado es esto de CodeRach: ¿cuántas excepciones debería arrojar un método como máximo?
¿Es este un estándar bien aceptado?
fuente
Respuestas:
Consideremos la situación en la que tiene el código proporcionado de:
El peligro aquí es que el código que escriba para llamar
delete()
se verá así:Esto también es malo. Y quedará atrapado con otra regla que marca la captura de la clase de excepción base.
La clave es no escribir código que haga que quieras escribir código incorrecto en otro lugar.
La regla con la que te encuentras es bastante común. Checkstyle lo tiene en sus reglas de diseño:
Esto describe con precisión el problema y cuál es el problema y por qué no debe hacerlo. Es un estándar bien aceptado que muchas herramientas de análisis estático identificarán y marcarán.
Y si bien puede hacerlo de acuerdo con el diseño del lenguaje, y puede haber ocasiones en que sea lo correcto, es algo que debería ver e inmediatamente decir "um, ¿por qué estoy haciendo esto?" Puede ser aceptable para el código interno donde todos son lo suficientemente disciplinados como para nunca
catch (Exception e) {}
, pero la mayoría de las veces he visto a personas cortar esquinas, especialmente en situaciones internas.No hagas que las personas que usan tu clase quieran escribir códigos incorrectos.
Debo señalar que la importancia de esto se reduce con Java SE 7 y versiones posteriores porque una sola instrucción catch puede capturar múltiples excepciones ( Capturar múltiples tipos de excepciones y volver a generar excepciones con la Comprobación de tipos mejorada de Oracle).
Con Java 6 y versiones anteriores, tendría un código similar al siguiente:
y
o
Ninguna de estas opciones con Java 6 es ideal. El primer enfoque viola DRY . Múltiples bloques haciendo lo mismo, una y otra vez, una vez para cada excepción. ¿Desea registrar la excepción y volver a lanzarla? Okay. Las mismas líneas de código para cada excepción.
La segunda opción es peor por varias razones. Primero, significa que está capturando todas las excepciones. El puntero nulo queda atrapado allí (y no debería). Además, está volviendo a lanzar una, lo
Exception
que significa que la firma del método sería lodeleteSomething() throws Exception
que hace un desastre más arriba en la pila, ya que las personas que usan su código ahora están obligadas a hacerlocatch(Exception e)
.Con Java 7, esto no es tan importante porque en su lugar puede hacer:
Además, el tipo de comprobar si uno qué coger el tipo de las excepciones que es lanzado:
El verificador de tipos reconocerá que solo
e
puede ser de tipos o . Todavía no estoy demasiado entusiasmado con el uso de este estilo, pero no está causando un código tan malo como lo era en Java 6 (donde te obligaría a que la firma del método sea la superclase que extienden las excepciones).IOException
SQLException
A pesar de todos estos cambios, muchas herramientas de análisis estático (Sonar, PMD, Checkstyle) siguen aplicando guías de estilo Java 6. No es algo malo Tiendo a estar de acuerdo con una advertencia de que estos se sigan cumpliendo, pero puede cambiar la prioridad de ellos a mayor o menor según cómo su equipo los priorice.
Si las excepciones deben ser activada o desactivada ... que es una cuestión de g r ae un t debate que uno puede encontrar fácilmente un sinnúmero de publicaciones en el blog que ocupan cada lado del argumento. Sin embargo, si está trabajando con excepciones marcadas, probablemente debería evitar lanzar varios tipos, al menos en Java 6.
fuente
foo.delete()
? ¿Sigue siendo atrapado y arrojado de nuevo?delete
arroja una excepción marcada que no sea IOException o SQLException en ese ejemplo. El punto clave que estaba tratando de hacer es que un método que llama a rethrowException seguirá obteniendo el tipo de Exception en Java 7. En Java 6, todo eso se fusiona con elException
tipo común que hace que el análisis estático y otros codificadores sean tristes.catch (Exception e)
y forzarlo a ser unocatch (IOException e)
ucatch (SQLException e)
otro.catch(IOException|SQLException ex)
lugar. Pero, si solo va a volver a lanzar la excepción, permitir que el verificador de tipo propague el tipo real de la excepción mientras que simplificar el código no es algo malo.La razón por la cual, idealmente, solo querría lanzar un tipo de excepción es porque hacer lo contrario probablemente viola los principios de Inversión de dependencia y responsabilidad única . Usemos un ejemplo para demostrar.
Digamos que tenemos un método que recupera datos de la persistencia, y esa persistencia es un conjunto de archivos. Como estamos tratando con archivos, podemos tener un
FileNotFoundException
:Ahora, tenemos un cambio en los requisitos y nuestros datos provienen de una base de datos. En lugar de un
FileNotFoundException
(ya que no estamos tratando con archivos), ahora lanzamos unSQLException
:Ahora tendríamos que revisar todo el código que usa nuestro método y cambiar la excepción que tenemos que verificar, de lo contrario el código no se compilará. Si nuestro método se llama a lo largo y ancho, eso puede ser mucho para cambiar / hacer que otros cambien. Lleva mucho tiempo y la gente no va a ser feliz.
La inversión de dependencia dice que realmente no deberíamos lanzar ninguna de estas excepciones porque exponen los detalles internos de implementación que estamos trabajando para encapsular. El código de llamada necesita saber qué tipo de persistencia estamos usando, cuando realmente debería preocuparse si se puede recuperar el registro. En cambio, deberíamos lanzar una excepción que transmita el error al mismo nivel de abstracción que estamos exponiendo a través de nuestra API:
Ahora, si cambiamos la implementación interna, simplemente podemos envolver esa excepción en una
InvalidRecordException
y pasarla (o no envolverla, y simplemente lanzar una nuevaInvalidRecordException
). El código externo no sabe ni le importa qué tipo de persistencia se está utilizando. Todo está encapsulado.En cuanto a la responsabilidad individual, debemos pensar en el código que arroja múltiples excepciones no relacionadas. Digamos que tenemos el siguiente método:
¿Qué podemos decir sobre este método? Podemos decir por la firma que abre un archivo y lo analiza. Cuando vemos una conjunción, como "y" o "o" en la descripción de un método, sabemos que está haciendo más de una cosa; Tiene más de una responsabilidad . Los métodos con más de una responsabilidad son difíciles de administrar, ya que pueden cambiar si alguna de las responsabilidades cambia. En cambio, deberíamos dividir los métodos para que tengan una única responsabilidad:
Hemos extraído la responsabilidad de leer el archivo de la responsabilidad de analizar los datos. Un efecto secundario de esto es que ahora podemos pasar cualquier información de cadena a los datos de análisis desde cualquier fuente: en memoria, archivo, red, etc. Ahora también podemos probar
parse
más fácilmente porque no necesitamos un archivo en el disco para ejecutar pruebas contra.A veces hay realmente dos (o más) excepciones que podemos lanzar de un método, pero si nos atenemos a SRP y DIP, las veces que nos encontramos con esta situación se vuelven más raras.
fuente
update
método es lo más atómico posible, y las excepciones están en el nivel adecuado de abstracción, entonces sí, parece que necesita lanzar dos tipos diferentes de excepciones, ya que hay dos casos excepcionales. Esa debería ser la medida de cuántas excepciones se pueden lanzar, en lugar de estas reglas de linter (a veces arbitrarias).catch (IOException | SQLException ex)
) porque el verdadero problema está en el diseño / modelo del programa.Recuerdo jugar un poco con esto cuando jugué con Java hace un tiempo, pero no era realmente consciente de la distinción entre marcado y no marcado hasta que leí tu pregunta. Encontré este artículo en Google bastante rápido, y entra en algo de la aparente controversia:
http://tutorials.jenkov.com/java-exception-handling/checked-or-unchecked-exceptions.html
Dicho esto, uno de los problemas que este tipo mencionaba con las excepciones marcadas es que (y personalmente me he encontrado con esto desde el principio con Java) si sigues agregando un montón de excepciones marcadas a las
throws
cláusulas en tus declaraciones de métodos, no solo ¿tiene que poner mucho más código repetitivo para admitirlo a medida que avanza a métodos de nivel superior, pero también crea un mayor lío y rompe la compatibilidad cuando intenta introducir más tipos de excepción a los métodos de nivel inferior? Si agrega un tipo de excepción marcada a un método de nivel inferior, entonces debe ejecutar nuevamente su código y ajustar también otras declaraciones de métodos.Un punto de mitigación mencionado en el artículo, y al autor no le gustó esto personalmente, fue crear una excepción de clase base, limitar sus
throws
cláusulas para usarlo solo y luego aumentar las subclases internamente. De esa manera, puede crear nuevos tipos de excepción marcados sin tener que ejecutar todo su código.Puede que al autor de este artículo no le haya gustado tanto, pero tiene mucho sentido en mi experiencia personal (especialmente si puedes buscar cuáles son todas las subclases de todos modos), y apuesto a que es por eso que el consejo que te dan es limite todo a un tipo de excepción marcado cada uno. Además, el consejo que mencionó en realidad permite múltiples tipos de excepciones marcadas en métodos no públicos, lo que tiene mucho sentido si este es su motivo (o incluso de otro modo). Si es solo un método privado o algo similar de todos modos, no habrá pasado por la mitad de su base de código cuando cambie una pequeña cosa.
Preguntó en gran medida si este es un estándar aceptado, pero entre su investigación que mencionó, este artículo razonablemente pensado, y hablando solo de la experiencia de programación personal, ciertamente no parece destacarse de ninguna manera.
fuente
Throwable
y terminar con ellos, en lugar de inventar toda su propia jerarquía?Lanzar varias excepciones marcadas tiene sentido cuando hay varias cosas razonables que hacer.
Por ejemplo, digamos que tienes un método
esto viola la regla de excepción de pne, pero tiene sentido.
Desafortunadamente, lo que sucede habitualmente son métodos como
Aquí hay pocas posibilidades de que la persona que llama haga algo específico según el tipo de excepción. Entonces, si queremos empujarlo para que se dé cuenta de que estos métodos PUEDEN y a veces IRÁN mal, arrojar SomethingMightGoWrongException es suficiente y mejor.
Por lo tanto, regla como máximo una excepción marcada.
Pero si su proyecto utiliza un diseño donde hay múltiples excepciones marcadas y significativas, esta regla no debería aplicarse.
Nota al margen: ¿Algo puede salir mal en casi todas partes, por lo que uno puede pensar en usarlo? extiende RuntimeException, pero hay una diferencia entre "todos cometemos errores" y "esto habla del sistema externo y, a veces, estará inactivo, lidia con él".
fuente