Il modo migliore per analizzare una grande classe prima di refactoring in classi più piccole?

8

Premessa

Non sono non in cerca di un modo per ridefinire una grande classe di codice per spaghetti, quell'argomento è stato trattato in altre domande.

Domanda

Sto cercando tecniche per iniziare a capire un file di classe scritto da un altro collega che si estende su oltre 4000 righe e ha un unico enorme metodo di aggiornamento che supera le 2000 righe.

Alla fine spero di costruire test unitari per questa classe e di rifattarla in molte classi più piccole che seguono DRY e il Principio di Responsabilità Unica.

Come posso gestire e affrontare questa attività? Mi piacerebbe essere in grado di disegnare un diagramma degli eventi che avvengono all'interno della classe e passare poi alla funzionalità astratta, ma sto cercando di ottenere una visione dall'alto di quali siano le responsabilità e le dipendenze della classe.

Modifica: Nelle risposte le persone hanno già trattato argomenti relativi ai parametri di input, questa informazione è per i nuovi arrivati:

In questo caso il metodo principale della classe non ha parametri di input ed esegue un ciclo while finché non viene richiesto di fermarsi. Il costruttore non accetta parametri.

Ciò aumenta la confusione della classe, i suoi requisiti e dipendenze. La classe ottiene riferimenti ad altre classi attraverso metodi statici, singleton e raggiungendo attraverso le classi a cui ha già fatto riferimento.

    
posta sydan 18.03.2015 - 15:01
fonte

4 risposte

10

È interessante notare che il refactoring è il modo più efficace per capire codice come questo. All'inizio non devi farlo in modo pulito. Puoi eseguire un passaggio di refactoring di analisi rapido e sporco, quindi ripristinarlo e farlo più attentamente con i test delle unità.

Il motivo per cui questo funziona è perché il refactoring fatto correttamente è una serie di piccoli cambiamenti, quasi meccanici, che puoi fare senza capire veramente il codice. Ad esempio, posso vedere un blob di codice che viene ripetuto ovunque e lo tiene in considerazione in un metodo, senza realmente bisogno di sapere come funziona. Potrei dargli un nome un po 'lugubre come step1 o qualcosa del genere. Poi noto che step1 e step7 sembrano apparire frequentemente insieme, e lo tengono in considerazione. Poi improvvisamente il fumo si è schiarito abbastanza da poter effettivamente vedere il quadro generale e fare alcuni nomi significativi.

Per trovare la struttura generale, ho trovato che la creazione di un grafico di dipendenza spesso aiuta. Lo faccio manualmente utilizzando graphviz in quanto ho trovato che il lavoro manuale mi aiuta a impararlo, ma probabilmente ci sono strumenti automatici disponibili. Ecco un esempio recente di una funzione su cui sto lavorando. Ho trovato che questo è anche un modo efficace per comunicare queste informazioni ai miei colleghi.

    
risposta data 18.03.2015 - 16:18
fonte
7

In primo luogo, una parola di cautela (anche se non è quello che stai chiedendo) prima di rispondere alla tua domanda: una domanda tremendamente importante da porsi è perché vuoi fare il refactoring? Il fatto che tu menzioni "collega" fa sorgere la domanda: c'è una ragione aziendale per il cambiamento? Soprattutto in un ambiente aziendale, ci sarà sempre un codice legacy e persino un codice appena impegnato di cui non sarai soddisfatto. Il fatto è che nessun ingegnere risolverà un determinato problema con la stessa soluzione. Se funziona e non ha implicazioni di prestazioni orribili, allora perché non lasciarlo essere? Il lavoro sulle feature è in genere molto più utile del refactoring perché non ti piace particolarmente un'implementazione. Detto questo, c'è qualcosa da dire per il debito tecnico e la pulizia del codice non mantenibile. Il mio unico suggerimento qui è che hai già fatto la differenza e hai fatto il buy-in per apportare le modifiche, altrimenti ti verrà chiesto "perché stai facendo questi cambiamenti" dopo essersi affondato in un intero molto sforzo e probabilmente otterrà molto più (e comprensibile) respingere.

Prima di affrontare un refactoring, voglio sempre assicurarmi di avere una ditta comprensione di ciò che sto cambiando. Esistono due metodi che uso generalmente per affrontare questo genere di cose: il logging e il debugging. Quest'ultimo è facilitato dall'esistenza di test unitari (e test di sistema se hanno senso). Se non ne possiedi, ti raccomanderei strongmente di scrivere test con copertura completa delle filiali per assicurarti che le tue modifiche non creino modifiche comportamentali impreviste. I test unitari e il passaggio attraverso il codice consentono di ottenere una comprensione di basso livello del comportamento in un ambiente controllato. La registrazione ti aiuta a comprendere meglio il comportamento nella pratica (questo è particolarmente utile in situazioni multithread).

