Come refactoring pur mantenendo precisione e ridondanza?

7

Prima di fare questa domanda, la prefaggerò con il nostro ambiente. Il mio team è composto da 3 programmatori che lavorano su più progetti diversi. A causa di ciò, il nostro uso dei test è per lo più limitato ai test molto generali della scatola nera.

Prendi anche le seguenti ipotesi:

  • I test delle unità verranno eventualmente scritti, ma sono in ordini rigorosi di refactoring
  • Ignora le tecniche Test-Driven Development comuni quando viene fornito questo ambiente, il mio tempo è limitato.
  • Capisco che se ciò fosse stato fatto correttamente , il nostro team avrebbe effettivamente salvare denaro nel lungo periodo termine con la costruzione di Unità-Test prima mano.

Sto per ridefinire una parte abbastanza grande del codice che è fondamentale. Mentre ritengo che il mio codice funzionerà in modo preciso una volta terminato e dopo il test della scatola nera, mi rendo conto che ci saranno nuovi dati che il nuovo codice potrebbe non essere in grado di gestire.

Quello che volevo sapere è come mantenere il vecchio codice che funziona il 98% delle volte in modo che possiamo chiamare quelle subroutine nel caso in cui il nuovo codice non funzioni correttamente. In questo momento sto pensando di separare il vecchio codice in un file di classe separato e aggiungere una variabile alla nostra configurazione che dirà al programma quale codice utilizzare. C'è un modo migliore per gestire questo?

NOTA: Usiamo il controllo di revisione e abbiamo build archiviati in modo che il client possa sempre ritornare a una build precedente, ma vorrei vedere se c'è un modo decente per farlo oltre a tornare. Voglio questo in modo che possano usare le altre nuove funzionalità fornite nella nuova build.

Modifica: Mentre sono d'accordo, dovrò scrivere Test di unità per questo, non credo che catturerò tutto con loro. Sto cercando modi per essere in grado di ripristinare il vecchio codice funzionale se qualcosa dovesse accadere. Mentre so che questa è una pratica sbagliata, sto pensando di rimuovere questo codice dopo che il nostro team è in grado di garantire che il nuovo codice funzioni agli stessi standard del vecchio.

    
posta jluzwick 08.02.2011 - 23:04
fonte

7 risposte

8

Dovresti scrivere alcuni test per il codice esistente prima di procedere con il refactoring. Come refactoring, continua a eseguire i test per assicurarti di non aver violato alcuna funzionalità prevista.

Potresti voler scrivere test unitari per il codice stesso e anche alcuni test di integrazione su come altre parti della tua applicazione interagiscono con la parte che stai per refactoring.

Ci sono framework che puoi usare per questo, o puoi semplicemente scrivere alcuni test interattivi / piccole app se tieni premuto per un po 'di tempo e il tuo codice esistente non è facilmente testabile. È possibile mantenere i test in giro per riferimento futuro, in modo che il refactoring di nuovo in futuro sia più facile da gestire.

Senza sapere come è strutturato il tuo codice e in quale lingua si trova, questo sarà un po 'vago, ma per passare facilmente dalla vecchia versione alla nuova, puoi usare il polimorfismo, l'inversione di dipendenza e l'iniezione di dipendenza. Qui sto facendo delle ipotesi su alcune cose, incluso il fatto che il tuo codice è orientato agli oggetti.
  • Estrai il codice che sta per essere refactored nella sua classe o assembly.
  • Estrai un'interfaccia e mantieni l'interfaccia con il codice che chiama la tua attuale implementazione.
  • Modificare il codice chiamante per utilizzare l'interfaccia anziché l'implementazione concreta. Ciò richiederà probabilmente la modifica dei costruttori per accettare un'istanza della tua prossima lezione di refactoring invece di crearne una.
  • Riforma il tuo codice. Assicurati che la nuova implementazione sia ancora conforme alla stessa interfaccia. Inserisci il codice refactored in una classe o un assembly diverso.
  • Usa un file di configurazione o scrivi un codice di inizializzazione che istanzia l'istanza giusta della tua classe (nuova o vecchia) e rendila disponibile a qualunque codice instanzia il codice che usa la classe refactored.

