Duplicazione illusoria del codice


56

Il solito istinto è rimuovere qualsiasi duplicazione di codice che vedi nel codice. Tuttavia, mi sono trovato in una situazione in cui la duplicazione è illusoria .

Per descrivere la situazione in modo più dettagliato: sto sviluppando un'applicazione Web e la maggior parte delle visualizzazioni sono sostanzialmente le stesse: visualizzano un elenco di elementi che l'utente può scorrere e scegliere, un secondo elenco che contiene elementi selezionati e un "Salva "per salvare il nuovo elenco.

Mi è sembrato che il problema fosse facile. Tuttavia, ogni vista ha le sue peculiarità: a volte è necessario ricalcolare qualcosa, a volte è necessario memorizzare alcuni dati aggiuntivi, ecc. Questi, ho risolto inserendo hook di callback nel codice logico principale.

Ci sono così tante minuscole differenze tra le visualizzazioni che sta diventando sempre meno gestibile, perché ho bisogno di fornire callback praticamente per tutte le funzionalità, e la logica principale inizia a sembrare un'enorme sequenza di invocazioni callback. Alla fine non sto risparmiando tempo o codice, perché ogni vista ha il suo codice che viene eseguito, il tutto nei callback.

I problemi sono:

  • le differenze sono così minuscole che il codice sembra quasi esattamente uguale in tutte le viste,
  • ci sono così tante differenze che quando si guardano i dettagli, codificare non è un po 'simile

Come dovrei gestire questa situazione?
Avere la logica di base composta interamente da chiamate di callback è una buona soluzione?
O dovrei piuttosto duplicare il codice e eliminare la complessità del codice basato su callback?


38
Di solito trovo utile lasciare andare la duplicazione inizialmente. Una volta che ho alcuni esempi, è molto più facile vedere cosa è comune e cosa no e trovare un modo per condividere le parti comuni.
Winston Ewert,

7
Ottima domanda: una cosa da considerare non è solo la duplicazione fisica del codice ma la duplicazione semantica. Se una modifica in una parte del codice significa necessariamente che la stessa modifica sarebbe duplicata tra le altre, allora quella parte è probabilmente un candidato per il refactoring o l'astrazione. A volte puoi normalizzare al punto in cui ti intrappoli, quindi considererei anche le implicazioni pratiche per il trattamento della duplicazione come semanticamente distinte: potrebbero essere compensate dalle conseguenze del tentativo di deduplicare.
Formica

Ricorda, puoi solo riutilizzare il codice che fa la stessa cosa. Se la tua app esegue operazioni diverse su schermate diverse, richiederà richiamate diverse. Nessun se, e, o ma.
corsiKa

13
La mia regola personale su questo è: se apporto una modifica al codice in un posto, è un bug se non apporto esattamente lo stesso dappertutto? Se è così, è il brutto tipo di duplicazione. Se non sono sicuro, vai con quello che è più leggibile per ora. Nel tuo esempio le differenze di comportamento sono intenzionali e non considerate bug, quindi alcune duplicazioni vanno bene.
Ixrec,

Potresti essere interessato a leggere sulla programmazione orientata agli aspetti.
Ben Jackson,

Risposte:


53

Alla fine devi fare un appello per giudicare se combinare un codice simile per eliminare la duplicazione.

Sembra esserci una sfortunata tendenza a prendere principi come "Non ripetere te stesso" come regole che devono essere seguite dal voto in ogni momento. In realtà, queste non sono regole universali ma linee guida che dovrebbero aiutarti a pensare e sviluppare un buon design.

Come tutto nella vita, è necessario considerare i benefici rispetto ai costi. Quanto codice duplicato verrà rimosso? Quante volte viene ripetuto il codice? Quanto sforzo sarà necessario per scrivere un design più generico? Quanto probabilmente svilupperai il codice in futuro? E così via.

Senza conoscere il tuo codice specifico, questo non è chiaro. Forse esiste un modo più elegante per rimuovere i duplicati (come quello suggerito da LindaJeanne). O forse semplicemente non c'è abbastanza vera ripetizione per giustificare l'astrazione.

