Come posso impedire la duplicazione sconosciuta del codice?


33

Lavoro su una base di codice piuttosto grande. Centinaia di classi, tonnellate di file diversi, molte funzionalità, occorrono più di 15 minuti per estrarre una nuova copia, ecc.

Un grosso problema con una base di codice così grande è che ha parecchi metodi di utilità e tali che fanno la stessa cosa, o ha codice che non usa questi metodi di utilità quando potrebbe. E anche i metodi di utilità non sono solo tutti in una classe (perché sarebbe un enorme pasticcio confuso).

Sono piuttosto nuovo nella base di codici, ma il capo squadra che ci lavora da anni sembra avere lo stesso problema. Porta a un sacco di codice e duplicazione del lavoro e come tale, quando qualcosa si rompe, di solito è rotto in 4 copie dello stesso codice

Come possiamo frenare questo schema? Come con la maggior parte dei grandi progetti, non tutto il codice è documentato (anche se alcuni lo sono) e non tutto il codice è ... beh, pulito. Fondamentalmente, sarebbe davvero bello se potessimo lavorare per migliorare la qualità in questo senso in modo che in futuro avessimo meno duplicazioni di codice e cose come le funzioni di utilità fossero più facili da scoprire.

Inoltre, le funzioni di utilità sono di solito in una classe di supporto statica, in una classe di supporto non statica che funziona su un singolo oggetto o è un metodo statico sulla classe con cui "aiuta" principalmente.

Ho avuto un esperimento nell'aggiunta di funzioni di utilità come metodi di estensione (non avevo bisogno di alcun interno della classe, e sicuramente era richiesto solo in scenari molto specifici). Ciò ha avuto l'effetto di evitare di ingombrare la classe primaria e simili, ma in realtà non è più rilevabile se non lo sai già


Risposte:


30

La semplice risposta è che non puoi davvero impedire la duplicazione del codice. Puoi comunque "risolverlo" attraverso un difficile processo progressivo ripetitivo continuo che si riduce in due passaggi:

Passaggio 1. Inizia a scrivere test sul codice legacy (preferibilmente utilizzando un framework di test)

Passaggio 2. Riscrivere / refactificare il codice duplicato utilizzando quanto appreso dai test

Puoi usare strumenti di analisi statica per rilevare codice duplicato e per C # ci sono molti strumenti che possono farlo per te:

Strumenti come questo ti aiuteranno a trovare punti nel codice che fanno cose simili. Continuare a scrivere test per determinare che lo fanno davvero; utilizzare gli stessi test per semplificare l'utilizzo del codice duplicato. Questo "refactoring" può essere eseguito in più modi ed è possibile utilizzare questo elenco per determinare quello corretto:

Inoltre c'è anche un intero libro su questo argomento di Michael C. Feathers, che lavora in modo efficace con il codice legacy . Approfondisce le diverse strategie che puoi adottare per modificare il codice in meglio. Ha un "algoritmo di modifica del codice legacy" che non è lontano dal processo in due passaggi sopra:

  1. Identificare i punti di cambio
  2. Trova punti di prova
  3. Rompere le dipendenze
  4. Scrivi test
  5. Apporta modifiche e refactoring

Il libro è una buona lettura se hai a che fare con lo sviluppo di campi bruni, vale a dire il codice legacy che deve cambiare.

In questo caso

Nel caso del PO, posso immaginare che il codice non verificabile sia causato da un vaso di miele per "metodi e trucchi di utilità" che assumono varie forme:

  • metodi statici
  • utilizzo di risorse statiche
  • lezioni individuali
  • valori magici

Prendi nota che non c'è nulla di sbagliato in questi, ma d'altra parte sono solitamente difficili da mantenere e cambiare. I metodi di estensione in .NET sono metodi statici, ma sono anche relativamente facili da testare.

Prima di procedere con i refactoring, tuttavia, parlane con la tua squadra. Devono essere tenuti sulla stessa pagina prima di procedere con qualsiasi cosa. Questo perché se stai eseguendo il refactoring di qualcosa, allora le probabilità sono alte causerai conflitti di unione. Quindi, prima di rielaborare qualcosa, investigalo, dì al tuo team di lavorare su quei punti di codice con cautela per un po 'fino a quando non hai finito.