Una volta acquisita una migliore comprensione di tutto ciò che avviene attraverso il debug e la registrazione, quindi inizierò a leggere il codice. A questo punto, generalmente ho una comprensione molto migliore di ciò che sta succedendo e sedermi e leggere 2k LoC dovrebbe avere molto più senso a quel punto. La mia intenzione qui è più di ottenere una visione end-to-end e assicurarsi che non ci sia alcun tipo di casi limite che i test unitari che ho scritto non stanno coprendo.

Quindi penserò al design e ne stilerò uno nuovo. Se la compatibilità con le versioni precedenti è di qualche importanza, farò in modo che l'interfaccia pubblica non cambi.

Con una solida comprensione di ciò che sta accadendo, test e un nuovo design alla mano, lo preparerò per la revisione. Una volta che il progetto è stato controllato da almeno due persone (si spera che abbiano familiarità con il codice), inizierò a implementare le modifiche.

Spero che ciò non sia troppo doloroso e ti aiuti.

    
risposta data 18.03.2015 - 15:52
fonte
4

Non esiste una ricetta generale, ma alcune regole empiriche ( supponendo un linguaggio tipizzato staticamente ma ciò non dovrebbe davvero avere importanza):

1) Dai un'occhiata alla firma del metodo. Ti dice cosa va in e spero che venga out . Dallo stato generale di questo dio -class, suppongo che questo sia un primo punto di dolore . Suppongo che venga usato più di un parametro.

2) Usa la funzione di ricerca del tuo editor / EDI per determinare Exit-Points (di solito viene usata una return statement_)

Da quello che sai, quale funzione richiede per testing e cosa ti aspetti in return .

Quindi un semplice primo test sarebbe chiamando la funzione con i parametri necessari e si aspetta che il risultato sia non-null . Non è molto, ma un punto di partenza.

Da ciò potresti inserire un cerchio ermeneutico (un termine coniato da H.G. Gadamer - un filosofo tedesco). Il punto è: ora avete una comprensione rudimentale della classe e aggiornate questa comprensione con nuove conoscenze dettagliate e una nuova comprensione dell'intera classe.

Questo combinato con il metodo scientifico : fai supposizioni e guarda se tengono.

3) prendi un parametro e guarda, dove nella classe è in qualche modo trasformato:

es. stai facendo Java come me, di solito ci sono getter e setter per i quali puoi guardare. Searchpattern $objectname . (o $objectname\.(get|set) se stai facendo Java)

Ora potresti fare ulteriori ipotesi su cosa fa il metodo.

Traccia solo i parametri di input ( prima ) ciascuno attraverso il metodo. Se necessario, crea alcuni diagrammi o tabelle , dove annoti ogni modifica a ciascuna delle variabili.

Da questo, puoi scrivere ulteriori test, descrivendo il comportamento del metodo. Se hai una comprensione approssimativa di come ogni parametro di input è trasformato lungo il metodo, inizia a sperimentare : passa in null per un parametro o strani input . Fai ipotesi, controlla il risultato e varia input e ipotesi.

Se lo fai una volta, hai una "tonnellata" di test che descrivono il comportamento del tuo metodo.

4) In un prossimo passaggio cercherei le dipendenze : cosa deve fare il metodo oltre al suo input per funzionare correttamente ? Ci sono possibilità di ridurre o ristrutturare quelli? Meno dipendenze hai, più chiaro vedi i punti, dove fare le prime divisioni.

5) Da lì puoi passare all'intero refactoring road con pattern di refactoring e refactoring ai pattern .

Ecco un bel Vid: GoGaRuCo 2014- Il metodo scientifico per la risoluzione dei problemi Riguarda la risoluzione dei problemi ma comunque utile per una metodologia generale di comprendere come qualcosa funziona .

Hai detto che la funzione chiamata ha nessun parametro di input : in questo caso speciale vorrei provare a identificare prima le dipendenze e il refactoring a parametri, in modo da poterli scambiare dentro e fuori a tuo piacimento.

    
risposta data 18.03.2015 - 16:03
fonte
3

Hai menzionato un metodo di 2000 righe update .

Comincio a leggere questo metodo dall'inizio alla fine. Quando trovi una serie di affermazioni che appartengono l'una all'altra, cioè condividono una responsabilità (ad esempio, crea un utente), estrai queste istruzioni in una funzione.

In questo modo, non cambi il modo in cui funziona il codice ma tu

  • abbrevia questo enorme metodo
  • ottenere una migliore comprensione di questo metodo
  • prepara il metodo per ulteriori refactoring

Ripeti questi passaggi per altri metodi.

    
risposta data 18.03.2015 - 16:40
fonte

Leggi altre domande sui tag