Domanda

Ecco il modo tipico di raggiungere questo obiettivo:

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

Penso che questa soluzione sia brutta. I tuoi metodi si riempiono rapidamente di codice boilerplate che controlla il contratto di parametri di input valido, oscurando il cuore del metodo.

Ecco cosa mi piacerebbe avere:

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

Se quelle annotazioni sembrano JSR 303 / Bean Validation Spec, è perché le ho prese in prestito. Purtroppo non sembrano funzionare in questo modo; sono destinati all'annotazione di variabili di istanza, quindi all'esecuzione dell'oggetto tramite un validatore.

Quale dei molti framework Java design-by-contract fornisci la funzionalità più vicina alla mia " mi piace avere " esempio? Le eccezioni che vengono generate dovrebbero essere eccezioni di runtime (come IllegalArgumentExceptions), quindi l'incapsulamento non viene interrotto.

È stato utile?

Soluzione

Se stai cercando un meccanismo di progettazione per contratto a tutti gli effetti darei un'occhiata ad alcuni dei progetti elencati nella Pagina Wikipedia per DBC .

Se stai cercando qualcosa di più semplice, puoi guardare Requisiti preliminari da raccolte google, che fornisce un metodo checkNotNull (). Quindi puoi riscrivere il codice che hai pubblicato su:

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

Altri suggerimenti

Ho visto una tecnica di Eric Burke che è approssimativamente simile alla seguente. È un uso elegante delle importazioni statiche. Il codice legge molto bene.

Per avere l'idea, ecco la classe Contratto . È minimo qui, ma può essere facilmente compilato secondo necessità.

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");
    }
}

Ed ecco un esempio di utilizzo. Il metodo foo () illustra le importazioni statiche:

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("");
    }
}

Nota: quando si sperimenta, si noti che potrebbe esserci un bug in giro a fare questo nel pacchetto predefinito. Ho sicuramente perso le cellule cerebrali provandolo.

Esiste un piccolo pacchetto Java Argument Validation , implementato come Plain Java. Viene fornito con diversi controlli / convalide standard. E per quei casi in cui qualcuno ha bisogno di proprie validazioni più specifiche, viene fornito con alcuni metodi di supporto. Per le convalide che si verificano più volte, basta estendere l'interfaccia ArgumentValidation, con la propria e creare la classe di implementazione che si estende dalla classe ArgumentValidationImpl.

Questo non risponde direttamente alla tua domanda, ma penso che parte del tuo problema sia che stai esagerando nella convalida. Ad esempio, è possibile sostituire il primo test con:

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

e fare affidamento su Java per lanciare un NullPointerException se x è null . Devi solo modificare il tuo "contratto" per dire che NPE viene lanciato per alcuni tipi di "mi hai chiamato con parametri illegali" situazioni.

Jared ti ha indicato vari framework che aggiungono supporto per DBC a Java.
Quello che ho trovato per funzionare meglio è: è sufficiente documentare il contratto in JavaDoc (o in qualsiasi Documentationframework utilizzato; Doxygen ha il supporto per i tag DBC.)
Avere il tuo codice offuscato da molti tiri e verifiche dei tuoi argomenti non è davvero utile per il tuo lettore. La documentazione è.

Userei Parameter Annotations, Reflection e una classe di validazione generica per creare una struttura a livello di app. ad esempio, puoi codificare un metodo di classe come:

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

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

}

I metodi di classe sono " contrassegnati come " per annotare i requisiti del contratto. Usa la riflessione per scoprire automaticamente i parametri, i loro valori e le annotazioni. Invia tutto a una classe statica per convalidare e farti sapere il risultato.

Non è una soluzione completamente funzionante, ma JSR-303 ha una proposta per un estensione di validazione a livello di metodo . Poiché è solo una proposta di estensione in questo momento, le implementazioni di JSR-303 sono libere di ignorarla. Trovare un'implementazione è un po 'più complicato. Non credo che Hibernate Validator lo supporti ancora, ma credo che agimatec-validation ha supporto sperimentale. Non ho usato neanche per questo scopo, quindi non so quanto bene funzionino. Sarei interessato a scoprire se qualcuno ci provasse.

Se stai usando Java 8, lambdas può essere usato per creare una soluzione molto elegante e leggibile per la validazione:

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;
    }
}

Lo usi come:

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

L'eccezione sarà simile a:

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 prima cosa bella è che la classe Assert sopra è tutto ciò che serve davvero:

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());
}

Naturalmente that () può essere implementato in vari modi: con una stringa di formato e argomenti, per lanciare altri tipi di eccezioni, ecc.

Tuttavia, non è necessario implementarlo per eseguire diversi test.

Non che non sia possibile preconfezionare i test se si desidera:

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

Assert.that(x, Assert.isNotNull);

// with a static import:

Assert.that(x, isNotNull);

Non ho idea se questo è male per le prestazioni o non è una buona idea per qualche altro motivo. (Ho appena iniziato a guardare lambda me stesso, ma il codice sembra funzionare come dovrebbe ...) Ma mi piace che Assert possa essere breve (non c'è bisogno di dipendenze che potrebbero non essere cruciali per il progetto) e che i test sono molto visibili.

Ecco un metodo per un messaggio di errore migliore:

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;
}

Lo chiami come:

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

E esce:

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)

Aggiornamento: se si desidera utilizzare la costruzione x = Assert ... con un test preconfezionato, il risultato verrà trasmesso al tipo utilizzato nel test preconfezionato . Quindi deve essere riportato al tipo di variabile ... SomeClass x = (SomeClass) Assert.that (x, isNotNull)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top