Dato che l'OP è nuovo nel codice, ci sono alcune altre cose da fare prima di fare qualsiasi cosa:

  • Prenditi del tempo per imparare dalla base di codice, ovvero rompere "tutto", testare "tutto", ripristinare.
  • Chiedi a qualcuno del team di rivedere il tuo codice prima di impegnarti. ;-)

In bocca al lupo!


In realtà abbiamo un bel po 'di test unitari e di integrazione. Non una copertura al 100%, ma alcune delle cose che facciamo sono quasi impossibili da testare senza cambiamenti radicali nella nostra base di codice. Non ho mai considerato l'uso dell'analisi statica per trovare la duplicazione. Dovrò provarlo dopo.
Earlz,

@Earlz: l'analisi del codice statico è fantastica! ;-) Inoltre, ogni volta che devi fare il cambiamento, pensa a soluzioni per rendere più facili i cambiamenti (controlla il refactor nel catalogo dei modelli per questo)
Spoike,

+1 Comprenderei se qualcuno mettesse una taglia su questa Q per assegnare questa risposta come "extra utile". Il catalogo Refactor to Patterns è oro, cose come questa alla moda di GuidanceExplorer.codeplex.com sono ottimi aiuti alla programmazione.
Jeremy Thompson,

2

Potremmo anche provare a vedere il problema da un'altra angolazione. Invece di pensare che il problema sia la duplicazione del codice, potremmo considerare se il problema nasce dalla mancanza di politiche per il riutilizzo del codice.

Di recente ho letto il libro Ingegneria del software con componenti riutilizzabili e ha davvero una serie di idee molto interessanti su come promuovere la riusabilità del codice a livello di organizzazione.

L'autore di questo libro, Johannes Sametinger, descrive una serie di ostacoli al riutilizzo del codice, alcuni concettuali altri tecnici. Per esempio:

Concettuale e tecnico

  • Difficoltà a trovare software riutilizzabile : il software non può essere riutilizzato se non lo si trova. È improbabile che il riutilizzo avvenga quando un repository non dispone di informazioni sufficienti sui componenti o quando i componenti sono classificati male.
  • Non riusabilità del software trovato : un facile accesso al software esistente non aumenta necessariamente il riutilizzo del software. Inavvertitamente, il software viene raramente scritto in modo tale che altri possano riutilizzarlo. Modificare e adattare il software di qualcun altro può diventare ancora più costoso della programmazione della funzionalità necessaria da zero.
  • Componenti legacy non adatti al riutilizzo : il riutilizzo dei componenti è difficile o impossibile a meno che non siano stati progettati e sviluppati per il riutilizzo. La semplice raccolta di componenti esistenti da vari sistemi software legacy e il tentativo di riutilizzarli per nuovi sviluppi non è sufficiente per un riutilizzo sistematico. La riprogettazione può aiutare a estrarre componenti riutilizzabili, tuttavia lo sforzo potrebbe essere considerevole.
  • Tecnologia orientata agli oggetti : è opinione diffusa che la tecnologia orientata agli oggetti abbia un impatto positivo sul riutilizzo del software. Sfortunatamente e erroneamente, molti credono anche che il riutilizzo dipenda da questa tecnologia o che l'adozione della tecnologia orientata agli oggetti sia sufficiente per il riutilizzo del software.
  • Modifica : i componenti non saranno sempre esattamente come li vogliamo. Se sono necessarie modifiche, dovremmo essere in grado di determinare i loro effetti sul componente e i suoi precedenti risultati di verifica.
  • Riutilizzo dei rifiuti : la certificazione di componenti riutilizzabili a determinati livelli di qualità aiuta a ridurre al minimo possibili difetti. Scarsi controlli di qualità sono uno dei principali ostacoli al riutilizzo. Abbiamo bisogno di alcuni mezzi per giudicare se le funzioni richieste corrispondono alle funzioni fornite da un componente.

