Come si impedisce la duplicazione di codice sconosciuta?

31

Lavoro su una base di codice piuttosto ampia. Centinaia di classi, tonnellate di file diversi, molte funzionalità, impiegano 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 simili che fanno la stessa cosa, o ha un 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 alla base di codice, ma il team leader che ha lavorato per 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 progetti di grandi dimensioni, non tutto il codice è documentato (anche se alcuni lo sono) e non tutto il codice è ... beh, pulito. In sostanza, sarebbe davvero bello se potessimo lavorare sul miglioramento della 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 solitamente in qualche classe helper statica, in una classe helper non statica che funziona su un singolo oggetto, o è un metodo statico sulla classe con cui principalmente "aiuta".

Ho avuto un esperimento nell'aggiunta di funzioni di utilità come metodi di estensione (non avevo bisogno di alcun interno della classe, ed era sicuramente richiesto solo in scenari molto specifici). Ciò ha avuto l'effetto di evitare di ingombrare la classe primaria e così via, ma non è davvero più rilevabile a meno che non ne sia già a conoscenza

    
posta Earlz 27.12.2012 - 19:50
fonte

7 risposte

30

La semplice risposta è che non puoi davvero impedire la duplicazione del codice. Puoi comunque "aggiustarlo" attraverso un processo incrementale ripetitivo e difficoltoso che si riduce in due passaggi:

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

Passaggio 2. Riscrivi / rifatta il codice duplicato utilizzando ciò che hai imparato dai test

Puoi utilizzare strumenti di analisi statici per rilevare il codice duplicato e per C # ci sono un sacco di strumenti che possono farlo per te:

Strumenti come questo ti aiuteranno a trovare punti nel codice che fa cose simili. Continua a scrivere test per determinare che lo fanno davvero; utilizzare gli stessi test per semplificare l'uso del codice duplicato. Questo "refactoring" può essere fatto in diversi modi e puoi usare questo elenco per determinare quello corretto:

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

  1. Identifica i punti di cambiamento
  2. Trova punti di prova
  3. Annulla dipendenze
  4. Scrivi test
  5. Apporta modifiche e refactoring

Il libro è una buona lettura se hai a che fare con lo sviluppo di brown-field, cioè il codice legacy che deve essere modificato.

In questo caso

Nel caso dell'OP posso immaginare che il codice non testabile sia causato da un pot di miele per "metodi e trucchi di utilità" che prendono varie forme:

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

Prima di procedere con i refactoring, parlane con il tuo team. Devono essere tenuti sulla stessa pagina prima di procedere con qualsiasi cosa. Questo perché se stai refactoring qualcosa allora le probabilità sono alte causerai conflitti di fusione. Quindi, prima di rielaborare qualcosa, indagare, dire al tuo team di lavorare su quei punti di codice con cautela per un po 'finché non hai finito.

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

  • Prenditi del tempo per imparare dal codebase, cioè interrompi "tutto", prova "tutto", ripristina.
  • Chiedi a qualcuno del team di rivedere il codice prima di impegnarsi. ; -)

Buona fortuna!

    
risposta data 27.12.2012 - 20:03
fonte
1

Ci sono 2 soluzioni possibili:

Prevenzione - Cerca di avere la documentazione più buona possibile. Rendi ogni funzione correttamente documentata e facile da cercare attraverso tutta la documentazione. Inoltre, quando si scrive codice, è ovvio dove dovrebbe andare il codice, quindi è ovvio dove cercare. La quantità limitante di codice "utilità" è uno dei punti chiave di questo. Ogni volta che sento "facciamo classe di utilità", i miei capelli si alzano e il mio sangue si congela, perché ovviamente è un problema. Avere sempre un modo semplice e veloce per chiedere alle persone di conoscere la base di codice ogni volta che alcune funzionalità esistono già.

Soluzione - Se la prevenzione fallisce, dovresti essere in grado di risolvere in modo rapido ed efficiente la parte di codice problematica. Il tuo processo di sviluppo dovrebbe consentire di correggere rapidamente il codice duplicato. Il test delle unità è perfetto per questo, perché puoi modificare il codice in modo efficiente senza temere di romperlo. Quindi, se trovi 2 pezzi di codice simili, la loro astrazione in una funzione o classe dovrebbe essere facile con un po 'di refactoring.

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

    
risposta data 27.12.2012 - 20:05
fonte
1

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 è originato dalla mancanza di politiche per il riutilizzo del codice.

Recentemente ho letto il libro Ingegneria del software con componenti riutilizzabili e in effetti ha una serie di idee molto interessanti su come favorire 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 alcuni tecnici. Ad esempio:

Conceptual and Technical

  • Difficulty finding reusable software: software cannot be reuse unless it can be found. Reuse is unlikely to happen when a repository does not have sufficient information about components or when components are poorly classified.
  • Nonreusability of found software: easy access to existing software does not necessarily increase software reuse. Unintentionally, software is seldom written in a way so that others can reuse it. Modifying and adapting someone else's software can become even more expensive than programming the needed functionality from scratch.
  • Legacy components not suitable for reuse: Reuse of components is hard or impossible unless they have been designed and developed for reuse. Simply gathering existing components from various legacy software systems and trying to reuse them for new developments is not sufficient for systematic reuse. Re-engineering can help in extracting reusable components, however the effort might be considerable.
  • Object-oriented technology: It is widely believed that object-oriented technology has a positive impact on software reuse. Unfortunately and wrongly, many also believe reuse depends on this technology or that adopting object-oriented technology suffices for software reuse.
  • Modification: components will not always be exactly the way we want them. If modifications are necessary, we should be able to determine their effects on the component and its previous verification results.
  • Garbage reuse: Certifying reusable components to certain quality levels helps minimizing possible defects. Poor quality controls is one of the major barriers to reuse. We need some means of judging whether the required functions match the functions provided by a component.

