Um serviço da web retorna um XML enorme e preciso acessar campos profundamente aninhados dele. Por exemplo:
return wsObject.getFoo().getBar().getBaz().getInt()
O problema é que getFoo()
, getBar()
, getBaz()
tudo pode retorno null
.
No entanto, se eu verificar null
em todos os casos, o código se torna muito prolixo e difícil de ler. Além disso, posso perder as verificações de alguns dos 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();
É aceitável escrever
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
return -1;
}
ou isso seria considerado um antipadrão?
java
exception
nullpointerexception
null
custom-error-handling
David Frank
fonte
fonte
null
verificações, poiswsObject.getFoo().getBar().getBaz().getInt()
já é um cheiro de código. Leia o que é a "Lei de Demeter" e prefira refatorar seu código de acordo. Então, o problema com osnull
cheques também desaparecerá. E pense em usarOptional
.wsdl2java
, que não respeita a Lei de Demeter.Respostas:
Pegar
NullPointerException
é uma coisa realmente problemática, pois pode acontecer em quase qualquer lugar. É muito fácil pegar um de um bug, pegá-lo sem querer e continuar como se tudo estivesse normal, escondendo assim um problema real. É tão complicado lidar com isso, então é melhor evitar completamente. (Por exemplo, pense no desempacotamento automático de um nuloInteger
.)Eu sugiro que você use a
Optional
classe em vez disso. Muitas vezes, essa é a melhor abordagem quando você deseja trabalhar com valores que estão presentes ou ausentes.Usando isso, você poderia escrever seu código assim:
Por que opcional?
Usar
Optional
s em vez denull
para valores que podem estar ausentes torna esse fato muito visível e claro para os leitores, e o sistema de tipos garantirá que você não o esqueça acidentalmente.Você também obtém acesso a métodos para trabalhar com esses valores de forma mais conveniente, como
map
eorElse
.Ausência é válida ou erro?
Mas também pense se é um resultado válido para os métodos intermediários retornarem nulo ou se isso é um sinal de erro. Se for sempre um erro, provavelmente é melhor lançar uma exceção do que retornar um valor especial, ou para os próprios métodos intermediários lançar uma exceção.
Talvez mais opcionais?
Se, por outro lado, os valores ausentes dos métodos intermediários forem válidos, talvez você também possa alternar para
Optional
s para eles?Então você pode usá-los assim:
Por que não opcional?
A única razão que posso pensar para não usar
Optional
é se isso é realmente uma parte crítica de desempenho do código e se a sobrecarga de coleta de lixo acabar sendo um problema. Isso ocorre porque algunsOptional
objetos são alocados cada vez que o código é executado, e a VM pode não ser capaz de otimizá-los. Nesse caso, seus testes de if originais podem ser melhores.fonte
Try
vez deOptional
. Embora não existeTry
na API do Java, há muitas libs proporcionando um, por exemplo javaslang.io , github.com/bradleyscollins/try4j , functionaljava.org ou github.com/jasongoodwin/better-java-monadsFClass::getBar
etc seria mais curto.static
método, que incorrerá em uma penalidade muito pequena.Eu sugiro considerar
Objects.requireNonNull(T obj, String message)
. Você pode construir cadeias com uma mensagem detalhada para cada exceção, comoEu sugiro que você não use valores de retorno especiais, como
-1
. Esse não é um estilo Java. Java projetou o mecanismo de exceções para evitar essa maneira antiquada que veio da linguagem C.Jogar
NullPointerException
também não é a melhor opção. Você poderia fornecer sua própria exceção (tornando-se verificado a garantia de que ele será tratado por um utilizador ou desmarcada para processá-lo em uma maneira mais fácil) ou usar uma exceção específica do analisador XML que você está usando.fonte
Objects.requireNonNull
eventualmente jogaNullPointerException
. Portanto, isso não torna a situação diferente dereturn wsObject.getFoo().getBar().getBaz().getInt()
if
s como OP mostrouOptional
classe, ou talvez retornar um anulávelInteger
Assumindo que a estrutura de classes está realmente fora de nosso controle, como parece ser o caso, acho que pegar o NPE como sugerido na pergunta é de fato uma solução razoável, a menos que o desempenho seja uma preocupação importante. Uma pequena melhoria pode ser envolver a lógica de jogar / pegar para evitar confusão:
Agora você pode simplesmente fazer:
fonte
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");
não fornece um erro no tempo de compilação que pode ser problemático.Como já apontado por Tom no comentário,
A seguinte declaração desobedece a Lei de Deméter ,
O que você quer é
int
e pode obtê-loFoo
. A lei de Deméter diz: nunca fale com estranhos . Para o seu caso, você pode ocultar a implementação real sob o capô deFoo
eBar
.Agora, você pode criar método
Foo
para buscarint
a partirBaz
. Em última análise,Foo
teremosBar
eBar
podemos acessarInt
sem exporBaz
diretamente aFoo
. Portanto, as verificações de nulos provavelmente são divididas em classes diferentes e apenas os atributos necessários serão compartilhados entre as classes.fonte
null
verificação de suas próprias subtags.Minha resposta vai quase na mesma linha que @janki, mas eu gostaria de modificar o snippet de código ligeiramente como abaixo:
Você também pode adicionar uma verificação de nulo para
wsObject
, se houver alguma chance desse objeto ser nulo.fonte
Você diz que alguns métodos "podem retornar
null
", mas não diz em que circunstâncias eles retornamnull
. Você diz que pega o,NullPointerException
mas não diz por que o pega. Essa falta de informação sugere que você não tem um entendimento claro do que servem as exceções e por que são superiores à alternativa.Considere um método de classe destinado a realizar uma ação, mas o método não pode garantir que executará a ação, devido a circunstâncias além de seu controle (o que é de fato o caso para todos os métodos em Java ). Chamamos esse método e ele retorna. O código que chama esse método precisa saber se foi bem-sucedido. Como pode saber? Como pode ser estruturado para lidar com as duas possibilidades, de sucesso ou fracasso?
Usando exceções, podemos escrever métodos que têm sucesso como uma pós-condição . Se o método retornar, foi bem-sucedido. Se ele lançar uma exceção, ele falhou. Esta é uma grande vitória para maior clareza. Podemos escrever código que processa claramente o caso normal de sucesso e mover todo o código de tratamento de erros para
catch
cláusulas. Freqüentemente, acontece que os detalhes de como ou por que um método não teve êxito não são importantes para o chamador, portanto, a mesmacatch
cláusula pode ser usada para lidar com vários tipos de falha. E muitas vezes acontece que um método não precisa capturar exceções em tudo , mas pode apenas permitir que eles se propaguem para o seu interlocutor. As exceções devido a bugs do programa estão nessa última classe; poucos métodos podem reagir apropriadamente quando há um bug.Então, esses métodos que retornam
null
.null
valor indica um bug em seu código? Em caso afirmativo, você não deve capturar a exceção. E seu código não deve tentar adivinhar a si mesmo. Basta escrever o que é claro e conciso, pressupondo que funcionará. Uma cadeia de chamadas de método é clara e concisa? Depois é só usá-los.null
valor indica uma entrada inválida para o seu programa? Em caso afirmativo, a nãoNullPointerException
é uma exceção apropriada para lançar, porque convencionalmente é reservado para indicar bugs. Você provavelmente deseja lançar uma exceção personalizada derivada deIllegalArgumentException
(se quiser uma exceção não verificada ) ouIOException
(se quiser uma exceção marcada). É necessário que seu programa forneça mensagens de erro de sintaxe detalhadas quando houver entrada inválida? Nesse caso, verificar cada método para umnull
valor de retorno e, em seguida, lançar uma exceção de diagnóstico apropriada é a única coisa que você pode fazer. Se o seu programa não precisar fornecer diagnósticos detalhados, encadear as chamadas de método, capturar qualquer umaNullPointerException
e lançar sua exceção personalizada é mais claro e conciso.Uma das respostas afirma que as chamadas de método encadeadas violam a Lei de Demeter e, portanto, são ruins. Essa afirmação está errada.
fonte
Para melhorar a legibilidade, você pode querer usar várias variáveis, como
fonte
Não pegue
NullPointerException
. Você não sabe de onde vem (eu sei que não é provável no seu caso, mas talvez outra coisa o tenha jogado) e é lento. Você deseja acessar o campo especificado e, para isso, todos os outros campos não devem ser nulos. Esta é uma razão válida perfeita para verificar todos os campos. Eu provavelmente verificaria em um if e, em seguida, criaria um método para facilitar a leitura. Como outros apontaram, já retornando -1 é muito oldschool mas não sei se você tem um motivo para isso ou não (por exemplo, falando com outro sistema).Edit: É discutível se ele desobedece a Lei de Demeter uma vez que o WsObject é provavelmente apenas uma estrutura de dados (verifique https://stackoverflow.com/a/26021695/1528880 ).
fonte
Se você não quiser refatorar o código e puder usar o Java 8, é possível usar referências de método.
Uma demonstração simples primeiro (desculpem as classes internas estáticas)
Resultado
A interface
Getter
é apenas uma interface funcional, você pode usar qualquer equivalente.GetterResult
classe, acessadores retirados para maior clareza, contêm o resultado da cadeia getter, se houver, ou o índice do último getter chamado.O método
getterChain
é um pedaço de código simples e padronizado, que pode ser gerado automaticamente (ou manualmente quando necessário).Estruturei o código de forma que o bloco de repetição seja evidente.
Essa não é uma solução perfeita, pois você ainda precisa definir uma sobrecarga
getterChain
por número de getters.Em vez disso, eu refatoraria o código, mas se não puder e você se descobrir usando longas cadeias de getter, muitas vezes, você pode considerar construir uma classe com sobrecargas que levam de 2 a, digamos, 10 getters.
fonte
Como outros já disseram, respeitar a Lei de Deméter é definitivamente parte da solução. Outra parte, sempre que possível, é alterar esses métodos encadeados para que não possam retornar
null
. Você pode evitar o retornonull
retornando um vazioString
, um vazioCollection
ou algum outro objeto fictício que significa ou faz o que quer que o chamador façanull
.fonte
Eu gostaria de adicionar uma resposta que enfoque o significado do erro . A exceção nula em si não fornece nenhum erro completo de significado. Portanto, eu aconselho a evitar lidar com eles diretamente.
Existem milhares de casos em que seu código pode dar errado: não é possível conectar ao banco de dados, Exceção de E / S, Erro de rede ... Se você lidar com eles um por um (como a verificação de nulo aqui), seria muito complicado.
No código:
Mesmo quando você sabe qual campo é nulo, não tem ideia do que está errado. Talvez Bar seja nulo, mas é esperado? Ou é um erro de dados? Pense nas pessoas que leem seu código
Como na resposta do xenteros, eu proporia o uso de exceção desmarcada personalizada . Por exemplo, nesta situação: Foo pode ser nulo (dados válidos), mas Bar e Baz nunca devem ser nulos (dados inválidos)
O código pode ser reescrito:
fonte
NullPointerException
é uma exceção de tempo de execução, de modo geral, não é recomendado capturá-la, mas evitá-la.Você terá que capturar a exceção sempre que quiser chamar o método (ou ele se propagará pela pilha). No entanto, se no seu caso você puder continuar trabalhando com aquele resultado com valor -1 e tiver certeza de que ele não se propagará porque você não está usando nenhuma das "peças" que podem ser nulas, então me parece correto apanha isto
Editar:
Eu concordo com a resposta posterior de @xenteros, será melhor iniciar sua própria exceção em vez de retornar -1, você pode chamá-la,
InvalidXMLException
por exemplo.fonte
Tenho acompanhado este post desde ontem.
Estive comentando / votando os comentários que dizem que pegar NPE é ruim. É aqui que tenho feito isso.
Resultado
3.216
0,002
Eu vejo um vencedor claro aqui. Ter if checks é muito menos caro do que pegar uma exceção. Eu vi essa maneira de fazer o Java-8. Considerando que 70% dos aplicativos atuais ainda são executados em Java-7, estou adicionando esta resposta.
Conclusão Para qualquer aplicação de missão crítica, o manuseio do NPE é caro.
fonte
Se a eficiência for um problema, a opção 'catch' deve ser considerada. Se 'pegar' não pode ser usado porque iria propagar (como mencionado por 'SCouto'), em seguida, usar variáveis locais para evitar várias chamadas para métodos
getFoo()
,getBar()
egetBaz()
.fonte
Vale a pena considerar a criação de sua própria exceção. Vamos chamá-lo de MyOperationFailedException. Você pode jogá-lo em vez de retornar um valor. O resultado será o mesmo - você sairá da função, mas não retornará o valor -1 embutido em código, que é o antipadrão Java. Em Java, usamos exceções.
EDITAR:
De acordo com a discussão nos comentários, deixe-me acrescentar algo aos meus pensamentos anteriores. Neste código, existem duas possibilidades. Uma é que você aceita nulo e a outra é que é um erro.
Se for um erro e ocorrer, você pode depurar seu código usando outras estruturas para fins de depuração quando os pontos de interrupção não são suficientes.
Se for aceitável, você não se importa onde esse nulo apareceu. Se você fizer isso, definitivamente não deve encadear essas solicitações.
fonte
O método que você tem é longo, mas muito legível. Se eu fosse um novo desenvolvedor chegando à sua base de código, poderia ver o que você estava fazendo rapidamente. A maioria das outras respostas (incluindo capturar a exceção) não parece estar tornando as coisas mais legíveis e algumas estão tornando-as menos legíveis na minha opinião.
Considerando que você provavelmente não tem controle sobre a origem gerada e presumindo que você realmente só precisa acessar alguns campos profundamente aninhados aqui e ali, eu recomendaria agrupar cada acesso profundamente aninhado com um método.
Se você se pegar escrevendo muitos desses métodos ou se sentir tentado a tornar esses métodos estáticos públicos, eu criaria um modelo de objeto separado, aninhado como você gostaria, com apenas os campos de seu interesse, e converteria da web modelo de objeto de serviços para seu modelo de objeto.
Quando você está se comunicando com um serviço da Web remoto, é muito comum ter um "domínio remoto" e um "domínio de aplicativo" e alternar entre os dois. O domínio remoto é frequentemente limitado pelo protocolo da web (por exemplo, você não pode enviar métodos auxiliares para frente e para trás em um serviço RESTful puro e modelos de objetos profundamente aninhados são comuns para evitar várias chamadas de API) e, portanto, não são ideais para uso direto em seu cliente.
Por exemplo:
fonte
aplicando a Lei de Demeter,
fonte
Dando uma resposta que parece diferente de todas as outras.
Razão:
Para tornar seu código fácil de ler, tente verificar as condições:
EDITAR:
Todas as sugestões serão apreciadas .. !!
fonte
wsObject
estará contendo o valor retornado do Webservice .. !! O serviço já será chamado ewsObject
obteráXML
dados longos como uma resposta do webservice .. !! Portanto, não há nada como o servidor localizado em outro continente porquegetFoo()
é apenas um elemento que obtém o método getter e não uma chamada de serviço da Web .. !! @xenterosEu escrevi uma classe chamada
Snag
que permite definir um caminho para navegar por uma árvore de objetos. Aqui está um exemplo de seu uso:O que significa que a instância
ENGINE_NAME
efetivamente chamariaCar?.getEngine()?.getName()
a instância passada a ela e retornarianull
se alguma referência fosse retornadanull
:Não está publicado no Maven, mas se alguém achar útil, está aqui (sem garantia, é claro!)
É um pouco básico, mas parece funcionar. Obviamente, está mais obsoleto com as versões mais recentes do Java e outras linguagens JVM que suportam navegação segura ou
Optional
.fonte