Copiare e incollare è la scorciatoia più veloce del momento e una delle più costose sul lungo periodo. Ma il principio che dovrebbe evitarla viene citato quasi sempre in una forma amputata — «non ripeterti» — che ne perde il contenuto e lo trasforma in un'ossessione per la rimozione delle righe simili.

Che cosa dice davvero#

la formulazione originale
La formulazione originale, che quasi nessuno cita per intero:  "Every piece of KNOWLEDGE must have a single, unambiguous,   authoritative representation within a system."                          — Hunt & Thomas, The Pragmatic ProgrammerLa parola è CONOSCENZA, non codice. La differenza non è accademica:decide quando applicare il principio e quando ignorarlo.  Due frammenti identici che esprimono la STESSA regola    → duplicazione vera. Va unificata.  Due frammenti identici che esprimono regole DIVERSE    → coincidenza. Unificarli crea un accoppiamento fra cose che      devono poter cambiare separatamente.Il test: "se questa regola cambia, devono cambiare entrambi i punti?"Se la risposta è no, non è duplicazione.

La sostituzione di «conoscenza» con «codice» è ciò che produce le astrazioni sbagliate discusse in SOLID, DRY e race condition: si unifica ciò che appare uguale, invece di ciò che è la stessa cosa.

La duplicazione vera#

sparso.jsJavaScript
// L'esempio da manuale: l'aliquota scritta in cinque punti.const totale = imponibile * 1.22;              // carrelloconst iva = riga.prezzo * 0.22;                // riga fatturaif (prezzo > soglia / 1.22) { ... }            // controllo soglia<span>Prezzi IVA inclusa (22%)</span>          <!-- template -->"L'aliquota applicata è del 22%"               // email di conferma// Il giorno in cui l'aliquota cambia, il quinto punto lo trova il// cliente. Ma il problema non è "aggiornare cinque numeri": è che// nessuno sa QUANTI siano i punti. Una ricerca di "1.22" non trova// "0.22", né "22%" nel testo, né il valore già calcolato in una// costante altrove.

Qui la risposta al test è chiaramente sì: se l'aliquota cambia, tutti e cinque i punti devono cambiare, perché esprimono la stessa unica regola. È duplicazione, e il costo non è «aggiornare cinque numeri» — è che nessuno sa quanti siano i punti. Una ricerca di 1.22 non trova 0.22, né 22% scritto nel template, né un valore già precalcolato in una costante da qualche altra parte.

iva.jsJavaScript
// ✓ Una sola definizione, e le forme derivate calcolate da lì.//   Compresa quella testuale: anche il "22%" nel template è conoscenza.export const IVA = {  aliquota: 0.22,  get moltiplicatore() { return 1 + this.aliquota; },  get etichetta() { return (this.aliquota * 100).toFixed(0) + '%'; },  applica(imponibile) { return Math.round(imponibile * this.moltiplicatore); },  scorpora(lordo)     { return Math.round(lordo / this.moltiplicatore); }};// Nota: importi in CENTESIMI, interi. I decimali in virgola mobile// perdono precisione (0.1 + 0.2 !== 0.3) e su una fattura la differenza// di un centesimo è un documento che non quadra.

Il dettaglio da non saltare è l'etichetta testuale: anche il «22%» che compare nel template è quella conoscenza, e derivarla dalla stessa fonte evita il caso più imbarazzante — il sito che calcola correttamente il nuovo importo e continua a scrivere accanto la percentuale vecchia.

Le regole hanno una storia#

aliquote.jsJavaScript
// Il caso che l'esempio dell'IVA nasconde: le regole hanno una STORIA.// Una fattura del 2012 va ricalcolata con l'aliquota del 2012, non con// quella di oggi. Una costante unica è ancora una duplicazione — nel// tempo invece che nello spazio.const ALIQUOTE = [  { dal: '2013-10-01', aliquota: 0.22 },  { dal: '2011-09-17', aliquota: 0.21 },  { dal: '1997-10-01', aliquota: 0.20 }];export function aliquotaAl(data) {  const d = String(data).slice(0, 10);  const r = ALIQUOTE.find(a => a.dal <= d);  if (!r) throw new Error('nessuna aliquota per ' + d);  return r.aliquota;}// La fonte di verità unica non è "il valore": è "la regola che, data// una data, restituisce il valore". Vale per aliquote, listini,// commissioni, scaglioni: qualunque cosa che il legislatore o il// commerciale possa cambiare senza riscrivere il passato.

Questo è il livello che l'esempio da manuale nasconde. Una costante unica risolve la duplicazione nello spazio e ne lascia una nel tempo: una fattura emessa nel 2012 va ricalcolata con l'aliquota del 2012. Se la costante è un numero, ristampare un documento vecchio produce un importo diverso da quello che il cliente ha pagato.

La fonte di verità unica, in questi casi, non è il valore ma la funzione: «data una data, restituisci l'aliquota vigente». Vale per le aliquote, i listini, le commissioni, gli scaglioni contributivi, le condizioni contrattuali — per qualunque regola che qualcuno possa cambiare senza per questo riscrivere il passato.

La duplicazione che quasi nessuno riconosce#