Esistono strutture di iniezione delle dipendenze (note anche come "contenitori IoC") che potrebbero renderti più facile. Se ne cerchi uno per la tua lingua, probabilmente troverai alcuni tutorial che renderanno più chiaro ciò che ho detto qui.

    
risposta data 08.02.2011 - 23:09
fonte
3

Usa i test unitari per dimostrare che il codice refactored funziona come ti aspetti. Non mantenere il vecchio codice in giro nel codebase, altrimenti hai rifatto tutto per niente. Come hai detto, puoi sempre dire ai tuoi attuali clienti di astenersi dall'aggiornare se qualcosa non va bene con la versione dell'applicazione utilizzando il codice refactored.

    
risposta data 08.02.2011 - 23:09
fonte
3

Sono d'accordo con gli altri utenti che i test unitari sono molto importanti. Tuttavia, per cercare di rispondere alla tua domanda, se per qualche motivo non puoi scrivere dei test.

Modi diversi dai test per migliorare la correttezza dei passaggi di refactoring.

  1. Utilizza strumenti di refactoring automatici. Se la tua lingua / IDE ha strumenti di refactoring automatizzati (ad esempio eclipse, resharper) li usa. Ti permetteranno di estrarre interfacce e metodi, spostare metodi, rinominare oggetti, creare deleghe di adattatori e molte altre cose. È importante notare che lo faranno sempre (o quasi sempre ;-) farlo bene. E rileverà situazioni in cui potrebbe verificarsi ambiguità.
  2. Rendi il tuo refactoring in passaggi il più possibile piccoli. Usa cambiamenti di refactoring molto piccoli che hanno molte probabilità di successo. Segui tutti i passaggi, non saltarli o provare a combinare i passaggi. I refactoring nel libro di Martin Fowler sono molto affidabili se seguiti con precisione.
  3. Se stai utilizzando una lingua con una buona digitazione statica; fare affidamento il più possibile sul tipo Sistema e compilatore. Ad esempio, se molti parametri vengono passati in giro come stringhe e interi che potrebbero facilmente essere confusi, introdurre un tipo di wrapper per i parametri uno alla volta. Ciò ti aiuterà a capire il flusso di dati nella tua applicazione e renderà il codice più auto-documentante.
  4. Utilizza il controllo della versione. Controlla in ogni piccola fase di refactoring. Quindi, se accade qualcosa di inaspettato, puoi tornare indietro attraverso la cronologia del controllo della versione e rivalutare ciò che hai fatto.
  5. Non aver paura di tornare. Se esegui il refactoring e ti confondi, trova qualcosa di inaspettato, o scopri di dover prima eseguire un altro refactoring per consentire a quello corrente di continuare, quindi ripristina le modifiche. Quando lavoro su un vecchio codice legacy complesso, di solito faccio molte più riprese prima di trovare una sequenza di refactoring di cui sono soddisfatto. Usa una penna e un foglio per tenere traccia delle opportunità di refactoring che trovi.
  6. Accoppia programma. Due occhi sono molto meglio di uno in questa situazione. Puoi anche programmare come tripletta se hai le persone disponibili.
  7. Revisione del codice. Dopo aver completato una sequenza di refactoring, chiedi a qualcun altro di leggere la cronologia del controllo della versione e controlla che siano d'accordo con il tuo ragionamento e i tuoi cambiamenti.
  8. Test manuali regolari (come dici tu non puoi avere test automatici). Poco dopo aver apportato una modifica al test manuale, meno modifiche possibili potrebbero averlo causato, quindi più velocemente sarai in grado di rintracciare ciò che si è rotto. Non aspettare fino a quando non hai apportato molte modifiche, quindi esegui tutto il test manuale alla fine; dato che avrai poca idea su quale delle tue modifiche abbia infranto le cose.
risposta data 09.02.2011 - 13:12
fonte
3

Il refactoring senza test unitari è davvero, davvero rischioso. Se hai una serie di test di sistema molto solida, potresti farla franca, ma comunque, ci sono buone probabilità, introdurrai nuovi bug. Quindi una soluzione possibile è semplicemente scrivere test di unità senza dire al tuo capo . Se non sta eseguendo il micromanaging, non saprà su che cosa passi il tuo tempo, e puoi semplicemente includere il tempo per scrivere i test unitari nelle tue stime per le attività di refactoring.