L'attenzione insufficiente al design è una trappola, ma attenzione anche alla sovra-progettazione.


Il tuo commento su "tendenze sfortunate" e seguire ciecamente le linee guida è sul posto, credo.
Mael,

1
@Mael Stai dicendo che se non dovessi mantenere questo codice in futuro, non avresti buone ragioni per ottenere il design giusto? (senza offesa, voglio solo sapere cosa ne pensi)
Spottato il

2
@Mael Naturalmente potremmo considerarlo solo uno sfortunato giro di parole! : D Comunque, penso che dovremmo essere severi con noi stessi come lo siamo per gli altri quando scrivo il codice (mi considero come un altro quando leggo il mio codice 2 settimane dopo averlo scritto).
Scoperto il

2
@ user61852 quindi non ti piacerà molto The Codeless Code .
RubberDuck,

1
@ user61852, haha - ma se si fa tutto dipende (su informazioni non dato in questione)? Poche cose sono meno utili della certezza in eccesso.

43

Ricorda che DRY riguarda la conoscenza . Non importa se due parti di codice sembrano simili, identiche o totalmente diverse, ciò che conta è se la stessa conoscenza del tuo sistema può essere trovata in entrambi.

Una conoscenza potrebbe essere un dato di fatto ("la deviazione massima consentita dal valore previsto è dello 0,1%") o potrebbe essere un aspetto del processo ("questa coda non contiene mai più di tre elementi"). È essenzialmente qualsiasi singola informazione codificata nel tuo codice sorgente.

Quindi, quando decidi se qualcosa è una duplicazione che dovrebbe essere rimossa, chiedi se è una duplicazione della conoscenza. In caso contrario, è probabilmente una duplicazione accidentale e l'estrazione in un luogo comune causerà problemi quando in seguito si desidera creare un componente simile in cui quella parte apparentemente duplicata è diversa.


12
Questo! L'obiettivo di DRY è evitare modifiche duplicate .
Matthieu M.,

Questo è abbastanza utile.

1
Ho pensato che l'obiettivo di DRY fosse quello di garantire che non ci fossero due bit di codice che dovessero comportarsi in modo identico ma non lo sono. Il problema non è il lavoro raddoppiato perché le modifiche al codice devono essere applicate due volte, il vero problema è quando una modifica del codice deve essere applicata due volte ma non lo è.
gnasher729,

3
@ gnasher729 Sì, questo è il punto. Se due parti di codice hanno una duplicazione di conoscenza , allora ti aspetteresti che quando uno deve cambiare, anche l'altro dovrà cambiare, portando al problema che descrivi. Se hanno una duplicazione accidentale , allora quando uno deve cambiare, l'altro potrebbe aver bisogno di rimanere lo stesso. In tal caso, se hai estratto un metodo comune (o qualsiasi altra cosa), ora hai un problema diverso da affrontare
Ben Aaronson,

1
Anche duplicazione essenziale e duplicazione accidentale , vedi An Accidental Doppelgänger in Ruby e ho DRY-ed il mio codice e ora è difficile lavorare con. Quello che è successo? . I duplicati accidentali si verificano anche su entrambi i lati di un confine di contesto . Riepilogo: unisci i duplicati solo se ha senso per i loro clienti che queste dipendenze vengano modificate contemporaneamente .
Eric,

27

Hai preso in considerazione l'utilizzo di un modello di strategia ? Avresti una classe View che contiene il codice comune e le routine chiamate da più view. I figli della classe View conterrebbero il codice specifico per tali istanze. Userebbero tutti l'interfaccia comune creata per la vista e quindi le differenze sarebbero incapsulate e coerenti.


5
No, non l'ho considerato. Grazie per il suggerimento Da una lettura veloce del modello di strategia sembra qualcosa che sto cercando. Sicuramente indagherò ulteriormente.
Mael,

3
c'è un modello di modello . Puoi anche considerare che
Shakil,

