Fino a che punto posso spingere il refactoring senza modificare il comportamento esterno?


27

Secondo Martin Fowler , il refactoring del codice è (enfasi il mio):

Il refactoring è una tecnica disciplinata per ristrutturare un corpo di codice esistente, alterando la sua struttura interna senza modificarne il comportamento esterno . Il suo cuore è una serie di piccoli comportamenti che preservano le trasformazioni. Ogni trasformazione (chiamata "refactoring") fa ben poco, ma una sequenza di trasformazioni può produrre una ristrutturazione significativa. Poiché ogni refactoring è piccolo, è meno probabile che vada storto. Il sistema continua inoltre a funzionare completamente dopo ogni piccolo refactoring, riducendo le possibilità che un sistema possa rompersi seriamente durante la ristrutturazione.

Che cos'è il "comportamento esterno" in questo contesto? Ad esempio, se applico il refactoring del metodo move e sposta un metodo su un'altra classe, sembra che cambi comportamento esterno, no?

Quindi, sono interessato a capire a che punto un cambiamento smette di essere un refattore e diventa qualcosa di più. Il termine "refactoring" può essere utilizzato in modo improprio per cambiamenti più ampi: esiste una parola diversa per questo?

Aggiornare. Molte risposte interessanti sull'interfaccia, ma spostare il refactoring del metodo non cambierebbe l'interfaccia?


Se il comportamento esistente fa schifo o è incompleto, quindi modificarlo, eliminarlo / riscriverlo. Allora potresti non ricostituire, ma a chi importa quale sia il nome se il sistema (giusto?) Migliorerà come risultato di ciò.
Giobbe

2
Al tuo supervisore potrebbe interessarti se ti è stato dato il permesso di refactoring e hai fatto una riscrittura.
JeffO,

2
I limiti del refactoring sono unit test. Se hai una specifica disegnata da loro, qualsiasi cosa cambi che non rompa i test è refactoring?
George Silva,


Se vuoi saperne di più, questo argomento è anche un campo di ricerca attivo. Ci sono molte pubblicazioni scientifiche su questo argomento, ad esempio informatik.uni-trier.de/~ley/db/indices/a-tree/s/…
Skarab,

Risposte:


25

"Esterno" in questo contesto significa "osservabile per gli utenti". Gli utenti possono essere umani nel caso di un'applicazione o altri programmi nel caso di un'API pubblica.

Quindi, se sposti il ​​metodo M dalla classe A alla classe B, ed entrambe le classi si trovano all'interno di un'applicazione e nessun utente può osservare alcun cambiamento nel comportamento dell'app dovuto alla modifica, puoi giustamente chiamarlo refactoring.

Se, OTOH, qualche altro sottosistema / componente di livello superiore modifica il suo comportamento o si interrompe a causa della modifica, ciò è effettivamente (di solito) osservabile dagli utenti (o almeno dai registri di controllo degli amministratori di sistema). O se le tue classi facevano parte di un'API pubblica, potrebbe esserci un codice di terze parti là fuori che dipende dal fatto che M sia parte della classe A, non B. Quindi nessuno di questi casi è refactoring in senso stretto.

c'è la tendenza a chiamare qualsiasi rielaborazione del codice come refactoring che, credo, non è corretto.

In effetti, è una conseguenza triste ma attesa che il refactoring diventi di moda. Gli sviluppatori hanno lavorato alla rielaborazione del codice in modo ad hoc per anni ed è sicuramente più facile imparare una nuova parola d'ordine che analizzare e cambiare abitudini radicate.

Quindi qual è la parola giusta per rielaborazioni che cambiano il comportamento esterno?

Lo chiamerei riprogettazione .

Aggiornare

Molte risposte interessanti sull'interfaccia, ma spostare il refactoring del metodo non cambierebbe l'interfaccia?

Di cosa? Le classi specifiche, sì. Ma queste classi sono direttamente visibili al mondo esterno in qualche modo? In caso contrario, poiché si trovano all'interno del programma e non fanno parte dell'interfaccia esterna (API / GUI) del programma , nessuna modifica apportata è osservabile da parti esterne (a meno che la modifica non rompa qualcosa, ovviamente).