Altre difficoltà tecniche di base includono

  • Concordare su cosa costituisce una componente riutilizzabile.
  • Comprendere cosa fa un componente e come usarlo.
  • Comprendere come interfacciare i componenti riutilizzabili con il resto di un progetto.
  • Progettare componenti riutilizzabili in modo che siano facili da adattare e modificare in modo controllato.
  • Organizzazione di un repository in modo che i programmatori possano trovare e utilizzare ciò di cui hanno bisogno.

Secondo l'autore, diversi livelli di riusabilità si verificano a seconda della maturità di un'organizzazione.

  • Riutilizzo ad hoc tra i gruppi di applicazioni : se non vi è alcun impegno esplicito per il riutilizzo, il riutilizzo può avvenire in modo informale e casuale al massimo. La maggior parte del riutilizzo, se presente, avverrà all'interno dei progetti. Questo porta anche allo scavenging del codice e finisce nella duplicazione del codice.
  • Riutilizzo basato su repository tra i gruppi di applicazioni : la situazione migliora leggermente quando viene utilizzato un repository di componenti ed è accessibile da vari gruppi di applicazioni. Tuttavia, non esiste alcun meccanismo esplicito per inserire i componenti nel repository e nessuno è responsabile della qualità dei componenti nel repository. Ciò può comportare molti problemi e ostacolare il riutilizzo del software.
  • Riutilizzo centralizzato con un gruppo di componenti: In questo scenario un gruppo di componenti è esplicitamente responsabile del repository. Il gruppo determina quali componenti devono essere archiviati nel repository e garantisce la qualità di questi componenti e la disponibilità della documentazione necessaria e aiuta a recuperare i componenti adatti in un particolare scenario di riutilizzo. I gruppi di applicazioni sono separati dal gruppo di componenti, che funge da tipo di subappaltatore per ciascun gruppo di applicazioni. Un obiettivo del gruppo di componenti è minimizzare la ridondanza. In alcuni modelli, i membri di questo gruppo possono anche lavorare su progetti specifici. Durante l'avvio del progetto le loro conoscenze sono preziose per favorire il riutilizzo e grazie al loro coinvolgimento in un particolare progetto possono identificare possibili candidati per l'inclusione nel repository.
  • Riutilizzo basato sul dominio : la specializzazione dei gruppi di componenti equivale al riutilizzo basato sul dominio. Ogni gruppo di dominio è responsabile dei componenti nel proprio dominio, ad esempio componenti di rete, componenti dell'interfaccia utente, componenti del database.

Quindi, forse, oltre a tutti i suggerimenti forniti in altre risposte, potresti lavorare sulla progettazione di un programma di riusabilità, coinvolgere la gestione, formare un gruppo di componenti responsabile dell'identificazione dei componenti riutilizzabili facendo analisi del dominio e definire un repository di componenti riutilizzabili che altri sviluppatori possono facilmente interrogare e cercare soluzioni cotte ai loro problemi.


1

Esistono 2 possibili soluzioni:

Prevenzione : cerca di avere la migliore documentazione possibile. Rendi tutte le funzioni adeguatamente documentate e facili da cercare nell'intera documentazione. Inoltre, quando scrivi il codice, rendi ovvio dove dovrebbe andare il codice, quindi è ovvio dove cercare. Limitare la quantità di codice "utility" è uno dei punti chiave di questo. Ogni volta che sento "lascia fare classe di utilità", i miei capelli si alzano e il mio sangue si congela, perché ovviamente è un problema. Avere sempre un modo rapido e semplice per chiedere alle persone di conoscere la base di codice ogni volta che esiste già qualche funzione.

Soluzione - Se la prevenzione fallisce, dovresti essere in grado di risolvere rapidamente ed efficacemente il pezzo di codice problematico. Il processo di sviluppo dovrebbe consentire di correggere rapidamente il codice duplicato. Il test unitario è perfetto per questo, perché puoi modificare in modo efficiente il codice senza temere di romperlo. Quindi se trovi 2 pezzi di codice simili, astrarli in una funzione o in una classe dovrebbe essere facile con un po 'di refactoring.

Personalmente non penso che la prevenzione sia possibile. Più si prova, più è problematico trovare funzionalità già esistenti.