5

Qual è il potenziale di cambiamento? Ad esempio, la nostra applicazione ha 8 diverse aree di business con un potenziale di 4 o più tipi di utenti per ogni area. Le viste sono personalizzate in base al tipo di utente e all'area.

Inizialmente, questo è stato fatto usando la stessa vista con alcuni controlli qua e là per determinare se cose diverse dovevano essere mostrate. Nel corso del tempo, alcune aree di business hanno deciso di fare cose drasticamente diverse. Alla fine, abbiamo sostanzialmente migrato a una vista (viste parziali, nel caso di ASP.NET MVC) per funzionalità per area di business. Non tutte le aree di business hanno la stessa funzionalità, ma se si desidera una funzionalità di un'altra, quell'area ha una propria visione. È molto meno complicato per la comprensione del codice, nonché per la testabilità. Ad esempio, apportare una modifica per un'area non causerà una modifica indesiderata per un'altra area.

Come menzionato da @ dan1111, può arrivare a una sentenza. Nel tempo, potresti scoprire se funziona o no.


2

Un problema potrebbe essere che stai fornendo un'interfaccia (interfaccia teorica, non funzionalità linguistica) a un solo livello di funzionalità:

A(a,b,c) //a,b,c are your callbacks or other dependencies

Invece di più livelli a seconda della quantità di controllo richiesta:

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

Per quanto ho capito, esponi solo l'interfaccia di alto livello (A), nascondendo i dettagli di implementazione (le altre cose lì).

Nascondere i dettagli dell'implementazione presenta dei vantaggi e hai appena riscontrato uno svantaggio: il controllo è limitato, a meno che tu non aggiunga esplicitamente funzionalità per ogni singola cosa che sarebbe stata possibile usando direttamente le interfacce di basso livello.

Quindi, hai due opzioni. O si utilizza solo l'interfaccia di basso livello, si utilizza l'interfaccia di basso livello perché l'interfaccia di alto livello era troppo impegnativa per la manutenzione, oppure esporre interfacce di alto e basso livello. L'unica opzione ragionevole è quella di offrire interfacce di livello alto e basso (e tutto il resto), supponendo che si desideri evitare il codice ridondante.

Quindi quando scrivi un'altra delle tue cose, guardi tutte le funzionalità disponibili che hai scritto fino a quel punto (innumerevoli possibilità, spetta a te decidere quali potrebbero essere riutilizzate) e metterle insieme.

Utilizzare un singolo oggetto in cui è necessario un controllo limitato.

Utilizzare la funzionalità di livello più basso quando è necessario che si verifichino delle stranezze.

Inoltre non è molto bianco e nero. Forse la tua grande classe di alto livello PU reason ragionevolmente coprire tutti i possibili casi d'uso. Forse i casi d'uso sono così variabili che basta la funzionalità primitiva di livello più basso. Sta a te trovare l'equilibrio.


1

Ci sono già altre risposte utili. Aggiungerò il mio.

La duplicazione è negativa perché

  1. ingombra il codice
  2. ingombra la nostra comprensione del codice, ma soprattutto
  3. perché se cambi qualcosa qui e devi anche cambiare qualcosa , potresti dimenticare / introdurre bug / .... ed è difficile non dimenticare mai.

Quindi il punto è: non stai eliminando la duplicazione per il gusto di farlo o perché qualcuno ha detto che è importante. Lo stai facendo perché vuoi ridurre bug / problemi. Nel tuo caso, sembra che se cambi qualcosa in una vista, probabilmente non dovrai cambiare la stessa identica linea in tutte le altre viste. Quindi hai una duplicazione apparente , non una duplicazione effettiva.

Un altro punto importante è non riscrivere mai da zero qualcosa che ora funziona solo sulla base di una questione di principio, come ha detto Joel (avresti già sentito parlare di lui ...). Quindi, se le tue opinioni funzionano, procedi per migliorare passo dopo passo e non cadere in preda al "singolo peggior errore strategico che qualsiasi azienda di software può fare".

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.