Controllare le precondizioni nel modo corretto

6

Ho una classe con circa 1300 linee e ha molti metodi simili a CRUD che necessitano di parametri da controllare, per alcuni è più di poche regole.

Per motivi di chiarezza, userò nomi generici per la mia classe e i miei metodi.

Ho finito di implementare tutte le precondizioni di recente, e sebbene lo abbia fatto in modo caustico con if block all'inizio dei metodi e procedure simili, ho finito per avere una specie di chunks ripetuti (che ho provato ad estrarre in metodi privati, ma questo non ha aiutato completamente) e una classe che va bene, ma essendo in ordine e chiaro come sono, voglio davvero ristrutturarlo.

Quindi questa è una versione molto succinta della mia classe, quindi hai l'idea di come appare:

public class Event {
    public void setAttribute1(List<Attribute1> attribute1) {
        if (attribute1 == null)
            throw new IllegalArgumentException("Attribute1 cannot be null");

        if (attribute1.isEmpty())
            throw new IllegalArgumentException("Attribute1 cannot be empty");

        // check for repeated elements

        // check for more things

        this.attribute1 = attribute1;
    }

    public void addAttribute1(Attribute1 attr) {
        // null check

        // check not existing

        // check for more things

        attribute1.add(attr);
    }

    public void removeAttribute1(Attribute1 attr) {
        // null check

        // check not existing

        attribute1.remove(attr);
    }

    public void setMyMap(Map<K, V> myMap) {
        // null check

        // keys check

        // values check

        // more and more

        this.myMap = myMap;
    }

    // a long etc...
}

Finisco con enormi pezzi, talvolta parzialmente ripetuti, di controlli di precondizione. Questo è disordinato e confuso. Soprattutto quando ho molti metodi simili per lo stesso attributo, dove ho gli stessi controlli, tranne che non sono la stessa cosa. Nel caso in cui l'esempio sopra non aiuti a capire cosa intendo, questo sarebbe un caso che si ripete molto nella mia classe:

public class Event {
    private List<K> kDomain;
    private List<V> vDomain;
    private int kConfig;

    public void setMap(Map<K, Set<V>> map) {
        // null check

        // all k in K should be in kDomain

        // the length of the map should match an arithmetical operation based on the value of kConfig
        // (and similar checks)

        // a null Set<V> associated to a K should be illegal

        // for each k, each v in V should be in vDomain
    }

    public void addVtoK(K k, V v) {
        // null check k and v

        // k must be in kDomain

        // v must be in vDomain

        // if k has already a set of Vs, v must not be in that set already (already existing object check)
    }

    public void addVsToK(K k, Set<V> vs) {
        // null check k and vs

        // k must be in kDomain

        // all v in vs should be in vDomain

        // no v in vs can already be associated to k
    }
}

Come puoi vedere, ho controlli lunghi che sembrano molto simili, ma non sono uguali.

Sto cercando di trovare i modi migliori per ristrutturare la mia intera classe, che sono abbastanza sicuro che finirò per riscrivere.

Ho esaminato Condizioni di Guava e sembra netti, ma anche se sarebbe un miglioramento del codice, ci sono alcune precondizioni da controllare che sono un po 'più complesse da elaborare e richiedono un pensiero progettuale più profondo.

Quale sarebbe l'approccio corretto? O sto pensando troppo al mio problema?

    
posta dabadaba 25.04.2016 - 22:31
fonte

2 risposte

4

Sebbene sia ammirevole controllare le condizioni preliminari, mi chiedo:

  1. è l'utilizzo della tua classe in modo tale che dovrai controllare tutti questi elementi? per esempio. Controllerò le precondizioni per un insieme di componenti che sono esposte per un uso più generale, ma per un uso più limitato in cui so come verrà utilizzato il componente, non eseguirò così tanti controlli. L'argomento contrario è che se si estrae un componente per un uso più diffuso (ad esempio in una libreria), sarà necessario applicare più precondizioni, ma è un approccio pragmatico
  2. Sono davvero necessari tutti questi controlli? per esempio. se si specifica un insieme vuoto di elementi da aggiungere, mi interessa davvero che non venga aggiunto nulla? Non voglio che il mio codice cliente sia disseminato di controlli Set vuoti quando si aggiunge a questa entità. Lascia che sia l'entità a occuparsene (cioè non fare nulla)

Se, nonostante quanto sopra, hai masse di codice ripetuto per le tue precondizioni, forse dai un'occhiata a I proxy dinamici di Java . Questi ti permettono di scrivere un metodo wrapper che avvolgerà tutte le chiamate di metodi sul tuo oggetto. Puoi quindi verificare se il metodo che stai chiamando è un setXYZ() e se stai aggiungendo un set di attributi, o solo uno, e puoi invocare genericamente le precondizioni prima di delegare ulteriormente alla tua implementazione reale.

    
risposta data 26.04.2016 - 10:35
fonte
1

È responsabilità del metodo convalidare i propri input. Dovrebbe farlo in modo completo e rigoroso, indipendentemente dal fatto che tu stia costruendo o meno una biblioteca. Raramente (imo) c'è una buona ragione per saltare sulla corretta convalida dell'input. Gestire i casi limite come parte del set naturale di input se restituiscono un risultato significativo è accettabile (ad esempio lasciando che i set vuoti iterino 0 volte su un ciclo).

La convalida dei parametri per ciascun metodo in modo indipendente può comportare qualche ripetizione se una convalida simile avviene in metodi diversi. Puoi stare bene con quella ripetizione:

L'ASCIUTTO è sovrastimato - quasi sempre crea un accoppiamento più stretto tra i processi, e in questo contesto potrebbe anche introdurre efficacemente l'accoppiamento, indirettamente tra i tuoi (presumibilmente) metodi altrimenti indipendenti. Se si effettuasse il refactoring in modo tale da convalidare i parametri a livello centrale, si renderanno più metodi dipendenti dai metodi del validatore. Non consiglierei sempre quell'approccio, esso può esplodere rapidamente in complessità combinatoria, specialmente se poi in seguito si scopre che i metodi del validatore dipendono da altri metodi e / o in seguito chiamano molti altri metodi ... ecc. Quindi si ha un pezzo banale di Il codice (il validatore) è stato incorporato in profondità nella gerarchia della chiamata e può diventare molto difficile da rifattorizzare su un'architettura meno simile a spaghetti.

Le parti che possono essere OK a ASCIUGARE in questo contesto sono singoli pezzi di logica, riutilizzabili, non banali (ma non enormi) che probabilmente rimarranno piuttosto semplici metodi "foglia" dell'albero delle chiamate nel tempo . Tuttavia, non applicare DRY a semplici controlli, ad es. controllando per non nulla. È un equilibrio tra semplicità del codice e manutenibilità, da un lato, e non creare un accoppiamento troppo stretto che potrebbe consolidare la rigidità in futuro.

    
risposta data 03.01.2017 - 16:54
fonte