web-dev-qa-db-fra.com

Style de constructeur Java: les paramètres de contrôle ne sont pas nuls

Quelles sont les meilleures pratiques si vous avez une classe qui accepte certains paramètres mais qu'aucun d'entre eux n'est autorisé à être null?

Ce qui suit est évident mais l'exception est un peu non spécifique:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        if (one == null || two == null)
        {
            throw new IllegalArgumentException("Parameters can't be null");
        }
        //...
     }
}

Ici, les exceptions vous permettent de savoir quel paramètre est null, mais le constructeur est maintenant très laid:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        if (one == null)
        {
            throw new IllegalArgumentException("one can't be null");
        }           
        if (two == null)
        {
            throw new IllegalArgumentException("two can't be null");
        }
        //...
  }

Ici, le constructeur est plus ordonné, mais maintenant le code constructeur n'est pas vraiment dans le constructeur:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        setOne(one);
        setTwo(two);
     }


     public void setOne(Object one)
     {
        if (one == null)
        {
            throw new IllegalArgumentException("one can't be null");
        }           
        //...
     }

     public void setTwo(Object two)
     {
        if (two == null)
        {
            throw new IllegalArgumentException("two can't be null");
        }
        //...
     }
  }

Lequel de ces styles est le meilleur? 

Ou existe-t-il une alternative plus largement acceptée?

61
user130076

Le deuxième ou le troisième. 

Parce que cela indique à l'utilisateur de votre API ce qui n'a pas fonctionné.

Pour moins de verbosité, utilisez Validate.notNull(obj, message) dans commons-lang. Ainsi, votre constructeur ressemblera à:

public SomeClass(Object one, Object two) {
    Validate.notNull(one, "one can't be null");
    Validate.notNull(two, "two can't be null");
    ...
}

Placer le chèque dans le passeur est également acceptable, avec le même commentaire de verbosité. Si vos setters ont également pour rôle de préserver la cohérence des objets, vous pouvez également choisir le troisième.

84
Bozho

Vous pouvez utiliser l'une des nombreuses bibliothèques conçues pour faciliter les vérifications de précondition. Nombreux codes dans Google Guava uses com.google.common.base.Preconditions

Méthodes statiques simples à appeler au début de vos propres méthodes pour vérifier les arguments et l'état corrects. Cela permet des constructions telles que

 if (count <= 0) {
   throw new IllegalArgumentException("must be positive: " + count);
 }

être remplacé par le plus compact

 checkArgument(count > 0, "must be positive: %s", count);

Il a checkNotNull qui est est largement utilisé dans Guava . Vous pouvez alors écrire:

 import static com.google.common.base.Preconditions.checkNotNull;
 //...

 public SomeClass(Object one, Object two) {
     this.one = checkNotNull(one);
     this.two = checkNotNull(two, "two can't be null!");
     //...
 }

La plupart des méthodes sont surchargées pour ne prendre aucun message d'erreur, un message d'erreur fixe ou un message d'erreur modélisé avec varargs.


Sur IllegalArgumentException vs NullPointerException

Alors que votre code d'origine jette IllegalArgumentException sur les arguments null, le Preconditions.checkNotNull de Guava jette plutôt NullPointerException.

Voici une citation de Effective Java 2nd Edition: Article 60: Privilégiez l'utilisation des exceptions standard:

On peut soutenir que tous les invocations de méthode erronées se résument à un argument ou à un état illégal, mais d'autres exceptions sont couramment utilisées pour certains types d'arguments et d'états illégaux. Si un appelant transmet null à un paramètre pour lequel les valeurs NULL sont interdites, la convention stipule que NullPointerException doit être émis plutôt que IllegalArgumentException.

Une NullPointerException n'est pas réservée au moment où vous accédez aux membres d'une référence null; il est assez courant de les lancer lorsqu'un argument est null lorsqu'il s'agit d'une valeur illégale.

System.out.println("some string".split(null));
// throws NullPointerException
36