Sento che al di là di questo c'è una domanda più profonda: esiste una classe specifica come entità indipendente da sola? Nella maggior parte dei casi, la risposta è no : la classe esiste solo come parte di un componente più grande, un ecosistema di classi e oggetti, senza il quale non può essere istanziato e / o è inutilizzabile. Questo ecosistema non include solo le sue dipendenze (dirette / indirette), ma anche altre classi / oggetti che dipendono da esso. Questo perché senza queste classi di livello superiore, la responsabilità associata alla nostra classe potrebbe essere insignificante / inutile per gli utenti del sistema.

Ad esempio nel nostro progetto che si occupa di noleggio auto, esiste un Chargeclasse. Questa classe non è di alcuna utilità per gli utenti del sistema da sola, perché gli agenti di stazione di noleggio e i clienti non possono fare molto con un addebito individuale: si occupano dei contratti di contratto di noleggio nel loro insieme (che comprendono un sacco di diversi tipi di addebiti) . Gli utenti sono per lo più interessati alla somma totale di questi addebiti, che dovranno pagare alla fine; l'agente è interessato alle diverse opzioni di contratto, alla durata del noleggio, al gruppo di veicoli, al pacchetto assicurativo, agli articoli extra ecc. ecc. selezionati, che (tramite sofisticate regole commerciali) regolano quali spese sono presenti e come viene calcolato il pagamento finale di questi. E i rappresentanti dei paesi / analisti aziendali si preoccupano delle regole aziendali specifiche, delle loro sinergie ed effetti (sul reddito dell'azienda, ecc.).

Recentemente ho refactored questa classe, rinominando la maggior parte dei suoi campi e metodi (per seguire la convenzione standard di denominazione Java, che è stata totalmente trascurata dai nostri predecessori). Pianifico anche ulteriori rifatturazioni da sostituire Stringe charcampi con più appropriati enume booleantipi. Tutto ciò cambierà sicuramente l'interfaccia della classe, ma (se svolgo correttamente il mio lavoro) nessuno di questi sarà visibile agli utenti della nostra app. Nessuno di loro si preoccupa di come sono rappresentate le singole accuse, anche se sicuramente conoscono il concetto di accusa. Avrei potuto selezionare ad esempio un centinaio di altre classi che non rappresentavano alcun concetto di dominio, quindi essendo anche concettualmente invisibile agli utenti finali, ma ho pensato che fosse più interessante scegliere un esempio in cui vi sia almeno una certa visibilità a livello di concetto. Questo dimostra bene che le interfacce di classe sono solo rappresentazioni di concetti di dominio (nella migliore delle ipotesi), non la cosa reale *. La rappresentazione può essere modificata senza influire sul concetto. E gli utenti hanno solo e comprendono il concetto; è nostro compito fare la mappatura tra concetto e rappresentazione.

* E si può facilmente aggiungere che il modello di dominio, che la nostra classe rappresenta, è esso stesso solo una rappresentazione approssimativa di alcune "cose ​​reali" ...


3
pignolo, direi che il "cambiamento di design" non riprogetta. la riprogettazione sembra troppo sostanziale.
user606723

4
interessante - nel tuo esempio la classe A è "un corpo di codice esistente", e se il metodo M è pubblico, allora il comportamento esterno di A viene modificato. Quindi potresti probabilmente dire che la classe A è stata ridisegnata, mentre il sistema generale è in fase di refactoring.
salsiccia

Mi piace osservabile per gli utenti. Ecco perché non direi che il superamento dei test unitari è un segno, ma piuttosto un test end-to-end o l'integrazione sarebbe un segno.
Andy Wiesendanger,

8

Esterno significa semplicemente interfaccia nel suo vero significato linguale. Considera una mucca per questo esempio. Finché nutri delle verdure e ottieni il latte come valore di ritorno, non ti interessa come funzionano i suoi organi interni. Ora, se Dio cambia gli organi interni delle mucche, in modo che il suo sangue diventi di colore blu, fintanto che il punto di ingresso e il punto di uscita (bocca e latte) non cambiano, può essere considerato un refactoring.


1
Non penso sia una buona risposta. Il refactoring può implicare modifiche alle API. Ma non dovrebbe influire sull'utente finale o sul modo in cui i file / input vengono letti / generati.
rd

