Un service Web renvoie un fichier XML énorme et je dois accéder à des champs profondément imbriqués. Par exemple:
return wsObject.getFoo().getBar().getBaz().getInt()
Le problème est que getFoo()
, getBar()
, getBaz()
peut tous renvoyer null
.
Cependant, si je vérifie null
dans tous les cas, le code devient très détaillé et difficile à lire. De plus, il se peut que je manque les contrôles pour certains champs.
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();
Est-il acceptable d'écrire
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
return -1;
}
ou serait-ce considéré comme un antipattern?
Capturer NullPointerException
est une chose vraiment problématique car elle peut se produire presque n'importe où. Il est très facile d’en obtenir un d’un bogue, de l’attraper par accident et de continuer comme si tout était normal, dissimulant ainsi un problème réel. Il est si difficile à gérer qu'il est donc préférable d'éviter tout à fait. (Par exemple, pensez au déballage automatique d'un null Integer
. )
Je vous suggère d'utiliser plutôt la classe Optional
. C'est souvent la meilleure approche lorsque vous souhaitez travailler avec des valeurs présentes ou absentes.
En utilisant cela, vous pourriez écrire votre code comme ceci:
public Optional<Integer> m(Ws wsObject) {
return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
.map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
.map(b -> b.getBaz())
.map(b -> b.getInt());
// Add this if you want to return an -1 int instead of an empty optional if any is null
// .orElse(-1);
// Or this if you want to throw an exception instead
// .orElseThrow(SomeApplicationException::new);
}
Utiliser Optional
s au lieu de null
pour les valeurs qui pourraient être absentes rend ce fait très visible et clair pour les lecteurs, et le système de types veillera à ce que vous ne l'ignoriez pas accidentellement.
Vous avez également accès à des méthodes pour travailler avec ces valeurs plus facilement, comme map
et orElse
.
Mais réfléchissez également à la question de savoir si les méthodes intermédiaires renvoient null ou s'il s'agit d'un résultat d'erreur. S'il s'agit toujours d'une erreur, il vaut probablement mieux lever une exception que de renvoyer une valeur spéciale ou laisser les méthodes intermédiaires émettre une exception.
Si par contre les valeurs absentes des méthodes intermédiaires sont valides, vous pouvez peut-être également passer à Optional
s?
Ensuite, vous pourriez les utiliser comme ceci:
public Optional<Integer> mo(Ws wsObject) {
return wsObject.getFoo()
.flatMap(f -> f.getBar())
.flatMap(b -> b.getBaz())
.flatMap(b -> b.getInt());
}
La seule raison pour laquelle je peux penser à ne pas utiliser Optional
est si cela fait vraiment partie du code, ce qui est essentiel à la performance, et si la surcharge de la récupération de place s'avère être un problème. En effet, quelques Optional
objets sont alloués chaque fois que le code est exécuté et VM peut-être ne peut pas les optimiser. . Dans ce cas, vos if-tests originaux pourraient être meilleurs.
Je suggère d'envisager Objects.requireNonNull(T obj, String message)
. Vous pouvez créer des chaînes avec un message détaillé pour chaque exception, comme
requireNonNull(requireNonNull(requireNonNull(
wsObject, "wsObject is null")
.getFoo(), "getFoo() is null")
.getBar(), "getBar() is null");
Je vous suggère de ne pas utiliser de valeurs de retour spéciales, comme -1
. Ce n'est pas un style Java. Java a conçu le mécanisme des exceptions pour éviter cette méthode ancienne issue du langage C.
Lancer NullPointerException
n'est pas la meilleure option aussi. Vous pouvez fournir votre propre exception (en la faisant cochée pour garantir qu'elle sera gérée par un utilisateur ou non cochée pour le traiter plus facilement) ou utilisez une exception spécifique de l'analyseur XML que vous utilisez.
En supposant que la structure de classe échappe effectivement à notre contrôle, comme cela semble être le cas, je pense que rattraper le NPE tel que suggéré dans la question est effectivement une solution raisonnable, à moins que la performance ne soit une préoccupation majeure. Une petite amélioration pourrait consister à envelopper la logique lancer/attraper pour éviter l'encombrement:
static <T> T get(Supplier<T> supplier, T defaultValue) {
try {
return supplier.get();
} catch (NullPointerException e) {
return defaultValue;
}
}
Maintenant, vous pouvez simplement faire:
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
Comme déjà souligné par Tom dans le commentaire,
La déclaration suivante désobéit à la loi de Demeter ,
wsObject.getFoo().getBar().getBaz().getInt()
Ce que vous voulez, c'est int
et vous pouvez l'obtenir de Foo
. Loi de Déméter dit: ne parlez jamais aux étrangers . Pour votre cas, vous pouvez masquer l'implémentation réelle sous le capot de Foo
et Bar
.
Maintenant, vous pouvez créer une méthode dans Foo
pour récupérer int
à partir de Baz
. En fin de compte, Foo
aura Bar
et dans Bar
nous pourrons accéder à Int
sans exposer Baz
directement à Foo
. Ainsi, les contrôles nuls sont probablement divisés en différentes classes et seuls les attributs requis seront partagés entre les classes.
Ma réponse va presque dans la même ligne que @janki, mais je voudrais modifier l'extrait de code légèrement comme ci-dessous:
if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null)
return wsObject.getFoo().getBar().getBaz().getInt();
else
return something or throw exception;
Vous pouvez également ajouter une vérification nulle pour wsObject
, s'il existe un risque que cet objet soit null.
Pour améliorer la lisibilité, vous pouvez utiliser plusieurs variables, telles que
Foo theFoo;
Bar theBar;
Baz theBaz;
theFoo = wsObject.getFoo();
if ( theFoo == null ) {
// Exit.
}
theBar = theFoo.getBar();
if ( theBar == null ) {
// Exit.
}
theBaz = theBar.getBaz();
if ( theBaz == null ) {
// Exit.
}
return theBaz.getInt();
Vous dites que certaines méthodes "peuvent retourner null
" mais ne dites pas dans quelles circonstances elles renvoient null
. Vous dites que vous attrapez le NullPointerException
mais vous ne dites pas pourquoi vous l'attrapez. Ce manque d'informations suggère que vous ne comprenez pas clairement à quoi servent les exceptions et pourquoi elles sont supérieures à l'alternative.
Considérons une méthode de classe censée effectuer une action, mais la méthode ne peut pas garantir elle exécutera l'action, en raison de circonstances indépendantes de sa volonté (ce qui est en fait le cas pour tous méthodes en Java ). Nous appelons cette méthode et elle retourne. Le code qui appelle cette méthode doit savoir s'il a réussi. Comment peut-il savoir? Comment peut-il être structuré pour faire face aux deux possibilités de réussite ou d'échec?
En utilisant des exceptions, nous pouvons écrire des méthodes qui ont success comme condition de post . Si la méthode retourne, c'est réussi. S'il lève une exception, il a échoué. C'est une grande victoire pour plus de clarté. Nous pouvons écrire du code qui traite clairement le cas normal de réussite et déplacer tout le code de traitement des erreurs dans les clauses catch
. Il arrive souvent que l'appelant n'ait pas à expliquer en détail comment ou pourquoi une méthode a échoué. La même clause catch
peut donc être utilisée pour gérer plusieurs types d'échec. Et il arrive souvent qu'une méthode n'ait pas besoin d'attraper les exceptions du tout, mais peut simplement leur permettre de se propager à l'appelant sa. Les exceptions dues à des bogues de programme appartiennent à cette dernière classe; peu de méthodes peuvent réagir de manière appropriée en cas de bogue.
Donc, ces méthodes qui retournent null
.
null
indique un bogue dans votre code? Si tel est le cas, vous ne devriez pas attraper l'exception du tout. Et votre code ne devrait pas essayer de deviner lui-même. Écrivez simplement ce qui est clair et concis en supposant que cela fonctionnera. Une chaîne d'appels de méthode est-elle claire et concise? Ensuite, utilisez-les simplement.null
indique une entrée invalide dans votre programme? Si tel est le cas, une NullPointerException
ne constitue pas une exception appropriée, car elle est généralement réservée à l’indication de bogues. Vous voudrez probablement lancer une exception personnalisée dérivée de IllegalArgumentException
(si vous voulez un exception non contrôlée ) ou IOException
(si vous voulez une exception cochée). Votre programme est-il tenu de fournir des messages d'erreur de syntaxe détaillés en cas d'entrée non valide? Si tel est le cas, en vérifiant chaque méthode pour une valeur de retour null
, le lancement d'une exception de diagnostic appropriée est la seule chose que vous puissiez faire. Si votre programme n'a pas besoin de fournir des diagnostics détaillés, enchaîner les appels de méthode, capturer tout NullPointerException
puis lancer votre exception personnalisée est la plus claire et la plus concise.Une des réponses affirme que les appels de méthode chaînés violent le Loi de Demeter et sont donc mauvais. Cette affirmation est erronée.
J'ai suivi ce post depuis hier.
J'ai commenté/voté les commentaires qui dit, attraper NPE est mauvais. Voici pourquoi je fais ça.
package com.todelete;
public class Test {
public static void main(String[] args) {
Address address = new Address();
address.setSomeCrap(null);
Person person = new Person();
person.setAddress(address);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
try {
System.out.println(person.getAddress().getSomeCrap().getCrap());
} catch (NullPointerException npe) {
}
}
long endTime = System.currentTimeMillis();
System.out.println((endTime - startTime) / 1000F);
long startTime1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
if (person != null) {
Address address1 = person.getAddress();
if (address1 != null) {
SomeCrap someCrap2 = address1.getSomeCrap();
if (someCrap2 != null) {
System.out.println(someCrap2.getCrap());
}
}
}
}
long endTime1 = System.currentTimeMillis();
System.out.println((endTime1 - startTime1) / 1000F);
}
}
public class Person {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
package com.todelete;
public class Address {
private SomeCrap someCrap;
public SomeCrap getSomeCrap() {
return someCrap;
}
public void setSomeCrap(SomeCrap someCrap) {
this.someCrap = someCrap;
}
}
package com.todelete;
public class SomeCrap {
private String crap;
public String getCrap() {
return crap;
}
public void setCrap(String crap) {
this.crap = crap;
}
}
Sortie
3.216
0,002
Je vois un gagnant clair ici. Avoir si les chèques est beaucoup moins cher que d'attraper une exception. J'ai vu cette façon de faire Java-8. Étant donné que 70% des applications actuelles fonctionnent toujours sur Java-7, j'ajoute cette réponse.
Résultat final Pour toutes les applications critiques, la manipulation des NPE est coûteuse.
NullPointerException
est une exception à l'exécution, il n'est donc généralement pas recommandé de l'attraper, mais de l'éviter.
Vous devrez intercepter l'exception où vous voulez appeler la méthode (ou elle se propagera dans la pile). Néanmoins, si dans votre cas, vous pouvez continuer à travailler avec ce résultat avec la valeur -1 et que vous êtes certain qu'il ne se propagera pas car vous n'utilisez aucun des "éléments" pouvant être nuls, il me semble correct de le faire. attrape ça
Modifier:
Je suis d’accord avec le dernier réponse de @xenteros, il serait préférable de lancer votre propre exception au lieu de renvoyer -1, vous pouvez l’appeler InvalidXMLException
par exemple.
Si vous ne voulez pas refactoriser le code et que vous pouvez utiliser Java 8, il est possible d'utiliser des références de méthode.
Une simple démonstration d'abord (excusez les classes internes statiques)
public class JavaApplication14
{
static class Baz
{
private final int _int;
public Baz(int value){ _int = value; }
public int getInt(){ return _int; }
}
static class Bar
{
private final Baz _baz;
public Bar(Baz baz){ _baz = baz; }
public Baz getBar(){ return _baz; }
}
static class Foo
{
private final Bar _bar;
public Foo(Bar bar){ _bar = bar; }
public Bar getBar(){ return _bar; }
}
static class WSObject
{
private final Foo _foo;
public WSObject(Foo foo){ _foo = foo; }
public Foo getFoo(){ return _foo; }
}
interface Getter<T, R>
{
R get(T value);
}
static class GetterResult<R>
{
public R result;
public int lastIndex;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
WSObject wsObjectNull = new WSObject(new Foo(null));
GetterResult<Integer> intResult
= getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);
GetterResult<Integer> intResult2
= getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);
System.out.println(intResult.result);
System.out.println(intResult.lastIndex);
System.out.println();
System.out.println(intResult2.result);
System.out.println(intResult2.lastIndex);
// TODO code application logic here
}
public static <R, V1, V2, V3, V4> GetterResult<R>
getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
{
GetterResult result = new GetterResult<>();
Object tmp = value;
if (tmp == null)
return result;
tmp = g1.get((V1)tmp);
result.lastIndex++;
if (tmp == null)
return result;
tmp = g2.get((V2)tmp);
result.lastIndex++;
if (tmp == null)
return result;
tmp = g3.get((V3)tmp);
result.lastIndex++;
if (tmp == null)
return result;
tmp = g4.get((V4)tmp);
result.lastIndex++;
result.result = (R)tmp;
return result;
}
}
Sortie
241
4nul
2
L'interface Getter
n'est qu'une interface fonctionnelle, vous pouvez utiliser n'importe quel équivalent.GetterResult
class, les accesseurs supprimés pour plus de clarté, conservent le résultat de la chaîne de getter, le cas échéant, ou l'index du dernier getter appelé.
La méthode getterChain
est un code simple, qui peut être généré automatiquement (ou manuellement si nécessaire).
J'ai structuré le code de manière à ce que le bloc répété soit évident.
Ce n'est pas une solution parfaite car vous devez encore définir une surcharge de getterChain
par nombre de getters.
Je refacturerais le code à la place, mais si vous ne le pouvez pas et que vous vous trouvez en utilisant de longues chaînes de getter, vous pouvez souvent envisager de construire une classe avec les surcharges qui prennent de 2 à, par exemple, 10, des getters.
Comme d'autres l'ont dit, le respect de la loi de Demeter fait définitivement partie de la solution. Une autre partie, dans la mesure du possible, consiste à changer ces méthodes chaînées afin qu'elles ne puissent pas retourner null
. Vous pouvez éviter de renvoyer null
en renvoyant plutôt un String
vide, un Collection
vide ou un autre objet factice qui signifie ou fait tout ce que l'appelant ferait avec null
.
Ne pas attraper NullPointerException
. Vous ne savez pas d'où ça vient (je sais que ce n'est pas probable dans votre cas mais peut-être quelque chose d'autre l'a jeté) et c'est lent. Vous voulez accéder au champ spécifié et pour cela tous les autres champs doivent être non nuls. C’est une raison valable de vérifier chaque champ. Je voudrais probablement vérifier dans un si et ensuite créer une méthode de lisibilité. Comme d'autres l'ont déjà indiqué, -1 est très ancien, mais je ne sais pas si vous avez une raison ou non (par exemple, si vous parlez à un autre système).
public int callService() {
...
if(isValid(wsObject)){
return wsObject.getFoo().getBar().getBaz().getInt();
}
return -1;
}
public boolean isValid(WsObject wsObject) {
if(wsObject.getFoo() != null &&
wsObject.getFoo().getBar() != null &&
wsObject.getFoo().getBar().getBaz() != null) {
return true;
}
return false;
}
Edit: Il est discutable de ne pas respecter la loi de Demeter puisque le WsObject n’est probablement qu’une structure de données (cocher https://stackoverflow.com/a/26021695/152888 ).
Je voudrais ajouter une réponse qui se concentre sur le signification de l'erreur. L'exception nulle en elle-même ne fournit aucune erreur complète de sens. Donc, je conseillerais d'éviter de traiter directement avec eux.
Il y a des milliers de cas où votre code peut mal tourner: impossible de se connecter à la base de données, IO Exception, erreur réseau ... Si vous les traitez un par un (comme la vérification nulle ici), ce serait trop embêtant.
Dans le code:
wsObject.getFoo().getBar().getBaz().getInt();
Même lorsque vous savez quel champ est nul, vous n'avez aucune idée de ce qui ne va pas. Peut-être que Bar est nul, mais est-ce prévu? Ou est-ce une erreur de données? Pensez aux personnes qui lisent votre code
Comme dans la réponse de xenteros, je proposerais d'utiliser exception non contrôlée personnalisée. Par exemple, dans cette situation: Foo peut être nul (données valides), mais Bar et Baz ne doivent jamais être nuls (données non valides).
Le code peut être réécrit:
void myFunction()
{
try
{
if (wsObject.getFoo() == null)
{
throw new FooNotExistException();
}
return wsObject.getFoo().getBar().getBaz().getInt();
}
catch (Exception ex)
{
log.error(ex.Message, ex); // Write log to track whatever exception happening
throw new OperationFailedException("The requested operation failed")
}
}
void Main()
{
try
{
myFunction();
}
catch(FooNotExistException)
{
// Show error: "Your foo does not exist, please check"
}
catch(OperationFailedException)
{
// Show error: "Operation failed, please contact our support"
}
}
Cela vaut la peine d’envisager de créer votre propre exception. Appelons cela MyOperationFailedException. Vous pouvez le jeter à la place en renvoyant une valeur. Le résultat sera le même - vous quitterez la fonction, mais vous ne retournerez pas la valeur codée en dur -1, qui est Java anti-pattern. In Java nous utilisons des exceptions.
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
throw new MyOperationFailedException();
}
EDIT:
Selon la discussion dans les commentaires, permettez-moi d'ajouter quelque chose à mes pensées précédentes. Dans ce code, il y a deux possibilités. La première est que vous acceptez null et la deuxième est qu’il s’agit d’une erreur.
Si c'est une erreur et qu'elle se produit, vous pouvez déboguer votre code en utilisant d'autres structures à des fins de débogage lorsque les points d'arrêt ne suffisent pas.
Si c'est acceptable, vous ne vous souciez pas de l'endroit où ce null est apparu. Si vous le faites, vous ne devriez certainement pas enchaîner ces demandes.
Si l'efficacité est un problème, alors l'option "capture" doit être envisagée. Si 'catch' ne peut pas être utilisé car il se propagerait (comme indiqué par 'SCouto'), utilisez des variables locales pour éviter les appels multiples aux méthodes getFoo()
, getBar()
et getBaz()
.
return wsObject.getFooBarBazInt();
en appliquant la loi de Demeter,
class WsObject
{
FooObject foo;
..
Integer getFooBarBazInt()
{
if(foo != null) return foo.getBarBazInt();
else return null;
}
}
class FooObject
{
BarObject bar;
..
Integer getBarBazInt()
{
if(bar != null) return bar.getBazInt();
else return null;
}
}
class BarObject
{
BazObject baz;
..
Integer getBazInt()
{
if(baz != null) return baz.getInt();
else return null;
}
}
class BazObject
{
Integer myInt;
..
Integer getInt()
{
return myInt;
}
}
La méthode que vous avez est longue, mais très lisible. Si j'étais un nouveau développeur venant dans votre base de code, je pourrais voir ce que vous faisiez assez rapidement. La plupart des autres réponses (y compris attraper l'exception) ne semblent pas rendre les choses plus lisibles et certaines le rendent moins lisible à mon avis.
Étant donné que vous n’avez probablement pas le contrôle de la source générée et que vous avez vraiment besoin d’accéder à quelques champs profondément imbriqués ici et là, je vous recommande d’envelopper chaque accès profondément imbriqué avec une méthode.
private int getFooBarBazInt() {
if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
}
Si vous vous trouvez en train d'écrire beaucoup de ces méthodes ou si vous vous sentez tenté de créer ces méthodes statiques publiques, je créerais un modèle objet séparé, imbriqué comme vous le souhaitez, avec uniquement les champs qui vous intéressent et que vous convertissez à partir du Web. modèle d'objet de services à votre modèle d'objet.
Lorsque vous communiquez avec un service Web distant, il est très courant de disposer d'un "domaine distant" et d'un "domaine d'application" et de basculer entre les deux. Le domaine distant est souvent limité par le protocole Web (par exemple, vous ne pouvez pas envoyer de méthodes auxiliaires dans un service RESTful pur et les modèles d'objet profondément imbriqués sont courants pour éviter plusieurs appels d'API) et ne sont donc pas idéaux pour une utilisation directe dans. votre client.
Par exemple:
public static class MyFoo {
private int barBazInt;
public MyFoo(Foo foo) {
this.barBazInt = parseBarBazInt();
}
public int getBarBazInt() {
return barBazInt;
}
private int parseFooBarBazInt(Foo foo) {
if (foo() == null) return -1;
if (foo().getBar() == null) return -1;
if (foo().getBar().getBaz() == null) return -1;
return foo().getBar().getBaz().getInt();
}
}
Donner une réponse qui semble différente de toutes les autres.
Je vous recommande de vérifier
NULL
dansif
s.
Raison:
Nous ne devrions laisser aucune chance à notre programme d’être écrasé. NullPointer est généré par le système. Le comportement des exceptions générées par le système ne peut pas être prédit. Vous ne devez pas laisser votre programme entre les mains de System lorsque vous avez déjà un moyen de le gérer vous-même. Et mettez le mécanisme de traitement des exceptions pour plus de sécurité. !!
Pour faciliter la lecture de votre code, essayez ceci pour vérifier les conditions:
if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null)
return -1;
else
return wsObject.getFoo().getBar().getBaz().getInt();
EDIT:
Ici, vous devez stocker ces valeurs
wsObject.getFoo()
,wsObject.getFoo().getBar()
,wsObject.getFoo().getBar().getBaz()
dans certaines variables. Je ne le fais pas parce que je ne connais pas les types de retour de ces fonctions.
Toute suggestion sera appréciée..!!
J'ai écrit une classe appelée Snag
qui vous permet de définir un chemin pour naviguer dans une arborescence d'objets. Voici un exemple d'utilisation:
Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();
Cela signifie que l'instance ENGINE_NAME
Appellerait effectivement Car?.getEngine()?.getName()
sur l'instance qui lui est transmise et renverrait null
si une référence renvoyait null
:
final String name = ENGINE_NAME.get(firstCar);
Ce n'est pas publié sur Maven mais si quelqu'un trouve cela utile, c'est ici (sans garantie bien sûr!)
C'est un peu basique mais cela semble faire l'affaire. De toute évidence, il est plus obsolète avec les versions plus récentes de Java et d'autres langages JVM qui prennent en charge la navigation sécurisée ou Optional
.