programacion - ¿Es mejor en Java realizar todas las comprobaciones necesarias de la persona que llama o en el método al que llama?
que es guis en java (11)
Veamos estos dos ejemplos.
Primero:
try {
execute(testObj);
} catch(Exception e) {
//do somethingwith that
}
public void execute(TestObj testObj) throws Exception {
if (testObj == null){
throw new Exception("No such object");
}
//do something with object
}
Segundo:
if (testObj != null){
execute(testObj);
} else {
//handle this differently
}
public void execute(TestObj testObj) {
//do something with object
}
No es el punto ahora mismo si tenemos que marcar "es nulo" o cualquier otra cosa. Quiero saber qué práctica es mejor en general: " verificar, luego hacer " o " hacer, luego manejar la excepción si ocurre ".
A proposito:
Si está creando una biblioteca, solo está proporcionando API en lugar de algún código ejecutable. No sabe si los usuarios realizarán la verificación del llamante, por lo que puede (no necesariamente, a veces si la declaración cumple su cometido) lanzar excepciones a su método para garantizar la solidez.
Si está creando una aplicación, la segunda forma es mucho mejor. Es una mala práctica usar la cláusula try-catch solo para verificar condiciones simples.
De hecho, las declaraciones de lanzamiento de excepciones y los cheques if-if pueden coexistir y lo recomiendo. Pero en su código, debe evitar causar excepciones como pueda como sea posible.
Por uso:
Se lanzan excepciones cuando algunas situaciones no son esperadas pero podrían suceder. Normalmente es útil para propósitos de depuración porque puede rastrear la pila evaluando la instancia de Exception
.
La cláusula if es útil en todos los casos. De hecho, las declaraciones de throw
menudo se envuelven en una cláusula if
. Significa que no desea (o no puede) lidiar con esa condición, simplemente lo deja en manos del depurador (administrador de excepciones). O en su lugar puedes escribir tu código como quieras.
En la mayoría de los casos, le gustaría lidiar con todas las condiciones, aunque algunas son menos posibles. Como dije, intente lanzar la menor cantidad de excepciones posible, no solo por cuestiones de rendimiento, sino también por la comodidad de uso.
Un ejemplo (malo) para mostrar que a veces las excepciones son inútiles:
public void method1 () throws Exception1, Exception2 {
if (condition) {
throw new Exception1("condition match");
} else {
throw new Exception2("condition mismatch");
}
}
public void method2 () {
try {
method1();
} catch (Exception1 e1) {
// do something
} catch (Exception2 e2) {
// do something
}
}
Normalmente, no escribirá código como este, pero como puede ver, un método se divide en dos y method1 casi no hace nada. Es lo mismo con su primer ejemplo, nunca divida el código que pertenece a un método en diferentes lugares.
Como dijo @LieRyan, solo debes verificar los límites entre los sistemas. Pero una vez que esté dentro de su propio código, aún necesita poder detectar problemas inesperados, principalmente porque:
- Usted (y sus compañeros de trabajo) no son perfectos y pueden pasar a un método que no puede manejarlo.
- si una línea usa dos objetos y el otro es nulo, el NPE no le dirá cuál es el culpable (System.out.println ("a.getName ()" + "b.getName ()") arroja NPE ... es una nula o b nula o ambas?
Documente claramente qué métodos aceptan nulos como parámetros o pueden devolver nulos.
Una buena herramienta que puede ayudarlo, si está utilizando Eclipse Juno, (creo que IntelliJ-Idea tiene algo similar) es habilitar el análisis de comprobación nula. Te permite escribir algunas anotaciones para que el tiempo de compilación sea nulo. Es realmente impresionante Puedes escribir algo como
public @NonNull String method(@Nullable String a){
//since a may be null, you need to make the check or code will not compile
if(a != null){
return a.toUppercase();
}
return ""; //if you write return null it won''t compile,
//because method is marked NonNull
}
También tiene un buen @NonNullByDefault, que básicamente dice "Ningún método acepta o devuelve un valor nulo a menos que esté marcado en @Nullable" Este es un buen valor por defecto, mantiene su código limpio y seguro.
Para más información, consulte la ayuda de Eclipse.
De acuerdo con Joshua Bloch
Effective Java Second Edition
, artículo 38 , es una mejor manera de verificar la validez de los parámetros dentro de public
métodos public
. Entonces, el primer enfoque es mucho mejor, excepto el hecho de que, en esta situación, debe lanzar básicamente NullPointerException
, que es RuntimeException
:
// do not need try...catch due to RuntimeException
execute(testObj);
public void execute(TestObj testObj) {
if (testObj == null){
throw new NullPointerException("No such object");
}
;//do smth with object
}
Depende. Ambos enfoques son lo suficientemente buenos. Primero es bueno cuando su función será utilizada por otros clientes. En este caso, sus controles evitan fallos debido a argumentos ilegales. Como autor, usted garantiza que su código funciona bien incluso en caso de argumentos de entrada incorrectos inesperados. El segundo se puede usar cuando usted no es un autor de la función o no quiere manejar las excepciones generadas por el método.
El segundo enfoque es mejor, porque es más fácil de leer y mantener.
También debe tener en cuenta que lanzar excepciones a veces solo mueve su problema de A a B y no resuelve el problema.
El segundo es mejor que en el primero, cualquier excepción podría dispararse y usted pensaría que esto es lo que se espera.
Tenga en cuenta que el uso de excepciones para controlar el flujo del programa es computacionalmente costoso, ya que construir el rendimiento de costos de excepción. Por ejemplo, en hibernación, es mejor hacer un SELECT COUNT(*)
para verificar si existe una fila que ver si se dispara una recordNotFoundException y basar la lógica de la aplicación en la activación de esa excepción.
Es mejor hacerlo desde la persona que llama porque puede, por ejemplo, si los datos son proporcionados por el usuario, solicitar datos nuevos si no son adecuados o no llamar a la función si los datos no son buenos. Por lo tanto, puede mantener su método "limpio" y ese método determinado solo realizará su función de propósito simple. Por lo tanto, será más fácilmente transferible y reutilizable en otras áreas. Puede, si los datos proporcionados al método para lanzar una excepción.
Por ejemplo, si es un método que escribe datos en un archivo y obtiene la ruta del archivo como entrada. Puede lanzar una excepción FileNotFound
.
De esa manera, el programador que utilice su método sabrá que debe proporcionar una ruta de archivo adecuada y realizar todas las comprobaciones necesarias de antemano.
Las excepciones deben usarse para exceptional conditions
; cosas que no esperas que sucedan. Validar la entrada no es muy excepcional.
Tampoco, solo debes hacer una comprobación de los límites entre los sistemas .
¿Qué son los límites entre el sistema?
- Cuando recibe la entrada del usuario,
- Cuando lees un archivo, red o socket,
- Cuando haces llamadas al sistema o interpretes comunicación,
- En los códigos de la biblioteca, en todas las API de cara al público.
- etc.
En el código de la biblioteca, dado que las API públicas pueden ser llamadas por un código externo, siempre debe verificar cualquier cosa que pueda ser llamada por un código externo. No hay necesidad de verificar un método solo interno (incluso si su modificador de acceso es público). Los errores de argumento pueden ser señalados por una excepción o por un código de retorno dependiendo del gusto personal, sin embargo, la excepción marcada no se puede ignorar, por lo que generalmente es el método preferido para señalar errores a códigos externos.
En un código de aplicación (a diferencia de una biblioteca), la comprobación solo debe realizarse cuando se recibe información del usuario, cuando se carga una URL, cuando se lee un archivo, etc. No es necesario realizar una comprobación de entrada en los métodos públicos porque son solo alguna vez llamado por su propio código de aplicación.
Dentro de su propio código, debe evitar la situación en la que necesita verificar en primer lugar. Por ejemplo, en lugar de verificar si es null
, puede usar "patrón de objeto nulo" . Sin embargo, si aún necesita hacer una verificación, hágalo lo antes posible, esto generalmente significa hacerlo afuera, pero trate de encontrar puntos aún más antiguos.
Incluso si no está escrito formalmente e incluso si no se aplica, todos los métodos, ya sean internos o públicos, deben tener un contract . La diferencia es que en el código externo, debe hacer cumplir el contrato como parte de su comprobación de errores, mientras que en el código solo interno puede y debe confiar en que la persona que llama sepa qué pasar y qué no pasar.
En resumen, dado que la verificación solo se realiza en los límites del sistema, si realmente tiene la opción de verificar dentro o fuera de un método, es probable que no deba verificarlo ya que no es un límite del sistema .
En un límite del sistema, ambos lados siempre tienen que verificar . La persona que llamó tuvo que verificar que la llamada externa se haya realizado correctamente y la persona que llamó tuvo que verificar que la persona que llama da una entrada / argumento sensato. Ignorar un error en la persona que llama es casi siempre un error. Se aplica el principio de robustez , siempre verifique todo lo que reciba del sistema externo, pero solo envíe lo que sabe que el sistema externo puede aceptar con seguridad.
Sin embargo, en proyectos muy grandes, a menudo es necesario compartimentar el proyecto en pequeños módulos. En este caso, los otros módulos del proyecto pueden tratarse como sistemas externos y, por lo tanto, debe realizar las comprobaciones en las API que están expuestas a los otros módulos del proyecto.
TLDR; el chequeo es dificil
Uno debe esforzarse, siempre que sea posible, para garantizar que si se lanza una excepción desde un método, la operación fallida no tendrá efectos secundarios. Si bien no hay medios estándar en Java o .net a través de los cuales una excepción pueda indicar que representa un "fallo sin efectos secundarios", hay muchos escenarios (por ejemplo, intentar deserializar una estructura de datos) donde es difícil predecir todos los tipos de fallas pueden ocurrir (por ejemplo, como resultado de intentar cargar un archivo dañado) pero donde casi todos los tipos de fallas sin efectos secundarios pueden manejarse de la misma manera (por ejemplo, informar al usuario que no se pudo cargar un documento) , muy probablemente porque está corrupto o en el formato incorrecto).
Incluso si un método se utilizará solo dentro del propio código, a menudo es bueno validar los parámetros antes de hacer algo con ellos que pueda afectar negativamente a otro código. Por ejemplo, supongamos que el código utiliza una lista ordenada perezosamente (es decir, mantiene una lista que puede o no ordenarse, junto con una bandera que indica si está ordenada o no); al agregar un elemento a la lista, simplemente lo agrega al final y establece el indicador de "clasificación requerida". La rutina de "agregar elemento" podría completarse "con éxito" si se le dio una referencia nula o un objeto que no se pudo comparar con los elementos de la lista, pero agregar un elemento de este tipo causaría que las operaciones de clasificación posteriores falle. Incluso si el método "agregar elemento" no tuviera que preocuparse por si el elemento era válido, sería mucho mejor que el método "agregar elemento" arrojara una excepción, que dejar la lista en un estado en el que las operaciones posteriores falla (en ese momento, incluso si uno descubriera que la ordenación falló debido a un elemento nulo en la lista, es posible que no haya forma de determinar cómo llegó allí).
Luego, el código de resolución de problemas, si el método foo
recibe un valor de parámetro que se pasará inmediatamente a la bar
y hará que la bar
lance, tener la excepción lanzada por foo
lugar de la bar
puede ser algo útil, pero el escenario donde la excepción terminará el lanzamiento esencialmente inmediato y sin efectos secundarios, incluso si se realiza mediante un método anidado, es mucho menos importante que los escenarios en los que foo
podría haber causado algunos efectos secundarios antes de lanzar una excepción o, lo que es peor, completar "normalmente" pero hacer que las operaciones futuras fracasen.
Yo diría que depende del tipo de excepción. Si la excepción se reduce a argumentos no válidos, verifíquelos en el método. De lo contrario, deje que la persona que llama lo maneje.
Ejemplo:
public void doSomething(MyObject arg1, MyObject arg2) throw SomeException {
if (arg1==null || arg2==null) {
throw new IllegalArgumentException(); //unchecked
}
// do something which could throw SomeException
return;
}
vocación:
try {
doSomething(arg1, arg2);
} catch (SomeException e) {
//handle
}