Riguardo a come reimplementare un codice esistente, vedi anche questa mia risposta precedente .

Hai aggiunto al tuo post:

While I agree I will need to write Unit Tests for this, I don't believe I will capture everything with them. I'm looking for ways to easily be able to revert to the old, functional code should anything happen. While I know this is a poor practice, I'm planning on removing this code after our team can guarantee that the new code works to the same standards as the old.

In che modo la garanzia della tua squadra può mai? Come notate correttamente, non sarete realisticamente in grado di catturare tutti i bug con i test. Ancora più fondamentalmente, non puoi provare provando che una parte di codice non banale è priva di errori .

Come ho suggerito nella mia precedente risposta, IMHO il meglio che puoi ottenere è scrivere una serie completa di test contro un'interfaccia / facciata comune, e sviluppare la nuova implementazione in modo che soddisfi tutti i test esistenti dall'inizio .

Nota anche che esiste una barriera psicologica qui: se tu (e i tuoi utenti) non ti fidi della nuova implementazione, puoi continuare a posticipare la sua implementazione fino alla fine dei tempi . Ad un certo punto, devi fare il salto di qualità dichiarando che è sufficiente sostituire il vecchio, rilasciandolo ufficialmente e ritirare la vecchia versione, risolvendo eventuali problemi nella nuova implementazione man mano che appaiono.

    
risposta data 09.02.2011 - 10:42
fonte
0

Quindi hai un metodo esistente, ad esempio myMethod . Vuoi essere in grado di continuare a correre come se fosse ancora alla vecchia maniera o di eseguirlo in un modo nuovo. Questo è facile. Estrai myMethod in myOldMethod . Scrivi una sostituzione e chiamala, per esempio, myNewMethod . Ora myMethod è esattamente uguale al mondo esterno e può chiamare myNewMethod o myOldMethod a seconda delle condizioni che scegli. Il vecchio codice è ancora lì, ancora utilizzabile, e puoi capovolgere un interruttore per confrontare vecchi e amp; nuova.

    
risposta data 08.02.2011 - 23:55
fonte
0

Sono empatico con la tua situazione.

Il tuo problema con il fatto di non essere in grado di scrivere i test unitari, potrebbe potenzialmente essere affrontato (a condizione che il tuo capo non vi ponga il veto) scrivendo più specifici test black box.

(Potresti anche usare il tuo tempo in modo più efficiente a lungo termine con questo approccio ... rispetto ai test di unità di scrittura ora. Se scrivi dei test unitari per il codice che stai per refactoring, è probabile che tu possa è necessario rivedere i test unitari per far fronte al refactoring.)

    
risposta data 09.02.2011 - 04:11
fonte
0

What I wanted to know is how to keep old code that functions 98% of the time so that we can call those subroutines in case the new code doesn't work properly

Puoi utilizzare il Metodo Sprout, come descritto in Lavorare efficacemente con il codice legacy . Tuttavia, affinché il software modificato scelga il percorso adatto in produzione, è necessario implementare un predicato booleano (funzione) per la scelta automatica tra il comportamento vecchio o nuovo. Se il programma non è in grado di scegliere automaticamente, dovrà essere configurato manualmente.

Se sia il vecchio che il nuovo software possono rilevare in modo affidabile i casi per i quali non è stato in grado di gestire (eseguendo un semplice controllo o eseguendo il lavoro effettivo e riportando l'errore a monte), è possibile utilizzare Chain-of-Responsibility modello.

While I agree I will need to write Unit Tests for this, I don't believe I will capture everything with them.

I Test di caratterizzazione possono essere utilizzati per acquisire e caratterizzare il comportamento esistente, in modo che non essere cambiamenti imprevisti che interrompono l'interoperabilità con altri sistemi. Se si ha accesso al vecchio codice sorgente, è possibile scrivere uno strumento per generare automaticamente un numero elevato di casi di test di caratterizzazione e registrare i risultati. Dal momento che non è necessario esercitare alcun giudizio umano sulla correttezza dei risultati, è abbastanza basso da automatizzare.

    
risposta data 09.02.2011 - 06:26
fonte

Leggi altre domande sui tag