0

Non credo che questo tipo di problemi abbia la soluzione generale. Il codice duplicato non verrà creato se gli sviluppatori hanno abbastanza disponibilità a cercare il codice esistente. Inoltre, gli sviluppatori potrebbero risolvere i problemi sul posto, se lo desiderano.

Se la lingua è C / C ++ la duplicazione sarà più facile a causa della flessibilità del collegamento (si può chiamare qualsiasi externfunzione senza informazioni preliminari). Per Java o .NET potrebbe essere necessario ideare classi di supporto e / o componenti di utilità.

Di solito inizio la rimozione della duplicazione del codice esistente solo se gli errori principali derivano dalle parti duplicate.


0

Questo è un problema tipico di un progetto più ampio che è stato gestito da molti programmatori, che hanno contribuito a volte con molta pressione dei pari. È molto allettante fare una copia di una classe e adattarla a quella classe specifica. Tuttavia, quando è stato riscontrato un problema nella classe di origine, dovrebbe essere risolto anche nei suoi decedenti che viene spesso dimenticato.

C'è una soluzione per questo e si chiama Generics che è stato introdotto in Java 6. È l'equivalente di C ++ chiamato Template. Codice di cui la classe esatta non è ancora nota all'interno di una classe generica. Controlla Java Generics e troverai tonnellate e tonnellate di documentazione per questo.

Un buon approccio è quello di riscrivere il codice che sembra essere copiato / incollato in molti punti riscrivendo il primo che è necessario correggere, a causa di un certo bug. Riscriverlo per usare Generics e anche scrivere un codice di test molto rigoroso.

Assicurarsi che venga invocato ogni metodo della classe Generic. Puoi anche introdurre strumenti di copertura del codice: il codice generico dovrebbe essere una copertura del codice completa poiché verrà utilizzato in diversi punti.

Scrivi anche il codice di test, ad esempio utilizzando JUnit o simile per la prima classe designata che verrà utilizzata insieme al pezzo di codice generico.

Inizia a utilizzare il codice generico per la seconda (la maggior parte delle volte) versione copiata quando tutto il codice precedente funziona ed è completamente testato. Vedrai che ci sono alcune righe di codice specifiche per quella Classe designata. È possibile chiamare queste righe di codice in un metodo protetto astratto che deve essere implementato dalla classe derivata che utilizza la classe di base generica.

Sì, è un lavoro noioso, ma man mano che procedi, migliorerà sempre di più le lezioni simili e lo sostituirà con qualcosa di molto molto pulito, ben scritto e molto più facile da mantenere.

Ho avuto una situazione simile in cui alla fine la classe generica ha sostituito qualcosa come 6 o 7 altre classi quasi identiche che erano quasi quasi quasi identiche ma che sono state copiate e incollate da vari programmatori per un periodo di tempo.

E sì, sono molto a favore del test automatizzato del codice. Avrà un costo maggiore all'inizio, ma ti farà sicuramente risparmiare un sacco di tempo nel complesso. E cerca di ottenere una copertura del codice di almeno l'80% e il 100% per il codice generico.

Spero che questa volontà aiuti e buona fortuna.


0

In realtà farò eco all'opinione meno popolare qui e fianco a fianco Gangnuse suggerirò che la duplicazione del codice non è sempre dannosa e a volte potrebbe essere il male minore.

Se, diciamo, mi dai la possibilità di usare:

A) Una libreria di immagini stabile (immutabile) e minuscola, ben collaudata , che duplica alcune dozzine di righe di banale codice matematico per la matematica vettoriale come prodotti a punti, lerps e morsetti, ma è completamente disaccoppiata da qualsiasi altra cosa e si sviluppa in una frazione di un secondo.

B) Una libreria di immagini instabile (che cambia rapidamente) che dipende da una libreria matematica epica per evitare quella dozzina di righe di codice sopra menzionate, con la libreria matematica che è instabile e riceve costantemente nuovi aggiornamenti e modifiche, e quindi anche la libreria di immagini deve essere ricostruito se non addirittura completamente cambiato. Ci vogliono 15 minuti per pulire tutto.

