Un servicio web devuelve un XML enorme y necesito acceder a campos profundamente anidados. Por ejemplo:
return wsObject.getFoo().getBar().getBaz().getInt()
El problema es que getFoo()
, getBar()
, getBaz()
puede volver todos null
.
Sin embargo, si verifico null
en todos los casos, el código se vuelve muy detallado y difícil de leer. Además, puedo perder los cheques de algunos de los campos.
if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
Es aceptable escribir
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
return -1;
}
¿O eso sería considerado un antipatrón?
java
exception
nullpointerexception
null
custom-error-handling
David Frank
fuente
fuente
null
cheques, ya quewsObject.getFoo().getBar().getBaz().getInt()
es un olor a código. Lea lo que es la "Ley de Demeter" y prefiera refactorizar su código en consecuencia. Entonces el problema con losnull
cheques desaparecerá también. Y piensa en usarOptional
.wsdl2java
, que no respeta la Ley de Demeter.Respuestas:
La captura
NullPointerException
es algo realmente problemático, ya que pueden ocurrir en casi cualquier lugar. Es muy fácil sacar uno de un error, atraparlo por accidente y continuar como si todo fuera normal, ocultando así un problema real. Es tan complicado de manejar que es mejor evitarlo por completo. (Por ejemplo, piense en el desempaquetado automático de un nuloInteger
).Sugiero que uses la
Optional
clase en su lugar. Este suele ser el mejor enfoque cuando desea trabajar con valores presentes o ausentes.Usando eso, podrías escribir tu código así:
¿Por qué opcional?
El uso de
Optional
s en lugar denull
para valores que podrían estar ausentes hace que ese hecho sea muy visible y claro para los lectores, y el sistema de tipos se asegurará de que no lo olvide accidentalmente.También obtiene acceso a métodos para trabajar con dichos valores de manera más conveniente, como
map
yorElse
.¿La ausencia es válida o es un error?
Pero también piense si es un resultado válido para que los métodos intermedios devuelvan nulo o si eso es un signo de error. Si siempre es un error, entonces probablemente sea mejor lanzar una excepción que devolver un valor especial, o que los propios métodos intermedios lancen una excepción.
¿Quizás más opcionales?
Si, por otro lado, los valores ausentes de los métodos intermedios son válidos, tal vez pueda cambiar a
Optional
s para ellos?Entonces podrías usarlos así:
¿Por qué no opcional?
La única razón en la que puedo pensar para no usar
Optional
es si se trata de una parte del código realmente crítica para el rendimiento, y si la sobrecarga de recolección de basura resulta ser un problema. Esto se debe a unosOptional
objetos se asignan cada vez que se ejecuta el código, y la máquina virtual podría no ser capaz de optimizar los distancia. En ese caso, sus pruebas if originales podrían ser mejores.fuente
Try
lugar deOptional
. A pesar de que no estáTry
en la API de Java, hay muchas bibliotecas que proporcionan una, por ejemplo javaslang.io , github.com/bradleyscollins/try4j , functionaljava.org o github.com/jasongoodwin/better-java-monadsFClass::getBar
etc sería más corto.static
método, que incurrirá en una penalización mínima.Sugiero considerarlo
Objects.requireNonNull(T obj, String message)
. Puede crear cadenas con un mensaje detallado para cada excepción, comoLe sugiero que no use valores de retorno especiales, como
-1
. Ese no es un estilo Java. Java ha diseñado el mecanismo de excepciones para evitar esta forma anticuada que provenía del lenguaje C.Lanzar
NullPointerException
tampoco es la mejor opción. Puede proporcionar su propia excepción ( marcándola para garantizar que será manejada por un usuario o desmarcada para procesarla de una manera más fácil) o usar una excepción específica del analizador XML que está utilizando.fuente
Objects.requireNonNull
eventualmente lanzaNullPointerException
. Así que esto no hace que la situación diferente quereturn wsObject.getFoo().getBar().getBaz().getInt()
if
Optional
clase, o tal vez devolver un valor anulableInteger
Suponiendo que la estructura de clases está fuera de nuestro control, como parece ser el caso, creo que detectar la NPE como se sugiere en la pregunta es de hecho una solución razonable, a menos que el rendimiento sea una preocupación importante. Una pequeña mejora podría ser ajustar la lógica de lanzar / atrapar para evitar el desorden:
Ahora puedes simplemente hacer:
fuente
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");
no da un error en el tiempo de compilación que podría ser problemático.Como ya señaló Tom en el comentario,
La siguiente declaración desobedece la Ley de Deméter ,
Lo que quieres es
int
y puedes conseguirloFoo
. Law of Demeter dice, nunca hables con extraños . Para su caso, puede ocultar la implementación real bajo el capó deFoo
yBar
.Ahora, puede crear método en el
Foo
que se ha podido irint
aBaz
. En definitiva,Foo
tendremosBar
y enBar
nosotros podremos accederInt
sin exponernosBaz
directamente aFoo
. Por lo tanto, las comprobaciones nulas probablemente se dividen en diferentes clases y solo los atributos requeridos se compartirán entre las clases.fuente
null
verificación de sus propias etiquetas secundarias.Mi respuesta va casi en la misma línea que @janki, pero me gustaría modificar el fragmento de código ligeramente como se muestra a continuación:
También puede agregar una verificación nula
wsObject
, si existe alguna posibilidad de que ese objeto sea nulo.fuente
Dice que algunos métodos "pueden regresar
null
" pero no dice en qué circunstancias regresannull
. Dices que lo atrapasNullPointerException
pero no dices por qué lo atrapas. Esta falta de información sugiere que no tiene una comprensión clara de para qué sirven las excepciones y por qué son superiores a la alternativa.Considere un método de clase que está destinado a realizar una acción, pero el método no puede garantizar que realizará la acción, debido a circunstancias fuera de su control (que de hecho es el caso de todos los métodos en Java ). Llamamos a ese método y regresa. El código que llama a ese método necesita saber si tuvo éxito. ¿Cómo puede saberlo? ¿Cómo puede estructurarse para hacer frente a las dos posibilidades, el éxito o el fracaso?
Usando excepciones, podemos escribir métodos que tengan éxito como condición posterior . Si el método regresa, fue exitoso. Si lanza una excepción, ha fallado. Esta es una gran victoria por claridad. Podemos escribir código que procese claramente el caso de éxito normal y mover todo el código de manejo de errores a
catch
cláusulas. A menudo resulta que los detalles de cómo o por qué un método no tuvo éxito no son importantes para la persona que llama, por lo que la mismacatch
cláusula se puede usar para manejar varios tipos de fallas. Y a menudo sucede que un método no necesita detectar excepciones en absoluto , sino que solo puede permitir que se propaguen a su llamador. Las excepciones debidas a errores del programa se encuentran en esta última clase; pocos métodos pueden reaccionar adecuadamente cuando hay un error.Entonces, esos métodos que regresan
null
.null
valor indica un error en su código? Si es así, no debería detectar la excepción en absoluto. Y su código no debería intentar adivinarse. Simplemente escriba lo que sea claro y conciso asumiendo que funcionará. ¿Es una cadena de llamadas a métodos clara y concisa? Entonces solo úsalas.null
valor indica una entrada no válida a su programa? Si lo hace,NullPointerException
no es una excepción apropiada para lanzar, porque convencionalmente está reservado para indicar errores. Probablemente desee lanzar una excepción personalizada derivada deIllegalArgumentException
(si desea una excepción sin marcar ) oIOException
(si desea una excepción marcada). ¿Es necesario que su programa proporcione mensajes de error de sintaxis detallados cuando hay una entrada no válida? Si es así, lonull
único que puede hacer es verificar cada método en busca de un valor de retorno y luego lanzar una excepción de diagnóstico adecuada. Si su programa no necesita proporcionar diagnósticos detallados, encadenar las llamadas al método juntas, detectar cualquieraNullPointerException
y luego lanzar su excepción personalizada es más claro y conciso.Una de las respuestas afirma que las llamadas al método encadenado violan la Ley de Demeter y, por lo tanto, son malas. Esa afirmación está equivocada.
fuente
Para mejorar la legibilidad, es posible que desee utilizar varias variables, como
fuente
No lo atrapes
NullPointerException
. No sabes de dónde viene (sé que no es probable en tu caso, pero quizás algo más lo arrojó) y es lento. Desea acceder al campo especificado y, para ello, todos los demás campos no deben ser nulos. Esta es una razón perfecta y válida para comprobar todos los campos. Probablemente lo verificaría en uno y luego crearía un método de legibilidad. Como otros señalaron, regresar -1 es muy antiguo, pero no sé si tiene una razón para ello o no (por ejemplo, hablando con otro sistema).Editar: Es discutible si desobedece la Ley de Demeter, ya que WsObject es probablemente solo una estructura de datos (consulte https://stackoverflow.com/a/26021695/1528880 ).
fuente
Si no desea refactorizar el código y puede usar Java 8, es posible usar referencias de métodos.
Primero una demostración simple (disculpe las clases internas estáticas)
Salida
La interfaz
Getter
es solo una interfaz funcional, puede usar cualquier equivalente.GetterResult
class, descriptores de acceso eliminados para mayor claridad, contienen el resultado de la cadena de captadores, si existe, o el índice del último captador llamado.El método
getterChain
es una pieza de código simple y repetitiva que se puede generar automáticamente (o manualmente cuando sea necesario).Estructuré el código para que el bloque repetido sea evidente.
Esta no es una solución perfecta ya que aún necesita definir una sobrecarga de
getterChain
por número de captadores.En su lugar, refactorizaría el código, pero si no puede y se encuentra usando cadenas de captadores largas, a menudo puede considerar construir una clase con las sobrecargas que llevan de 2 a, digamos, 10 captadores.
fuente
Como han dicho otros, respetar la Ley de Deméter es definitivamente parte de la solución. Otra parte, siempre que sea posible, es cambiar esos métodos encadenados para que no puedan regresar
null
. Puede evitar regresar devolviendonull
en su lugar un objeto vacíoString
, vacíoCollection
o algún otro objeto ficticio que signifique o haga lo que haría la persona que llamanull
.fuente
Me gustaría agregar una respuesta que se centre en el significado del error . La excepción nula en sí misma no proporciona ningún significado de error completo. Así que le aconsejo que evite tratar con ellos directamente.
Hay miles de casos en los que su código puede salir mal: no se puede conectar a la base de datos, excepción de E / S, error de red ... Si los trata uno por uno (como la comprobación nula aquí), sería demasiado complicado.
En el código:
Incluso cuando sabe qué campo es nulo, no tiene idea de lo que sale mal. Quizás Bar sea nulo, pero ¿se espera? ¿O es un error de datos? Piense en las personas que leen su código
Como en la respuesta de xenteros, propondría usar una excepción personalizada sin marcar . Por ejemplo, en esta situación: Foo puede ser nulo (datos válidos), pero Bar y Baz nunca deben ser nulos (datos no válidos)
El código se puede reescribir:
fuente
NullPointerException
es una excepción en tiempo de ejecución, por lo que, en general, no se recomienda detectarlo, sino evitarlo.Tendrá que capturar la excepción donde quiera que desee llamar al método (o se propagará por la pila). No obstante, si en tu caso puedes seguir trabajando con ese resultado con valor -1 y estás seguro de que no se propagará porque no estás usando ninguna de las "piezas" que pueden ser nulas, entonces me parece correcto Atrapalo
Editar:
Estoy de acuerdo con la respuesta posterior de @xenteros, será mejor lanzar su propia excepción en lugar de devolver -1, puede llamarlo,
InvalidXMLException
por ejemplo.fuente
He estado siguiendo esta publicación desde ayer.
He estado comentando / votando los comentarios que dicen, atrapar NPE es malo. He aquí por qué lo he estado haciendo.
Salida
3.216
0,002
Veo un claro ganador aquí. Tener cheques si es mucho menos costoso que detectar una excepción. He visto esa forma de hacer Java-8. Teniendo en cuenta que el 70% de las aplicaciones actuales todavía se ejecutan en Java-7, estoy agregando esta respuesta.
Conclusión Para cualquier aplicación de misión crítica, el manejo de NPE es costoso.
fuente
Si la eficiencia es un problema, entonces se debe considerar la opción de 'captura'. Si 'catch' no se puede usar porque se propagaría (como lo menciona 'SCouto'), use variables locales para evitar múltiples llamadas a métodos
getFoo()
,getBar()
ygetBaz()
.fuente
Vale la pena considerar la posibilidad de crear su propia excepción. Llamémoslo MyOperationFailedException. Puedes lanzarlo en lugar de devolver un valor. El resultado será el mismo: saldrá de la función, pero no devolverá el valor -1 codificado de forma rígida, que es un anti-patrón de Java. En Java usamos Excepciones.
EDITAR:
De acuerdo con la discusión en los comentarios, permítanme agregar algo a mis pensamientos anteriores. En este código hay dos posibilidades. Uno es que aceptas nulo y el otro es que es un error.
Si es un error y se produce, puede depurar su código utilizando otras estructuras con fines de depuración cuando los puntos de interrupción no sean suficientes.
Si es aceptable, no le importa dónde apareció este nulo. Si lo hace, definitivamente no debería encadenar esas solicitudes.
fuente
El método que tiene es extenso, pero muy legible. Si fuera un desarrollador nuevo que llegara a su base de código, podría ver lo que estaba haciendo con bastante rapidez. La mayoría de las otras respuestas (incluida la detección de la excepción) no parecen hacer las cosas más legibles y algunas lo hacen menos legible en mi opinión.
Dado que es probable que no tenga control sobre la fuente generada y asumiendo que realmente solo necesita acceder a algunos campos profundamente anidados aquí y allá, recomendaría envolver cada acceso profundamente anidado con un método.
Si se encuentra escribiendo muchos de estos métodos o si se siente tentado a hacer estos métodos estáticos públicos, entonces crearía un modelo de objeto separado, anidado como le gustaría, con solo los campos que le interesan, y convertiría desde la web. modelo de objetos de servicios a su modelo de objetos.
Cuando se comunica con un servicio web remoto, es muy típico tener un "dominio remoto" y un "dominio de aplicación" y cambiar entre los dos. El dominio remoto a menudo está limitado por el protocolo web (por ejemplo, no puede enviar métodos auxiliares de un lado a otro en un servicio RESTful puro y los modelos de objetos profundamente anidados son comunes para evitar múltiples llamadas API) y, por lo tanto, no es ideal para el uso directo en tu cliente.
Por ejemplo:
fuente
aplicando la Ley de Demeter,
fuente
Dar una respuesta que parece diferente a todas las demás.
Razón:
Para que su código sea fácil de leer, intente esto para verificar las condiciones:
EDITAR:
Cualquier sugerencia será apreciada..!!
fuente
wsObject
contendrá el valor devuelto por Webservice .. !! El Servicio ya se llamará ywsObject
obtendrá una gran cantidad deXML
datos como respuesta del servicio web .. !! Así que no hay nada como un servidor ubicado en otro continente porquegetFoo()
es solo un elemento que obtiene un método getter, no una llamada a un servicio web .. !! @xenterosEscribí una clase llamada
Snag
que te permite definir una ruta para navegar a través de un árbol de objetos. A continuación se muestra un ejemplo de su uso:Lo que significa que la instancia
ENGINE_NAME
llamaría efectivamenteCar?.getEngine()?.getName()
a la instancia que se le pasó y devolveríanull
si se devolviera alguna referencianull
:No está publicado en Maven, pero si alguien lo encuentra útil, está aquí. (¡sin garantía, por supuesto!)
Es un poco básico, pero parece funcionar. Obviamente, es más obsoleto con versiones más recientes de Java y otros lenguajes JVM que admiten navegación segura o
Optional
.fuente