validazione.jsJavaScript
// La duplicazione più diffusa e meno riconosciuta: la stessa regola// scritta due volte, in due linguaggi, su due lati della rete.// ✗ frontend/validazione.jsif (!email.includes('@') || password.length < 8) mostraErrore();// ✗ backend/utenti.jsif (!/^[^@]+@[^@]+$/.test(email) || password.length < 10) return err();//                                                    ~~ e sono già diverse// ✓ Una sola definizione, condivisa. Lo schema è la regola, e vive//   in un modulo che entrambi i lati importano.export const Registrazione = z.object({  email: z.string().email(),  password: z.string().min(10, 'Almeno 10 caratteri')});// client: feedback immediato          server: la verifica che contaRegistrazione.safeParse(dati)          Registrazione.parse(req.body)// Il client valida per COMODITÀ dell'utente, il server per SICUREZZA.// Sono due scopi diversi, ma la regola dev'essere la stessa — altrimenti// il modulo passa il controllo a schermo e viene rifiutato dal server.

È la duplicazione più diffusa dello sviluppo web, e viene raramente percepita come tale perché i due frammenti stanno in cartelle diverse, scritti magari da persone diverse. Il sintomo è inconfondibile: il modulo supera la validazione a schermo e viene rifiutato dal server, con un messaggio generico che l'utente non sa come interpretare.

La regola va definita una volta, in un modulo che entrambi i lati importano. Restano due scopi distinti — il client valida per dare un riscontro immediato, il server valida perché è l'unico controllo che conta — ma la regola è la stessa e non può divergere.

Dove DRY va ignorato di proposito#

eccezioni.jsJavaScript
// Dove DRY va deliberatamente ignorato.// 1. I TEST. La duplicazione in un test è documentazione: un test che//    costruisce i propri dati con tre helper condivisi non si legge//    più, e quando fallisce non si capisce cosa stesse verificando.test('rifiuta password corta', () => {  const dati = { email: 'a@b.it', password: 'corta' };   // esplicito, ripetuto  expect(() => Registrazione.parse(dati)).toThrow();});// 2. I CONFINI FRA MODULI INDIPENDENTI. Due servizi che condividono un//    tipo "Utente" sono due servizi accoppiati: il primo non può più//    cambiare quel tipo senza rompere il secondo. Spesso è preferibile//    che ciascuno definisca la propria vista del dato.// 3. LE MIGRAZIONI. Una migrazione descrive lo schema com'era in quel//    momento. Riusare i modelli applicativi correnti la rende non//    rieseguibile: fra sei mesi il modello sarà cambiato e la vecchia//    migrazione fallirà su una colonna che allora non esisteva.

Il caso dei test è quello su cui si discute di più, e vale la pena essere netti: in un test la duplicazione è documentazione. Un test che costruisce i propri dati con tre funzioni di supporto condivise smette di essere leggibile, e quando fallisce non si capisce più che cosa stesse verificando. Il valore di un test sta nel poter essere letto dall'alto in basso senza aprire altri file.

Il caso dei confini fra moduli è meno ovvio ma più importante in architettura: due servizi che condividono un tipo sono due servizi accoppiati. Il primo non può più modificare quel tipo senza rompere il secondo, e il vantaggio dell'indipendenza — che era il motivo per cui erano stati separati — è svanito. Spesso è preferibile che ciascuno definisca la propria vista del dato, accettando la duplicazione come prezzo del disaccoppiamento.

Trovarla senza affidarsi alla memoria#

ricerca
# Trovare la duplicazione senza affidarsi alla memoria.# 1. I letterali che compaiono ovunque: numeri e stringhe magiche.grep -rn --include='*.js' -oE '\b(0\.22|1\.22|22%)\b' src/ | sort | uniq -c      3 src/carrello.js:0.22      2 src/fattura.js:1.22      1 src/template/email.js:22%# 2. Blocchi identici: jscpd trova le copie strutturali, non solo#    quelle testuali (rinominare una variabile non le nasconde).npx jscpd src/ --min-lines 8 --threshold 1Found 4 clones (63 lines, 1.9% of 3312)  src/api/ordini.js [45-58]  ↔  src/api/preventivi.js [38-51]# Il risultato NON e' una lista di cose da unificare: e' una lista di# cose da guardare, chiedendosi se rispondono alla stessa autorita'.
Il risultato non è un elenco di cose da unificare: è un elenco di cose da guardare, ponendosi ogni volta la domanda del test.
SituazioneÈ duplicazione?Cosa fare
Stessa aliquota in cinque fileuna definizione, forme derivate calcolate
Stessa validazione su client e serverschema condiviso, importato da entrambi
Stesso testo di errore in tre punticatalogo dei messaggi
Due funzioni identiche, autorità diversenolasciarle separate, annotare la coincidenza
Dati di prova ripetuti nei testnola ripetizione è leggibilità
Stesso tipo in due servizi indipendentinociascuno la propria vista: il disaccoppiamento vale di più
Schema copiato in una vecchia migrazionenola migrazione descrive quel momento
Struttura simile fra due moduli, logica diversanounificare produrrebbe parametri booleani

Il criterio finale, che riassume tutto: si unifica ciò che cambierà insieme perché risponde alla stessa autorità. Non ciò che oggi si scrive allo stesso modo. La prima è una proprietà del dominio, e resta vera nel tempo; la seconda è una proprietà del testo, e cambia al primo requisito nuovo.