Other basic technical difficulties include

  • Agreeing on what a reusable component constitutes.
  • Understanding what a component does and how to use it.
  • Understanding how to interface reusable components to the rest of a design.
  • Designing reusable components so that they are easy to adapt and modify in a controlled way.
  • Organizing a repository so that programmers can find and use what they need.

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

  • Ad-hoc reuse among application groups: if there is no explicit commitment to reuse, then reuse can happen in an informal and haphazard way at best. Most of the reuse, if any, will occur within projects. This also leads to code scavenging and ends up in code duplication.
  • Repository-based reuse among application groups: the situation slightly improves when a component repository is used and can be accessed by various application groups. However, no explicit mechanism exists for putting components into the repository and no one is responsible for the quality of the components in the repository. This can lead to many problems and hamper software reuse.
  • Centralized reuse with a component group: In this scenario a component group is explicitly responsible for the repository. The group determines which components are to be stored in the repository and ensures that quality of these components and the availability of the necessary documentation, and helps retrieving suitable components in a particular reuse scenario. Application groups are separated from the component group, which acts as a kind of subcontractor to each application group. An objective of the component group is to minimize redundancy. In some models, the members of this group can also work on specific projects. During project start-ups their knowledge is valuable to foster reuse and thanks to their involvement in a particular project they can identify possible candidates for inclusion in the repository.
  • Domain-based Reuse: The specialization of component groups amounts to domain-based reuse. Each domain group is responsible for components in its domain, e.g. network components, user interface components, database components.

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

    
risposta data 02.04.2014 - 01:43
fonte
0

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

Se la lingua è C / C ++, l'unione della duplicazione sarà più semplice a causa della flessibilità del collegamento (è possibile chiamare qualsiasi funzione extern senza informazioni precedenti). Per Java o .NET potrebbe essere necessario ideare classi helper e / o componenti di utilità.

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

    
risposta data 28.12.2012 - 08:55
fonte
0

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

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 copiato / incollato in molti punti riscrivendo il primo che è necessario correggere cioè a causa di un certo bug. Riscrivialo per usare Generics e scrivi anche codice di test molto rigoroso.

Assicurati che ogni metodo della classe generica sia invocato. È inoltre possibile introdurre strumenti di copertura del codice: il codice generico dovrebbe essere completamente coperto dal codice in quanto verrà utilizzato in più punti.

Scrivi anche il codice di test, ovvero 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 versione (la maggior parte delle volte) copiata quando tutto il codice precedente funziona e viene completamente testato. Vedrai che ci sono alcune linee di codice che sono 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 andrai avanti diventerai sempre più bravo a strappare classi simili e sostituirlo con qualcosa che è molto molto pulito, ben scritto e molto più facile da mantenere.

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

E sì, sono molto favorevole al test automatico del codice. All'inizio costerà di più, ma in generale ti farà risparmiare moltissimo tempo. E cercare di ottenere una copertura del codice di almeno l'80% e il 100% per il codice generico.

Spero che questo aiuti e buona fortuna.

    
risposta data 29.12.2012 - 20:59
fonte
0

In realtà farò eco all'opinione meno popolare qui e alla parte con Gangnus e suggerisco che la duplicazione del codice non è sempre dannosa e potrebbe a volte essere il male minore.

Se, per esempio, mi dai la possibilità di usare:

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

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

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

Ma c'è anche l'accoppiamento e la stabilità a cui pensare, e qualche modesta duplicazione qua e là può servire come 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 provare a creare qualcosa di veramente stabile (come immutabile, trovando pochi motivi per cambiare in futuro) e affidabile le cui dipendenze da fonti esterne, se ci sono qualsiasi, sono molto stabili, nel tentativo di eliminare tutte le forme di duplicazione nel codice base. In un ampio ambiente di squadra, quest'ultimo tende a essere un obiettivo poco pratico, senza contare che può aumentare l'accoppiamento e la quantità di codice instabile che si ha nella base di codice.

    
risposta data 14.12.2017 - 06:32
fonte
-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 motivi:

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

  2. L'attività è teorica in termini di progetto. Ad esempio, è di fisica o matematica ecc. L'attività esiste indipendentemente sul tuo progetto. In questo caso la duplicazione del codice è cattiva e dovrebbe essere liquidata. Creerei una classe speciale per tali funzioni. E usa una tale funzione in qualsiasi modulo dove ti serve.

  3. Ma in altri casi la coincidenza di compiti è una coincidenza temporanea e niente di più. Sarebbe pericoloso credere che queste attività rimarranno le stesse durante i cambiamenti del progetto a causa del refactoring e persino del debugging. In questo caso sarebbe migliore 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 "senza saperlo", per lo più è proprio per questo motivo - non è una vera duplicazione!

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

    
risposta data 27.12.2012 - 23:51
fonte

Leggi altre domande sui tag