3

Per me, il refactoring è stato molto produttivo / confortevole quando i limiti sono stati stabiliti da test e / o da specifiche formali.

  • Questi confini sono sufficientemente rigidi da farmi sentire al sicuro sapendo che se di tanto in tanto attraverso, saranno rilevati abbastanza presto in modo da non dover ripristinare molte modifiche per recuperare. D'altra parte, offrono un margine di manovra sufficiente per migliorare il codice senza preoccuparsi di cambiare il comportamento irrilevante.

  • La cosa che mi piace particolarmente è che questi tipi di confini sono adattivi per così dire. Voglio dire, 1) faccio la modifica e verifico che sia conforme alle specifiche / ai test. Quindi, 2) viene passato al QA o al test utente - nota qui, potrebbe ancora fallire perché manca qualcosa nelle specifiche / nei test. OK, se 3a) il test ha esito positivo, ho finito. Altrimenti, se 3b) il test fallisce, allora I 4) ripristina la modifica e 5) aggiunge test o chiarisce le specifiche in modo che la prossima volta questo errore non si ripeta. Si noti che non importa se il test passa o fallisce, io guadagno qualcosa - uno di codice / test / spec viene migliorata - i miei sforzi non si trasformano in rifiuti totali.

Per quanto riguarda i confini di altri tipi, finora non ho avuto molta fortuna.

"Osservabile per gli utenti" è una scommessa sicura se si ha una disciplina per seguirla - che per me in qualche modo ha sempre comportato un grande sforzo nell'analizzare / creare nuovi test - forse troppo sforzo. Un'altra cosa che non mi piace di questo approccio è che seguirlo alla cieca può rivelarsi troppo restrittivo. - Questa modifica è vietata perché con essa il caricamento dei dati richiederà 3 secondi anziché 2. - Uhm bene che ne dici di verificare con gli utenti / esperto UX se questo è rilevante o no? - In nessun modo, è vietato qualsiasi cambiamento nel comportamento osservabile, punto. Sicuro? Scommetti! produttivo? non proprio.

Un altro che ho provato è quello di mantenere la logica del codice (il modo in cui lo capisco durante la lettura). Fatta eccezione per i cambiamenti più elementari (e in genere non molto fruttuosi), questo è sempre stato una lattina di vermi ... o dovrei dire una lattina di bug? Intendo bug di regressione. È troppo facile rompere qualcosa di importante quando si lavora con il codice spaghetti.


3

Il libro Refactoring è piuttosto forte nel suo messaggio che puoi eseguire il refactoring solo quando hai una copertura di test unitari .

Quindi, potresti dire che fintanto che non stai superando nessuno dei tuoi test unitari, stai refactoring . Quando si interrompono i test, non si esegue più il refactoring.

Ma: che ne dici di semplici refactoring come cambiare i nomi delle classi o dei membri? Non rompono i test?

Sì, sì, e in ogni caso dovrai considerare se l'interruzione è significativa. Se il tuo SUT è un'API / SDK pubblica, la ridenominazione è, in effetti, un cambiamento decisivo. In caso contrario, probabilmente è OK.

Tuttavia, considera che spesso i test non si interrompono perché hai cambiato il comportamento che ti interessa davvero, ma perché i test sono Test fragili .


3

Il modo migliore per definire "comportamento esterno", in questo contesto, può essere "casi di test".

Se si refactoring il codice e continua a superare i casi di test (definiti prima del refactoring), il refactoring non ha modificato il comportamento esterno. Se uno o più casi di test non riescono, allora si hanno cambiato il comportamento esterno.

Almeno, questa è la mia comprensione dei vari libri pubblicati sull'argomento (ad es. Di Fowler).


2

Il confine sarebbe la linea che indica tra chi sviluppa, mantiene, supporta il progetto e coloro che sono i suoi utenti diversi dai sostenitori, manutentori, sviluppatori. Quindi, per il mondo esterno, il comportamento sembra lo stesso mentre le strutture interne alla base del comportamento sono cambiate.

Quindi dovrebbe essere OK spostare le funzioni tra le classi purché non siano quelle visualizzate dagli utenti.

