Che cosa aiuterebbe nel rifattorizzare un metodo di grandi dimensioni per garantire che io non rompere nulla?

9

Attualmente sto refactoring una parte di un grande codebase senza test di unità di sorta. Ho provato a ridefinire il codice in modo brutale, cioè cercando di indovinare cosa sta facendo il codice e quali cambiamenti non cambierebbero il suo significato, ma senza successo: spezza casualmente le funzionalità attorno al codebase.

Si noti che il refactoring include lo spostamento del codice C # legacy in uno stile più funzionale (il codice precedente non utilizza nessuna delle funzionalità di .NET Framework 3 e successive, incluso LINQ), aggiungendo generici in cui il codice potrebbe trarne beneficio, ecc.

Non posso utilizzare metodi formali , visto quanto costerebbero.

D'altra parte, presumo che almeno "Ogni codice legacy refactored deve venire con test unitari" la regola dovrebbe essere rigorosamente rispettata, non importa quanto costerebbe. Il problema è che quando refactoring una piccola parte di un metodo privato 500 LOC, aggiungere test unitari sembra essere un compito difficile.

Che cosa può aiutarmi a sapere quali test unitari sono rilevanti per un dato pezzo di codice? Immagino che l'analisi statica del codice sarebbe in qualche modo utile, ma quali sono gli strumenti e le tecniche che posso usare per:

  • Sai esattamente quali test di unità dovrei creare,

  • E / o sapere se il cambiamento che ho fatto ha influenzato il codice originale in un modo che sta eseguendo in modo diverso da adesso?

posta Arseni Mourzenko 26.04.2013 - 13:50
fonte

3 risposte

12

Ho avuto sfide simili. Il libro Lavorare con il codice precedente è una grande risorsa, ma si presume che si possa eseguire il corno delle scarpe nei test unitari per supportare il proprio lavoro. A volte non è possibile.

Nel mio lavoro di archeologia (il mio termine per la manutenzione su codice legacy come questo), seguo un approccio simile a quello che hai delineato.

  • Inizia con una solida comprensione di ciò che la routine sta facendo attualmente.
  • Allo stesso tempo, identifica ciò che la routine era supposta fare. Molti pensano che questo proiettile e il precedente siano gli stessi, ma c'è una sottile differenza. Spesso, se la routine stava facendo ciò che avrebbe dovuto fare, non si sarebbero applicate modifiche di manutenzione.
  • Esegui alcuni esempi attraverso la routine e assicurati di colpire i casi limite, i relativi percorsi di errore, insieme al percorso della linea principale. La mia esperienza è che il danno collaterale (rottura delle caratteristiche) proviene da condizioni al contorno non implementate esattamente nello stesso modo.
  • Dopo questi casi di esempio, identifica i contenuti persistenti che non devono necessariamente essere persistenti. Ancora una volta, ho scoperto che si tratta di effetti collaterali come questo che portano a danni collaterali altrove.

A questo punto, dovresti avere un elenco di candidati su ciò che è stato esposto e / o manipolato da quella routine. Alcune di queste manipolazioni potrebbero essere involontarie. Ora utilizzo findstr e IDE per capire quali altre aree potrebbero fare riferimento agli articoli nell'elenco dei candidati. Passerò un po 'di tempo a capire come funzionano quei riferimenti e qual è la loro natura.

Infine, una volta che mi sono illuso di pensare capisco gli impatti della routine originale, apporterò le mie modifiche una alla volta e rieseguirò i passaggi di analisi che ho delineato sopra per verificare che il cambiamento funzioni come mi aspetto che funzioni. Cerco in particolare di evitare di cambiare più cose contemporaneamente, poiché ho scoperto che questo mi esplode quando cerco di verificare l'impatto. A volte puoi farcela con più cambi, ma se riesco a seguire un percorso uno alla volta, questa è la mia preferenza.

In breve, il mio approccio è simile a quello che hai esposto. È un sacco di lavoro di preparazione; quindi fare circospetto, cambiamenti individuali; e quindi verificare, verificare, verificare.

    
risposta data 26.04.2013 - 14:07
fonte
10

What would help when refactoring a large method to ensure that I don't break anything?

Risposta breve: piccoli passi.

The problem is that when I refactor a tiny part of a 500 LOC private method, adding unit tests appears to be a difficult task.

Considera questi passaggi:

  1. Sposta l'implementazione in una diversa (privata) funzione e delegare la chiamata.

    // old:
    private int ugly500loc(int parameters) {
        // 500 LOC here
    }
    
    // new:    
    private int ugly500loc_old(int parameters) {
        // 500 LOC here
    }
    
    private void ugly500loc(int parameters) {
        return ugly500loc_old(parameters);
    }
    
  2. Aggiungi codice di registrazione (assicurati che la registrazione non abbia esito negativo) nella tua funzione originale, per tutti gli ingressi e le uscite.

    private void ugly500loc(int parameters) {
        static int call_count = 0;
        int current = ++call_count;
        save_to_file(current, parameters);
        int result = ugly500loc_old(parameters);
        save_to_file(current, result); // result, any exceptions, etc.
        return result;
    }
    

    Esegui la tua applicazione e fai tutto ciò che puoi con esso (uso valido, uso non valido, uso tipico, uso atipico, ecc.)

  3. Ora hai max(call_count) set di input e output per scrivere i tuoi test con; Potresti scrivere un test singolo che itera su tutti i parametri / set di risultati che hai e li esegue in un ciclo. Puoi anche scrivere un test aggiuntivo che esegue una particolare combinazione (da utilizzare per controllare rapidamente il passaggio su un determinato insieme di I / O).

  4. Sposta // 500 LOC here di nuovo nella tua funzione ugly500loc (e rimuovi la funzionalità di registrazione).

  5. Inizia ad estrarre le funzioni dalla grande funzione (non fare nient'altro, estrai solo le funzioni) ed esegui i test. Dopodiché dovresti avere un numero minore di funzioni per il refactor, invece del 500LOC.

  6. Vivi felici e contenti.

risposta data 26.04.2013 - 14:36
fonte
3

Di solito i test delle unità sono la strada da percorrere.

Effettua i test necessari per dimostrare che la corrente funziona come previsto. Prenditi il tuo tempo e l'ultimo test deve renderti sicuro dell'output.

What can help me in knowing which unit tests are relevant for a given piece of code?

Sei nel processo di refactoring di un pezzo di codice, devi sapere esattamente cosa fa e cosa influenza. Quindi in pratica è necessario testare tutte le zone interessate. Questo ti richiederà molto tempo ... ma questo è un risultato previsto per qualsiasi processo di refactoring.

Quindi puoi separare tutto senza problemi.

AFAIK, non esiste una tecnica a prova di proiettile per questo ... devi solo essere metodico (su qualsiasi metodo ti senti a tuo agio), un sacco di tempo e tanta pazienza! :)

Ciao e buona fortuna!

Alex

    
risposta data 26.04.2013 - 17:17
fonte

Leggi altre domande sui tag