... quindi ovviamente dovrebbe essere un gioco da ragazzi per la maggior parte delle persone che A, e in realtà proprio a causa della sua duplicazione di codice minore, è preferibile. L'enfasi chiave che devo fare è la parte ben collaudata . Ovviamente non c'è niente di peggio che avere un codice duplicato che non funziona nemmeno in primo luogo, a quel punto si tratta di duplicare i bug.

Ma ci sono anche accoppiamenti e stabilità a cui pensare, e qualche modesta duplicazione qua e là può servire da meccanismo di disaccoppiamento che aumenta anche la stabilità (natura immutabile) del pacchetto.

Quindi il mio suggerimento sarà in realtà quello di concentrarsi maggiormente sui test e cercare di trovare qualcosa di veramente stabile (come immutabile, trovare pochi motivi per cambiare in futuro) e affidabile le cui dipendenze da fonti esterne, se ce ne sono, sono molto stabile, cercando di eliminare tutte le forme di duplicazione nella tua base di codice. In un ambiente di squadra di grandi dimensioni, quest'ultimo tende ad essere un obiettivo impraticabile, per non parlare del fatto che può aumentare l'accoppiamento e la quantità di codice instabile presente nella base di codice.


-2

Non dimenticare che la duplicazione del codice non è sempre dannosa. Immagina: ora hai qualche compito da risolvere in moduli assolutamente diversi del tuo progetto. Proprio ora è lo stesso compito.

Potrebbero esserci tre ragioni per questo:

  1. Alcuni temi attorno a questa attività sono gli stessi per entrambi i moduli. In questo caso la duplicazione del codice è errata e deve essere liquidata. Sarebbe intelligente creare una classe o un modulo per supportare questo tema e usare i suoi metodi in entrambi i moduli.

  2. Il compito è teorico in termini di progetto. Ad esempio, proviene dalla fisica o dalla matematica, ecc. L'attività esiste indipendentemente sul progetto. In questo caso la duplicazione del codice è errata e dovrebbe essere liquidata. Creerei una classe speciale per tali funzioni. E utilizzare una tale funzione in qualsiasi modulo in cui è necessario.

  3. Ma in altri casi la coincidenza dei compiti è una coincidenza temporanea e niente di più. Sarebbe pericoloso credere che questi compiti rimarranno gli stessi durante i cambiamenti del progetto a causa del refactoring e persino del debug. In questo caso sarebbe meglio creare due stesse funzioni / parti di codice in luoghi diversi. E i cambiamenti futuri in uno di essi non toccheranno l'altro.

E questo terzo caso accade molto spesso. Se duplichi "inconsapevolmente", per lo più lo è proprio per questo motivo - non è una vera duplicazione!

Quindi, cerca di mantenerlo pulito quando è veramente necessario e non aver paura della duplicazione se non è necessario.


2
code duplication is not always harmfulè un cattivo consiglio.
Tulains Córdova,

1
Dovrei inchinarmi alla tua autorità? Avevo messo le mie ragioni qui. Se sbaglio, mostra dove si trova l'errore. Ora sembra piuttosto la tua scarsa capacità di tenere discussioni.
Gangnus,

3
La duplicazione del codice è uno dei problemi principali nello sviluppo del software e molti scienziati e teorici dell'informatica hanno sviluppato paradigmi e metodologie proprio per evitare la duplicazione del codice come principale fonte di problemi di manutenibilità nello sviluppo del software. È come dire "scrivere codice povero non è sempre male", in questo modo tutto può essere retoricamente giustificato. Forse hai ragione, ma evitare la duplicazione del codice è un principio troppo buono per vivere in modo da incoraggiare il contrario ..
Tulains Córdova,

Io ho messo qui argomenti. Non hai. Il riferimento alle autorità non funzionerà dal 16 ° secolo. Non puoi garantire di averli compresi correttamente e che sono anche autorità per me.
Gangnus,

Hai ragione, la duplicazione del codice non è uno dei problemi fondamentali nello sviluppo del software e non sono stati sviluppati paradigmi e metodologie per evitarlo.
Tulains Córdova,
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.