Fintanto che la rilavorazione del codice non modifica i comportamenti esterni, aggiunge nuove funzioni o rimuove le funzioni esistenti, immagino sia corretto chiamare la rilavorazione un refactoring.


Disaccordo. Se sostituisci l'intero codice di accesso ai dati con nHibernate, non cambia il comportamento esterno ma non segue le "tecniche disciplinate" di Fowler. Questo sarebbe riprogettare e chiamarlo refactoring nasconde il fattore di rischio coinvolto.
pdr

1

Con tutto il rispetto, dobbiamo ricordare che gli utenti di una classe non sono gli utenti finali delle applicazioni costruite con la classe, ma piuttosto le classi che vengono implementate utilizzando - chiamando o ereditando da - la classe da refactoring .

Quando dici che "il comportamento esterno non dovrebbe cambiare", intendi che per quanto riguarda gli utenti, la classe si comporta esattamente come prima. È possibile che l'implementazione originale (non refactored) fosse una singola classe e che la nuova implementazione (refactored) abbia una o più superclasse su cui è costruita la classe, ma gli utenti non vedono mai l'interno (l'implementazione) vedi solo l'interfaccia.

Quindi, se una classe ha un metodo chiamato "doSomethingAmazing", non importa all'utente se questo è implementato dalla classe a cui si riferisce o da una superclasse su cui è costruita quella classe. Tutto ciò che conta per l'utente è che il nuovo (refactored) "doSomethingAmazing" abbia lo stesso risultato del vecchio (non riformato) "doSomethingAmazing.

Tuttavia, ciò che viene chiamato refactoring in molti casi non è un vero refactoring, ma forse una reimplementazione che viene eseguita per facilitare la modifica del codice per aggiungere alcune nuove funzionalità. Quindi, in questo caso successivo di (pseudo) -fattorizzazione, il nuovo codice (refactored) fa effettivamente qualcosa di diverso, o forse qualcosa di più del vecchio.


Che cosa succede se un modulo di Windows viene utilizzato per visualizzare una finestra di dialogo "Sei sicuro di voler premere il pulsante OK?" e ho deciso di rimuoverlo perché funziona poco bene e infastidisce gli utenti, quindi ho ripensato il codice, riprogettato, modificato, rimosso, altro?
Giobbe

@job: hai modificato il programma per soddisfare le nuove specifiche.
jmoreno,

IMHO potresti mescolare qui diversi criteri. La riscrittura del codice da zero non è in effetti un refactoring, ma è così indipendentemente dal fatto che abbia cambiato il comportamento esterno o meno. Inoltre, se cambiare un'interfaccia di classe non fosse refactoring, come mai Move Method et al. esiste nel catalogo dei refactoring ?
Péter Török,

@ Péter Török - Dipende interamente da cosa intendi per "cambiare un'interfaccia di classe" perché in un linguaggio OOP che implementa l'ereditarietà, l'interfaccia di una classe include non solo ciò che viene implementato dalla classe stessa da tutti i suoi antenati. Cambiare un'interfaccia di classe significa rimuovere / aggiungere un metodo all'interfaccia (o cambiare la firma di un metodo - cioè il numero un tipo di parametri che vengono passati). Refactoring indica chi sta rispondendo al metodo, alla classe o a una superclasse.
Zeke Hansell,

IMHO: l'intera domanda potrebbe essere troppo esoterica per essere utile ai programmatori.
Zeke Hansell,

1

Per "comportamento esterno" parla principalmente dell'interfaccia pubblica, ma comprende anche output / artefatti del sistema. (cioè hai un metodo che genera un file, cambiare il formato del file cambierebbe il comportamento esterno)

e: Considererei il "metodo di spostamento" una modifica al comportamento esterno. Tenete presente che Fowler sta parlando di basi di codice esistenti che sono state rilasciate in libertà. A seconda della situazione, potresti essere in grado di verificare che la modifica non interrompa alcun client esterno e procedere nel modo giusto.

e2: "Qual è la parola giusta per rielaborazioni che cambiano il comportamento esterno?" - Refactoring API, Breaking Change, ecc ... è ancora refactoring, non segue le best practice per il refactoring di un'interfaccia pubblica che è già in circolazione con i client.


Ma se sta parlando di un'interfaccia pubblica, che dire del refactoring "Metodo di spostamento"?
SiberianGuy

@Idsa Modificato la mia risposta per rispondere alla tua domanda.

@kekela, ma non mi è ancora chiaro dove finisca questa cosa del "refactoring"
SiberianGuy

@idsa Secondo la definizione che hai pubblicato, cessa di essere un refactoring nel momento in cui cambi un'interfaccia pubblica. (spostare un metodo pubblico da una classe all'altra sarebbe un esempio di questo)

