Come refactoring una miriade di classi simili

2

Mi trovo di fronte a classi simili A1, A2, ..., A100. Che ci crediate o no ma sì, ci sono circa cento classi che sembrano quasi uguali. Nessuna di queste classi è testata da unità (ovviamente ;-)). Ciascuna di queste classi è composta da circa 50 righe di codice che non è troppo di per sé. Ancora questo è troppo codice duplicato.

Considero le seguenti opzioni:

  1. Scrittura dei test per A1, ..., A100. Quindi refactoring creando un abstract base class AA.
    Pro : Sono (quasi totalmente) al sicuro dai test che nulla va storto.
    Con : Molto sforzo. Duplicazione del codice di prova.
  2. Scrittura di test per A1, A2. Riassunto del codice test duplicato e utilizzo dell'astrazione per creare il resto dei test. Quindi crea AA come in 1.
    Pro : meno sforzo rispetto a 1 ma mantenendo un livello di sicurezza simile.
    Con : trovo strano il codice di test generalizzato ; sembra spesso ... incoerente (è questa la parola giusta?). Normalmente preferisco un codice di prova specializzato per classi specializzate. Ma ciò richiede un buon design che è il mio obiettivo di tutto questo refactoring.
  3. Scrivi AA per primo, testandolo con lezioni di simulazione. Quindi ereditando A1, ..., A100 in successione.
    Pro : il modo più veloce per eliminare i duplicati.
    Con : la maggior parte delle classi di Ax sembra molto simile. Ma se no, c'è il pericolo di cambiare il codice ereditando da AA.
  4. Altre opzioni ...

All'inizio andai per 3. perché le classi di Ax erano davvero molto simili tra loro. Ma ora sono un po 'insicuro se questo è il modo giusto (da una unità che testa la prospettiva degli appassionati).

    
posta TobiMcNamobi 19.08.2014 - 11:56
fonte

4 risposte

1

Le tue opzioni sembrano molto ben pensate.

L'unico vero modo per dare un livello di comfort che il tuo refactoring non rompere nulla è l'opzione 1, in quanto è essenziale che il refactoring non cambi alcun comportamento, tuttavia l'opzione 3 sembra l'approccio più sensato.

Se potessi vedere gli stessi blocchi di codice / logica copiati su più classi, allora so che la logica non sarà diversa in una o più classi, quindi ha senso ridurre quella quantità di lavoro .

Come esempio (molto semplificato), diciamo che abbiamo 2 classi (C #):

Class1
{
    public int Method1(int x, int y)
    {
        int result = 0;

        result = x + y;

        return result;
    }
}

Class2
{
    public int Method1(int x, int y)
    {
        int result = 0;

        x++;
        y = y + 2;
        result = x + y;

        return result;
    }
}

Nelle classi sopra elencate possiamo astrarre result = x + y; in una classe base:

BaseClass1
{
    public int AddTwoNumbers(int x, int y)
    {
        return x + y;
    }
}

Dopo che entrambe le classi ereditano dalla classe base, possiamo tranquillamente sostituire result = x + y; con result = base.AddTwoNumbers(x, y); e aspettarci gli stessi risultati nelle sottoclassi.

Ovviamente result = x + y; è troppo semplice e intende rappresentare blocchi di codice ripetuti, ma una quantità maggiore di codice rende solo più difficile arrivare alla stessa conclusione: il refactoring del codice ripetuto in una classe base funzionerà allo stesso modo come se fosse stato incollato su più classi.

Ci saranno dei caveat e cosa-se a quello che ho detto, ma la domanda chiave è qual è il tuo livello di comfort nella tua abilità di refactoring?

Infine, presumo che il risultato finale sarà sottoposto a un gruppo di test / ambiente / colleghi in cui si eseguono test di regressione; questo confermerà che il tuo refactoring è buono, e se non lo avrai avrai la possibilità di ripararlo e riprovare.

Non sei un'isola e dovresti poter contare su tester / colleghi per dimostrare il tuo lavoro come parte del ciclo di vita dello sviluppo.

    
risposta data 21.08.2014 - 12:48
fonte
1

Mi piace una cosa che Ian Cooper dice "prova cose che vuoi preservare". Se non vuoi preservare la classe A non eseguire il test unitario individualmente, prova a spostare il test di un livello di astrazione.

Un'opzione può testare il tuo sistema tramite una facciata o qualcosa di simile (non capisco abbastanza il tuo sistema), testare il comportamento atteso dal tuo sistema (non dalle tue classi) e quando hai questo test che controlla il tuo sistema puoi cambiare il implementare e refactificare questa classe A senza interrompere il test.

    
risposta data 21.08.2014 - 14:03
fonte
0

Sembra che coprendo tutte le classi con test unitari produca un sacco di codice duplicato. Vorrei aggiungere qualche fase di analisi prima di iniziare a scrivere test unitari. Probabilmente, alcune classi hanno l'implementazione identica, potrebbero essere rilevate da alcuni analizzatori di codice statici (come Simian di PMD Rilevatore copia-incolla ). Queste classi potrebbero essere rimosse e il codice potrebbe essere refactored usando plain search-replace o simple regexp

Dopo aver terminato quella fase puoi iniziare la copertura con le classi di test unitarie che hanno un'implementazione veramente diversa

    
risposta data 19.08.2014 - 12:21
fonte
0

I test unitari sono ottimi per il refactoring! Assicurati solo di scrivere il codice di test in modo tale da non dover effettuare il refactoring dei test insieme al codice che stai testando. Rifattorizzare i test minerebbe qualsiasi cosa che dimostrerebbero i 100 test cut-and-past.

Scrivi prova un livello rimosso dal codice che stai rifacendo. Significato: testare la funzionalità che dovrebbe essere la stessa prima e dopo, in modo che i test non possano stabilire quale versione del codice sottostante si sta testando. Questa strategia potrebbe farti risparmiare la scrittura di 100 test cut-and-pasted.

Con un grande refactor, spesso è richiesto un test manuale.

    
risposta data 21.08.2014 - 13:23
fonte

Leggi altre domande sui tag