Vieille question une autre nouvelle réponse (déjà mentionnée dans un autre commentaire; mais je pense qu'elle mérite sa propre réponse).

Java 7 a ajouté Java.lang.Objects.requireNonNull() aux API que tout le monde peut utiliser. Donc, vérifier tous les arguments pour null revient à une courte liste comme:

this.arg1 = Objects.requireNonNull(arg1, "arg1 must not be null");
this.arg2 = Objects.requireNonNull(arg2, "arg2 must not be null");

Notes de côté:

  • assurez-vous de ne pas inverser les deux arguments - le second one est le message qui sera utilisé pour le NPE qui est renvoyé si le first argument est nul (si vous inversez, n'échouera jamais)
  • autre bonne pratique: si possible, définissez tous les membres de votre classe comme finaux (pour que vous puissiez en être sûr: lorsqu'un objet a été créé avec succès, tous ses membres ne sont pas nuls et ils ne changeront pas avec le temps).
29
GhostCat

J'aurais une méthode d'utilité:

 public static <T> T checkNull(String message, T object) {
     if(object == null) {
       throw new NullPointerException(message);
     }
     return object;
  }

Je voudrais qu'il renvoie l'objet pour que vous puissiez l'utiliser dans des tâches comme celle-ci:

 public Constructor(Object param) {
     this.param = checkNull("Param not allowed to be null", param);
 }

EDIT: En ce qui concerne les suggestions d'utilisation d'une bibliothèque tierce, Google Preconditions en particulier fait mieux que ce qui précède que mon code. Cependant, si c’est la seule raison pour inclure la bibliothèque dans votre projet, j’aimerais hésiter. La méthode est trop simple.

4
Yishai

Mis à part les réponses données ci-dessus qui sont toutes valables et raisonnables, je pense qu’il est bon de souligner que vérifier que null n’est pas nécessairement une "bonne pratique". (En supposant que des lecteurs autres que le PO pourraient prendre la question comme dogmatique)

Du blog Misko Hevery sur la testabilité: Assert ou pas Assert

3
qnoid

Comparaison des méthodes de vérification des conditions préalables dans Java: Guava, Apache Commons, Spring Framework et Plain Java Asserts

public static void fooSpringFrameworkAssert(String name, int start, int end) {
        // Preconditions
        Assert.notNull(name, "Name must not be null");
        Assert.isTrue(start < end, "Start (" + start + ") must be smaller than end (" + end + ")");

        // Do something here ...
    }

    public static void fooApacheCommonsValidate(String name, int start, int end) {
        // Preconditions
        Validate.notNull(name, "Name must not be null");
        Validate.isTrue(start < end, "Start (%s) must be smaller than end (%s)", start, end);

        // Do something here ...
    }

    public static void fooGuavaPreconditions(String name, int start, int end) {
        // Preconditions
        Preconditions.checkNotNull(name, "Name must not be null");
        Preconditions.checkArgument(start < end, "Start (%s) must be smaller than end (%s)", start, end);

        // Do something here ...
    }

    public static void fooPlainJavaAsserts(String name, int start, int end) {
        // Preconditions
        assert null != name : "Name must not be null";
        assert start < end : "Start (" + start + ") must be smaller than end (" + end + ")";

        // Do something here ...
    }

voici le résumé de cet article: http://www.sw-engineering-candies.com/blog-1/comparison-of-ways-to-check-preconditions-in-Java

2
nekperu15739

Une alternative au lancement d'une exception non contrôlée serait l'utilisation de assert. Sinon, je lancerais des exceptions vérifiées pour informer l'appelant du fait que le constructeur ne fonctionnerait pas avec des valeurs illégales.

La différence entre vos deux premières solutions - avez-vous besoin d'un message d'erreur détaillé, devez-vous savoir quel paramètre a échoué ou suffit-il de savoir que l'instance n'a pas pu être créée à cause d'arguments illégaux?

Notez que les deuxième et troisième exemples ne peuvent pas indiquer correctement que les deux paramètres ont été null.

BTW - Je vote pour une variante de (1):

if (one == null || two == null) {
    throw new IllegalArgumentException(
      String.format("Parameters can't be null: one=%s, two=%s", one, two));
}
1
Andreas_D

Les annotations pour l'analyse statique sont également utiles, en complément ou en lieu des vérifications d'exécution.

FindBugs, par exemple, fournit une annotation @NonNull. 

public SomeClass (@NonNull Object one, @NonNull Object two) {

1
Andy Thomas

Vous pouvez simplement avoir une méthode qui prend tous les arguments du constructeur que vous devez valider. Cette méthode lève une exception avec un message spécifique en fonction de l'argument non valide . Votre constructeur appelle cette méthode et, si elle réussit, initialise les valeurs.

0
Marc

Je suppose que vous parlez de la variable assert construite en Java. À mon avis, ce n'est pas une très bonne idée de l'utiliser. Puisqu'il peut être activé/désactivé en utilisant des paramètres de ligne de commande. Par conséquent, certains disent qu'il est uniquement acceptable d'utiliser des méthodes privées.

Mes mentors me disent de ne pas réinventer la roue! Leur conseil est d'utiliser des bibliothèques. Ils sont (probablement) bien conçus et testés. Bien sûr, il est de votre responsabilité de vous assurer de vous procurer une bibliothèque de bonne qualité.

D'autres me disent que Enterprise ppl - en certains termes - a tort et que vous introduisez plus de dépendance - pour des tâches simples - que nécessaire. Je peux accepter ce point aussi ... Mais voici ma dernière expérience:

J'ai d'abord écrit ma propre méthode privée pour vérifier les paramètres nuls. C'est ennuyeux et redondant. Je sais que je devrais le mettre dans une classe utilitaire. Mais pourquoi devrais-je l'écrire en premier lieu, alors que quelqu'un l'a déjà fait? Je peux gagner du temps sans écrire un test unitaire et concevoir un matériel existant. À moins que vous souhaitiez faire de l'exercice ou apprendre, je ne le recommanderais pas.

J'ai récemment commencé à utiliser la goyave de Google et, avec Apache commons, une fois que vous commencez à les utiliser, vous n'utilisez plus cette méthode. Vous allez découvrir et l'utiliser de plus en plus. À la fin, votre code sera plus court, plus lisible, plus cohérent et plus facile à gérer.

BTW .: Selon vos objectifs, je choisirais 2 ou 3 en utilisant l'une des bibliothèques mentionnées ci-dessus ...

0
uthomas