Interpretazione del principio DRY


10

In questo momento sto lottando con questo concetto di DRY (Don't Repeat Yourself) nel mio codice. Sto creando questa funzione in cui temo che stia diventando troppo complessa ma sto cercando di seguire il principio DRY.

createTrajectoryFromPoint(A a,B b,C c,boolean doesSomething,boolean doesSomething2)

Questa funzione che ho detto prende 3 parametri di input, e quindi la funzione farà qualcosa di leggermente diverso date le combinazioni booleane doesSomethinge doesSomething2. Tuttavia, il problema che sto riscontrando è che questa funzione sta crescendo notevolmente in complessità con ogni nuovo parametro booleano aggiunto.

Quindi la mia domanda è: è meglio avere un sacco di funzioni diverse che condividono molta della stessa logica (violando quindi il principio DRY) o una funzione che si comporta in modo leggermente diverso dato un numero di parametri ma rendendolo molto più complesso (ma preservare SECCO)?


3
La logica condivisa / comune può essere scomposta in funzioni private che tutte le diverse createTrajectory...funzioni pubbliche chiamano?
FrustratedWithFormsDesigner,

Potrebbe essere, ma quelle funzioni private avrebbero ancora bisogno di ottenere quei parametri booleani
Albinoswordfish

2
Penso che questo sarebbe / sarà molto più semplice rispondere a una specie di esempio concreto. La mia reazione immediata è che la dicotomia che stai rappresentando non è del tutto reale - cioè, quelle non sono le uniche due scelte. A parte questo, considererei qualsiasi uso di a booleancome parametro alquanto sospetto nella migliore delle ipotesi.
Jerry Coffin,


Uh, perché non stai prendendo in considerazione le cose condizionali nelle loro stesse funzioni?
Rig

Risposte:


19

argomenti booleani per attivare percorsi di codice diversi in una singola funzione / metodo è un odore di codice terribile .

Quello che stai facendo viola i principi del Loose Coupling e High Cohesion e Single Responsibility , che sono molto più importanti del DRY in precedenza.

Ciò significa che le cose dovrebbero dipendere da altre cose solo quando devono ( Accoppiamento ) e che dovrebbero fare una cosa e solo una cosa (molto bene) ( Coesione ).

Per tua omissione, questo è troppo strettamente accoppiato (tutte le bandiere booleane sono un tipo di dipendenza dello stato, che è una delle peggiori!) E ha troppe responsabilità individuali mescolate (eccessivamente complesse).

Quello che stai facendo non è comunque nello spirito di DRY. DRY è più sulla ripetizione ( Rsta per REPEAT). Evitare la copia e incolla è la sua forma più semplice. Quello che stai facendo non è legato alla ripetizione.

Il tuo problema è che la decomposizione del codice non è al livello corretto. Se pensi di avere un codice duplicato, allora quella dovrebbe essere la sua funzione / metodo parametrizzata in modo appropriato, non copiata e incollata, e gli altri dovrebbero essere nominati in modo descrittivo e delegati alla funzione / metodo principale.


Ok, sembra che il modo in cui sto scrivendo non sia molto buono (il mio sospetto iniziale) Non sono sicuro di cosa sia questo "Sprit of DRY"
Albinoswordfish


4

Il fatto che tu stia passando dei booleani per fare in modo che la funzione faccia cose diverse è una violazione del Principio della singola responsabilità. Una funzione dovrebbe fare una cosa. Dovrebbe fare solo una cosa e dovrebbe farlo bene.

Puzza di doverlo dividere in diverse funzioni più piccole con nomi descrittivi, separando i percorsi del codice corrispondenti ai valori di quei booleani.

Una volta fatto ciò, dovresti cercare il codice comune nelle funzioni risultanti e fattorizzarlo nelle sue stesse funzioni. A seconda di quanto sia complessa questa cosa, potresti persino essere in grado di fattorizzare una o due classi.

Naturalmente, questo presuppone che tu stia utilizzando un sistema di controllo della versione e che tu abbia una buona suite di test, in modo da poter eseguire il refactoring senza paura di rompere qualcosa.


3

Perché non crei un'altra funzione contenente tutta la logica della tua funzione prima di decidere di fare qualcosa o qualcosa2 e quindi avere tre funzioni come:

createTrajectoryFromPoint(A a,B b,C c){...}

dosomething(A a, B b, C c){...}

dosomething2(A a, B b, C c){...}

E ora passando tre tipi di parametri uguali a tre funzioni diverse, ti ripeterai di nuovo, quindi dovresti definire una struttura o classe contenente A, B, C.

In alternativa è possibile creare una classe contenente i parametri A, B, C e un elenco di operazioni da eseguire. Aggiungi quali operazioni (qualcosa, qualcosa2) vuoi fare con questi parametri (A, B, C) registrando le operazioni con l'oggetto. Quindi disporre di un metodo per chiamare tutte le operazioni registrate sul proprio oggetto.

public class MyComplexType
{
    public A a{get;set;}
    public B b{get;set;}
    public C c{get;set;}

    public delegate void Operation(A a, B b, C c);
    public List<Operation> Operations{get;set;}

    public MyComplexType(A a, B b, C c)
    {
        this.a = a;
        this.b = b;
        this.c = c   
        Operations = new List<Operation>();
    }

    public CallMyOperations()
    {
        foreach(var operation in Operations)
        {
            operation(a,b,c);
        }
    }
}

Per tenere conto delle possibili combinazioni di valori per il dosomething e il dosomething2 booleani, sono necessarie 4 funzioni, non 2, oltre alla funzione createTrajectoryFromPoint di base. Questo approccio non si adatta bene all'aumentare del numero di opzioni e persino nominare le funzioni diventa noioso.
JGWeissman,

2

DRY può essere portato troppo lontano, è meglio usare il principio della responsabilità singola (SRP) insieme a DRY. Aggiungere flag bool a una funzione per farlo fare versioni leggermente diverse dello stesso codice può essere un segno che stai facendo troppo con una funzione. In questo caso, suggerirei di creare una funzione separata per ogni caso rappresentato dai flag, quindi quando hai scritto ciascuna funzione dovrebbe essere abbastanza evidente se esiste una sezione comune che può essere spostata in una funzione privata senza passare tutti i flag , se non c'è una sezione apparente di codice, allora non ti stai davvero ripetendo, hai diversi casi diversi ma simili.


1

Di solito faccio diversi passaggi con questo problema, fermandomi quando non riesco a capire come andare oltre.

Innanzitutto, fai quello che hai fatto. Vai duro con DRY. Se non si finisce con un grande casino peloso, il gioco è fatto. Se, come nel tuo caso, non hai un codice duplicato ma ogni valore booleano viene controllato in 20 punti diversi, vai al passaggio successivo.

In secondo luogo, dividere il codice in blocchi. Ai booleani viene fatto riferimento una sola volta (bene, forse due volte a volte) per indirizzare l'esecuzione al blocco giusto. Con due booleani, si finisce con quattro blocchi. Ogni blocco è quasi identico. DRY è sparito. Non rendere ciascun blocco un metodo separato. Sarebbe più elegante, ma mettere tutto il codice in un metodo rende più facile, o addirittura possibile, per chiunque esegua la manutenzione vedere che è necessario apportare ogni modifica in quattro punti. Con un codice ben organizzato e un monitor alto, le differenze e gli errori saranno quasi evidenti. Ora hai un codice gestibile e funzionerà più velocemente del disordine aggrovigliato originale.

In terzo luogo, prova a catturare righe duplicate di codice da ciascuno dei tuoi blocchi e trasformali in metodi semplici e piacevoli. A volte non puoi fare niente. A volte non puoi fare molto. Ma ogni piccola parte che fai ti riporta indietro a SECCO e rende il codice un po 'più facile da seguire e più sicuro da mantenere. Idealmente, il metodo originale potrebbe finire senza un codice duplicato. A quel punto, potresti voler dividerlo in diversi metodi senza i parametri booleani oppure no. La convenienza del codice chiamante è ora la preoccupazione principale.

Ho aggiunto la mia risposta al numero elevato già qui a causa del secondo passaggio. Odio il codice duplicato, ma se è l'unico modo intelligibile per risolvere un problema, fallo in modo tale che chiunque possa sapere a colpo d'occhio cosa stai facendo. Utilizzare più blocchi e un solo metodo. Rendi i blocchi il più identici possibile in nomi, spaziatura, allineamenti, ... tutto. Le differenze dovrebbero quindi saltare fuori dal lettore. Potrebbe rendere ovvio come riscriverlo in modo ASCIUTTO e, in caso contrario, mantenerlo sarà ragionevolmente semplice.


0

Un approccio alternativo è quello di sostituire i parametri booleani con i parametri dell'interfaccia, con il codice per gestire i diversi valori booleani refactored nelle implementazioni dell'interfaccia. Quindi avresti

createTrajectoryFromPoint(A a,B b,C c,IX x,IY y)

dove hai implementazioni di IX e IY che rappresentano i diversi valori per i booleani. Nel corpo della funzione, ovunque tu abbia

if (doesSomething)
{
     ...
}
else
{
     ...
}

lo si sostituisce con una chiamata a un metodo definito su IX, con le implementazioni contenenti i blocchi di codice omessi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.