java - Cómo rastrear una NullPointerException en una cadena de getters
debugging (11)
Aquí se explica cómo encontrar el error, usando Eclipse.
Primero, establezca un punto de interrupción en la línea:
someObject.getSomething().getSomethingElse().
getAnotherThing().getYetAnotherObject().getValue();
Ejecute el programa en modo de depuración, permita que el depurador cambie a su perspectiva cuando se golpea la línea.
Ahora, resalte "someObject" y presione CTRL + SHIFT + I (o haga clic con el botón derecho y diga "inspeccionar").
¿Es nulo? Has encontrado tu NPE. ¿Es no nulo? A continuación, resalte algún Objeto.getSomething () (incluidos los paréntesis) e inspecciónelo. ¿Es nulo? Etc. Continúe por la cadena para averiguar dónde está ocurriendo su NPE, sin tener que cambiar su código.
Si obtengo una NullPointerException en una llamada como esta:
someObject.getSomething().getSomethingElse().
getAnotherThing().getYetAnotherObject().getValue();
Obtengo un texto de excepción bastante inútil como:
Exception in thread "main" java.lang.NullPointerException
at package.SomeClass.someMethod(SomeClass.java:12)
Me resulta bastante difícil saber qué es lo que llaman realmente returend null, a menudo encontrándome refaccionando el código a algo como esto:
Foo ret1 = someObject.getSomething();
Bar ret2 = ret1.getSomethingElse();
Baz ret3 = ret2.getAnotherThing();
Bam ret4 = ret3.getYetAnotherOject();
int ret5 = ret4.getValue();
y luego esperando una NullPointerException más descriptiva que me diga qué línea buscar.
Algunos de ustedes podrían argumentar que los getters concatenantes son de mal estilo y deberían evitarse de todos modos, pero mi pregunta es: ¿puedo encontrar el error sin cambiar el código?
Sugerencia: estoy usando eclipse y sé lo que es un depurador, pero no puedo determinar cómo aplicarlo al problema.
Mi conclusión sobre las respuestas:
Algunas respuestas me dijeron que no debería encadenar captadores uno tras otro, algunas respuestas me mostraron cómo depurar mi código si desobedecía ese consejo.
He aceptado una respuesta que me enseñó explícitamente cuándo encadenar getters:
- Si no pueden devolver el valor nulo, conéctelos todo el tiempo que desee. ¡No es necesario verificar! = Nulo, no hay necesidad de preocuparse por NullPointerExceptions ( tenga en cuenta que el encadenamiento sigue siendo una ley de demeter, pero puedo vivir con eso )
- Si pueden devolver un valor nulo, no lo haga nunca, nunca lo encadene, y realice una comprobación de valores nulos en cada uno que puedan devolver valores nulos.
Esto hace que cualquier buen consejo sobre depuración real sea inútil.
Coloque cada getter en su propia línea y depure. Paso a paso (F6) cada método para encontrar qué llamada devuelve nulo
El fracaso temprano también es una opción.
En cualquier parte de su código que pueda devolver un valor nulo, considere la posibilidad de introducir un cheque para un valor de retorno nulo.
public Foo getSomething()
{
Foo result;
...
if (result == null) {
throw new IllegalStateException("Something is missing");
}
return result;
}
En IntelliJ IDEA puede establecer exceptionbreakpoints . Esos puntos de interrupción se disparan siempre que se lanza una excepción específica (puede extender el alcance a un paquete o una clase).
De esa forma, debería ser fácil encontrar la fuente de su NPE.
Supongo que puedes hacer algo similar en netbeans o eclipse.
EDITAR: Here hay una explicación sobre cómo agregar un punto de excepción en eclipse
Es posible que desee consultar esta pregunta sobre cómo evitar! = Null .
Básicamente, si null es una respuesta válida, debes verificarla. Si no, asegúralo (si puedes). Pero haga lo que haga, intente minimizar los casos en que null es una respuesta válida para esto, entre otras razones.
La respuesta depende de cómo vea (el contrato de) sus compradores. Si pueden devolver null
, realmente debería verificar el valor de retorno cada vez. Si el getter no devuelve null
, el getter debería contener un cheque y lanzar una excepción ( IllegalStateException
?) En lugar de devolver null
, que prometió que nunca devolvería. La stacktrace te indicará el getter exacto. Incluso podría poner el estado inesperado encontrado en el mensaje de excepción.
Las expresiones encadenadas como esa son difíciles de depurar para NullPointerExceptions (y la mayoría de los otros problemas que pueden ocurrir), así que te aconsejaría que intentaras evitarlo. Probablemente ya hayas escuchado eso lo suficiente y, como en un cartel anterior mencionado, puedes agregar puntos de corte en la NullPointerException real para ver dónde ocurrió.
En eclipse (y la mayoría de los IDE) también puede usar expresiones de reloj para evaluar el código que se ejecuta en el depurador. Haga esto seleccionando el código y use el menú contextual para agregar un nuevo reloj.
Si tiene el control del método que devuelve nulo, también podría considerar el patrón Objeto nulo si nulo es un valor válido para devolver.
NPE es la excepción más inútil en Java, punto. Parece que siempre se implementa de forma perezosa y nunca dice exactamente qué lo causó, incluso tan simple como "la clase xyZ es nula" ayudaría mucho en la depuración de dichos casos.
De todos modos, la única buena manera que he encontrado para encontrar al lanzador de NPE en estos casos es el siguiente tipo de refactorización:
someObject.getSomething()
.getSomethingElse()
.getAnotherThing()
.getYetAnotherObject()
.getValue();
Ahí lo tienes, ahora NPE señala la línea correcta y, por lo tanto, el método correcto que arrojó el NPE real. No es una solución tan elegante como me gustaría que fuera, pero funciona.
Por lo general, no utilizo getters de cadena como este donde hay más de un getter nullable.
Si está ejecutando dentro de su ide, puede establecer un punto de interrupción y usar la funcionalidad "evaluar la expresión" de su ide en cada elemento sucesivamente.
Pero te rascarás la cabeza en cuanto recibas este mensaje de error de los registros de tu servidor de producción. Así que lo mejor es mantener un máximo de un elemento con nulos por línea.
Mientras tanto, podemos soñar con el operador de navegación segura de Groovy
Si te encuentras escribiendo a menudo:
a.getB().getC().getD().getE();
esto es probablemente un olor a código y debe evitarse. Puede refactorizar, por ejemplo, a a.getE()
que llama a b.getE()
que llama a c.getE()
que llama a d.getE()
. (Este ejemplo puede no tener sentido para su caso de uso particular, pero es un patrón para corregir el olor de este código).
Ver también la Ley de Demeter , que dice:
- Su método puede llamar a otros métodos en su clase directamente
- Su método puede llamar directamente a los métodos en sus propios campos (pero no en los campos de los campos)
- Cuando su método toma parámetros, su método puede llamar métodos directamente sobre esos parámetros.
- Cuando su método crea objetos locales, ese método puede llamar a métodos en los objetos locales.
Por lo tanto, uno no debería tener una cadena de mensajes, por ejemplo, a.getB().getC().doSomething()
. Seguir esta "ley" tiene muchos más beneficios aparte de hacer que NullPointerExceptions sea más fácil de depurar.
Si tiene que llegar al punto en el que está dividiendo la línea o realizando una depuración elaborada para detectar el problema, entonces esa es, en general, la forma en que Dios le dice que su código no está comprobando el nulo lo suficientemente temprano.
Si tiene un método o un constructor que toma un parámetro de objeto y el objeto / método en cuestión no puede lidiar razonablemente con que ese parámetro sea nulo, entonces simplemente verifique y arroje una NullPointerException en ese momento.
He visto a personas inventar reglas de "estilo de codificación" para tratar de resolver este problema, como "no tienes permitido más de un punto en una línea". Pero esto solo fomenta la programación que detecta el error en el lugar equivocado.