0

Il "metodo di spostamento" è una tecnica di refactoring, non un refactoring da solo. Un refactoring è il processo di applicazione di diverse tecniche di refactoring alle classi. Lì, quando dici, ho applicato "metodo move" a una classe, non intendi realmente "ho refactored (la classe)", intendi effettivamente "ho applicato una tecnica di refactoring su quella classe". Il refactoring, nel suo significato più puro, viene applicato al design, o più specifico, a una parte del design dell'applicazione che può essere vista come una scatola nera.

Si potrebbe dire che "refactoring" utilizzato nel contesto delle classi, significa "tecnica di refactoring", quindi "metodo di spostamento" non interrompe la definizione di refactoring-the-process. Nella stessa pagina, "refactoring" nel contesto del design, non rompe le funzionalità esistenti nel codice, ma "rompe" solo il design (che comunque è il suo scopo).

In conclusione, "il confine", menzionato nella domanda, viene superato se si confonde (mescolando: D) il refactoring-the-tecnica con il refactoring-the-process.

PS: bella domanda, a proposito.


Leggi più volte, ma ancora non capisco
SiberianGuy

quale parte non hai capito? (esiste il processo di refactoring al quale si applica la tua definizione o la tecnica di refactoring, nel tuo esempio, il metodo move, a cui la definizione non si applica; pertanto, il metodo move non interrompe la definizione di refactoring-the- processare o non oltrepassare i suoi confini, qualunque essi siano). sto dicendo che la preoccupazione che hai non dovrebbe esistere per il tuo esempio. il confine del refactoring non è qualcosa di sfocato. stai solo applicando una definizione di qualcosa a qualcos'altro.
Belun,

0

se parli di numeri di factoring, allora stai descrivendo il gruppo di numeri interi che quando moltiplicati insieme equivalgono al numero iniziale. Se prendiamo questa definizione per il factoring e la applichiamo al termine di programmazione refactor, il refactoring comporterebbe la scomposizione di un programma nelle unità logiche più piccole, in modo tale che quando vengono eseguite come programma, producano lo stesso output (dato lo stesso input ) come il programma originale.


0

I limiti di un refactoring sono specifici di un determinato refactoring. Semplicemente non può esserci una risposta e comprende tutto. Uno dei motivi è che il termine refactoring non è di per sé specifico.

Puoi refactoring:

  • Codice sorgente
  • Architettura del programma
  • Progettazione dell'interfaccia utente / Interazione dell'utente

e sono sicuro di molte altre cose.

Forse refactor potrebbe essere definito come

"un'azione o un insieme di azioni che riducono l'entropia in un particolare sistema"


0

Ci sono sicuramente alcuni limiti su quanto lontano puoi fare il refactoring. Vedi: Quando due algoritmi sono uguali?

Le persone di solito considerano gli algoritmi più astratti dei programmi che li implementano. Il modo naturale di formalizzare questa idea è che gli algoritmi sono classi di equivalenza di programmi rispetto a un'adeguata relazione di equivalenza. Sosteniamo che non esiste una simile relazione di equivalenza.

Quindi, non andare troppo lontano, o non avrai alcuna fiducia nel risultato. D'altra parte, l'esperienza impone che spesso è possibile sostituire un algoritmo con un altro e ottenere le stesse risposte, a volte più velocemente. Questa è la bellezza, eh?


0

Puoi pensare a un significato "esterno"

Esterno per alzarsi quotidianamente.

Quindi qualsiasi modifica al sistema che non ha effetto su nessun maiale, può essere considerata come refactoring.

La modifica di un'interfaccia di classe non è un problema, se la classe viene utilizzata solo da un singolo sistema creato e gestito dal team. Tuttavia, se la classe è una classe pubblica nel framework .net utilizzata da ogni programmatore .net, è una questione molto diversa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.