Question

Voici la manière typique d’atteindre cet objectif:

public void myContractualMethod(final String x, final Set<String> y) {
    if ((x == null) || (x.isEmpty())) {
        throw new IllegalArgumentException("x cannot be null or empty");
    }
    if (y == null) {
        throw new IllegalArgumentException("y cannot be null");
    }
    // Now I can actually start writing purposeful 
    //    code to accomplish the goal of this method

Je pense que cette solution est moche. Vos méthodes se remplissent rapidement de code passe-partout vérifiant le contrat de paramètres d’entrée valide, obscurcissant ainsi le cœur de la méthode.

Voici ce que j'aimerais avoir:

public void myContractualMethod(@NotNull @NotEmpty final String x, @NotNull final Set<String> y) {
    // Now I have a clean method body that isn't obscured by
    //    contract checking

Si ces annotations ressemblent à la spécification de validation JSR 303 / Bean, c'est parce que je les ai empruntées. Malheureusement, ils ne semblent pas fonctionner de cette façon. ils sont destinés à l'annotation des variables d'instance, puis à l'exécution de l'objet via un validateur.

Lequel des plusieurs cadres de conception Java par contrat fournir les fonctionnalités les plus proches de mon " aimer avoir & " Exemple? Les exceptions levées doivent être des exceptions d’exécution (comme IllegalArgumentExceptions) afin que l’encapsulation ne soit pas interrompue.

Était-ce utile?

La solution

Si vous recherchez un mécanisme complet de conception par contrat, jetez un coup d'œil à certains des projets répertoriés dans Page Wikipedia pour DBC .

Si vous cherchez quelque chose de plus simple, vous pouvez consulter la page Preconditions dans les collections Google, qui fournit une méthode checkNotNull (). Vous pouvez donc réécrire le code que vous avez posté sur:

public void myContractualMethod(final String x, final Set<String> y) {
    checkNotNull(x);
    checkArgument(!x.isEmpty());
    checkNotNull(y);
}

Autres conseils

J'ai découvert une technique de Eric Burke qui ressemble à peu près à ce qui suit. C'est une utilisation élégante des importations statiques. Le code se lit très bien.

Pour vous faire une idée, voici la classe Contract . Il est minime ici, mais peut être facilement rempli au besoin.

package net.codetojoy;

public class Contract {
    public static void isNotNull(Object obj) {
        if (obj == null) throw new IllegalArgumentException("illegal null");
    }
    public static void isNotEmpty(String s) {
        if (s.isEmpty()) throw new IllegalArgumentException("illegal empty string");
    }
}

Et voici un exemple d'utilisation. La méthode foo () illustre les importations statiques:

package net.codetojoy;

import static net.codetojoy.Contract.*;

public class Example {
    public void foo(String str) {
        isNotNull(str);
        isNotEmpty(str);
        System.out.println("this is the string: " + str);
    }

    public static void main(String[] args) {
        Example ex = new Example();
        ex.foo("");
    }
}

Remarque: lors des expérimentations, notez que il peut y avoir un bogue faire cela dans le paquet par défaut. J'ai certainement perdu des cellules du cerveau à essayer.

Il existe un petit package Validation de l'argument Java , implémenté en tant que Java pur. Il vient avec plusieurs contrôles / validations standard. Et pour les cas où quelqu'un a besoin de ses propres validations plus spécifiques, certaines méthodes d'assistance sont fournies. Pour les validations qui se produisent plusieurs fois, développez simplement l'interface ArgumentValidation, avec la vôtre. Et créez la classe d'implémentation qui s'étend à partir de la classe ArgumentValidationImpl.

Cela ne répond pas directement à votre question, mais je pense qu’une partie de votre problème tient au fait que vous exagérez la validation. Par exemple, vous pouvez remplacer le premier test par:

if (x.isEmpty()) {
    throw new IllegalArgumentException("x cannot be empty");
}

et comptez sur Java pour générer une NullPointerException si x est null . Il vous suffit de modifier votre " contrat " dire que des NPE sont lancés pour certains types de "vous m'avez appelé avec des paramètres illégaux" situations.

Jared vous a présenté divers cadres qui ajoutent la prise en charge de DBC à Java.
Ce que j’ai trouvé qui fonctionne le mieux, c’est: documentez simplement votre contrat dans JavaDoc (ou tout document de référence que vous utilisez; Doxygen prend en charge les balises DBC.)
Avoir votre code obscurci par de nombreux jets et des vérifications de vos arguments n’est pas vraiment utile pour votre lecteur. La documentation est.

J'utiliserais des annotations de paramètres, une réflexion et une classe de validation générique pour créer une fonction d'application. Par exemple, vous pouvez coder une méthode de classe telle que:

.. myMethod (@notNull String x, @notNullorZero String y) {

if (Validator.ifNotContractual(getParamDetails()) {
    raiseException..
    or 
    return ..
}

}

Les méthodes de classe sont "marquées". annoter leurs conditions contractuelles. Utilisez la réflexion pour découvrir automatiquement les paramètres, leurs valeurs et leurs annotations. Envoyez le tout à une classe statique pour valider et vous informer du résultat.

La solution ne fonctionne pas parfaitement, mais la JSR-303 propose un extension de validation au niveau de la méthode . Comme il ne s'agit que d'une proposition d'extension, les mises en œuvre de JSR-303 sont libres de l'ignorer. Trouver une implémentation est un peu plus délicat. Je ne pense pas que Hibernate Validator le supporte encore, mais je crois que agimatec-validation a un support expérimental. Je n'ai pas utilisé non plus à cette fin, donc je ne sais pas à quel point ils fonctionnent. Je serais intéressé de savoir si quelqu'un essaie de le faire.

Si vous utilisez Java 8, lambdas peut être utilisé pour créer une solution de validation très élégante et lisible:

public class Assert {

    public interface CheckArgument<O> {
        boolean test(O object);
    }

    public static final <O> O that(O argument, CheckArgument<O> check) {
        if (!check.test(argument))
            throw new IllegalArgumentException("Illegal argument: " + argument);
        return argument;
    }
}

Vous l'utilisez comme:

public void setValue(int value) {
    this.value = Assert.that(value, arg -> arg >= 0);
}

L'exception ressemblera à:

Exception in thread "main" java.lang.IllegalArgumentException: Illegal argument: -7
    at com.xyz.util.Assert.that(Assert.java:13)
    at com.xyz.Main.main(Main.java:16)

La première chose intéressante est que la classe Assert ci-dessus est tout ce dont vous avez réellement besoin:

public void setValue(String value) {
    this.value = Assert.that(value, arg -> arg != null && !arg.trim().isEmpty());
}

public void setValue(SomeObject value) {
    this.value = Assert.that(value, arg -> arg != null && arg.someTest());
}

Bien sûr, that () peut être implémenté de différentes manières: avec une chaîne de format et des arguments, pour générer d'autres types d'exceptions, etc.

Toutefois, il n'est pas nécessaire de le mettre en œuvre pour effectuer différents tests.

Cela ne veut pas dire que vous ne pouvez pas pré-conditionner les tests de package si vous aimez:

public static CheckArgument<Object> isNotNull = arg -> arg != null;

Assert.that(x, Assert.isNotNull);

// with a static import:

Assert.that(x, isNotNull);

Je ne sais pas si c'est mauvais pour la performance ou pas une bonne idée pour une autre raison. (Je viens juste de commencer à regarder moi-même lambdas mais le code semble fonctionner comme il se doit ...) Mais j'aime bien que Assert puisse être gardé court (aucun besoin de dépendances qui pourraient ne pas être cruciales pour le projet) et que les tests sont très visibles.

Voici une méthode pour améliorer le message d'erreur:

public static final <O> O that(O argument, CheckArgument<O> check,
    String format, Object... objects) 
{
    if (!check.test(argument))
        throw new IllegalArgumentException(
            String.format(format, objects));
    return argument;
}

Vous l'appelez comme ceci:

public void setValue(String value) {
    this.value = Assert.that(value, 
        arg -> arg != null && arg.trim().isEmpty(), 
        "String value is empty or null: %s", value);
}

Et vient:

Exception in thread "main" java.lang.IllegalArgumentException: String value is empty or null: null
    at com.xyz.util.Assert.that(Assert.java:21)
    at com.xyz.Main.main(Main.java:16)

Mise à jour: Si vous souhaitez utiliser la construction x = Assert ... avec un test préemballé, le résultat est converti dans le type utilisé dans le test préemballé. . Il doit donc être converti dans le type de la variable ... SomeClass x = (SomeClass) Assert.that (x, isNotNull)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top