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

27

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

Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior. Its heart is a series of small behavior preserving transformations. Each transformation (called a 'refactoring') does little, but a sequence of transformations can produce a significant restructuring. Since each refactoring is small, it's less likely to go wrong. The system is also kept fully working after each small refactoring, reducing the chances that a system can get seriously broken during the restructuring.

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

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

Aggiornamento. Un sacco di risposte interessanti all'interfaccia, ma non spostare il refactoring del metodo cambia l'interfaccia?

    
posta SiberianGuy 15.08.2011 - 00:10
fonte

13 risposte

25

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

Quindi, se sposti il metodo M dalla classe A alla classe B, ed entrambe le classi sono profonde all'interno di un'applicazione, e nessun utente può osservare alcun cambiamento nel comportamento dell'app a causa del cambiamento, allora puoi correttamente chiamarlo refactoring .

Se, OTOH, qualche altro sottosistema / componente di livello superiore cambia il suo comportamento o si interrompe a causa della modifica, questo è effettivamente (solitamente) osservabile per gli utenti (o almeno per gli amministratori di sistema che controllano i registri). Oppure se le tue classi facevano parte di un'API pubblica, potrebbe esserci un codice di terze parti che dipende dal fatto che M fa parte della classe A, non B. Quindi nessuno di questi casi è refactoring in senso stretto.

there is a tendency to call any code rework as refactoring which is, I guess, incorrect.

In effetti, è una triste, ma prevedibile conseguenza, che il refactoring diventerà di moda. Gli sviluppatori hanno eseguito la rilavorazione del codice in modo ad hoc per anni, ed è certamente più facile imparare una nuova parola d'ordine piuttosto che analizzare e modificare le abitudini radicate.

So what is the right word for reworks which change external behaviour?

Lo chiamerei riprogettazione .

Aggiornamento

A lot of interesting answers about interface, but wouldn't move method refactoring change the interface?

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

Sento che c'è una domanda più profonda al di là di questa: esiste una classe specifica come entità indipendente da sola? Nella maggior parte dei casi, la risposta è no : la 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 può essere priva di significato / inutile per gli utenti del sistema.

es. nel nostro progetto che riguarda gli affitti di auto, esiste una classe Charge . Questa classe non ha utilità per gli utenti del sistema da sola, poiché gli agenti delle stazioni di noleggio e i clienti non possono fare molto con un addebito individuale: trattano i contratti di affitto nel loro complesso (che includono un sacco di diversi tipi di addebiti) . Gli utenti sono per lo più interessati alla somma totale di queste spese, che devono pagare alla fine; l'agente è interessato alle diverse opzioni contrattuali, alla durata del noleggio, al gruppo di veicoli, al pacchetto assicurativo, agli articoli ecc. ecc. selezionati, che (tramite sofisticate regole aziendali) determinano quali sono le spese e come viene calcolato il pagamento finale fuori da questi. E i rappresentanti dei paesi / analisti aziendali si preoccupano delle regole commerciali specifiche, della loro sinergia ed effetti (sul reddito dell'azienda, ecc.). Una singola carica di per sé non ha significato senza l'immagine più grande.

Recentemente ho rifattorizzato questa classe, rinominando la maggior parte dei suoi campi e metodi (per seguire la convenzione di denominazione Java standard, che era completamente trascurata dai nostri predecessori). Inoltre, pianifico ulteriori rifattori per sostituire i campi String e char con i tipi più appropriati enum e boolean . Tutto questo cambierà sicuramente l'interfaccia della classe, ma (se faccio il mio lavoro correttamente) nessuno di questi sarà visibile agli utenti della nostra app. A nessuno di loro importa quanto siano rappresentate le singole accuse, anche se sicuramente conoscono il concetto di carica . Avrei potuto selezionare ad esempio un centinaio di altre classi che non rappresentano alcun concetto di dominio, quindi essere anche concettualmente invisibili agli utenti finali, ma ho pensato che fosse più interessante scegliere un esempio in cui vi è almeno una certa visibilità a livello di concetto. Questo dimostra 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 influenzare il 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, è di per sé solo una rappresentazione approssimativa di qualche "cosa reale" ...

    
risposta data 15.08.2011 - 00:29
fonte
8

Esterni significa semplicemente interfaccia nel suo vero significato linguale. Considera una mucca per questo esempio. Finché alimenti alcune verdure e prendi latte come valore di ritorno, non ti importa 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, finché il punto di ingresso e il punto di uscita (bocca e latte) non cambiano, può essere considerato un refactoring.

    
risposta data 15.08.2011 - 08:35
fonte
3

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

  • Questi limiti sono sufficientemente rigidi per farmi sentire al sicuro sapendo che se incrocio di tanto in tanto, verrà rilevato abbastanza presto da non dover eseguire il rollback di molte modifiche da 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 limiti sono adattivi per così dire. Voglio dire, 1) Faccio il cambiamento e verificare che sia conforme alle specifiche / test. Quindi, 2) è passato al QA o al test dell'utente - nota qui, può ancora fallire perché manca qualcosa nelle specifiche / test. OK, se passa il test 3a), ho finito, va bene. Altrimenti, se il test 3b) fallisce, allora I 4) ripristina le modifiche e 5) aggiungendo test o chiarendo le specifiche in modo che la prossima volta questo errore non si ripeta. Nota che indipendentemente dal fatto che il test passi o fallisca, I guadagno qualcosa - il codice / test / le specifiche vengono migliorati - i miei sforzi non si trasformano in sprechi totali.

Come per i confini di altri tipi - finora non ho avuto molta fortuna.

"Osservabile per gli utenti" è una scommessa sicura se uno ha una disciplina per seguirlo - che per me in qualche modo comportava sempre molto sforzo nell'analizzare esistenti / creare nuovi test - forse troppa fatica. Un'altra cosa che non mi piace di questo approccio è che seguire ciecamente potrebbe rivelarsi troppo restrittivo. - Questa modifica è vietata perché con esso il caricamento dei dati impiegherà 3 sec anziché 2. - Uhm bene, che ne dici di verificare con utenti / esperti UX se questo è rilevante o meno? - In nessun modo, qualsiasi cambiamento nel comportamento osservabile è proibito, periodo. Sicuro? Scommetti! produttivo? non proprio.

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

    
risposta data 16.08.2011 - 00:35
fonte
3

Il messaggio Rifattorizzazione è abbastanza strong nel suo messaggio che puoi eseguire solo il refactoring quando hai copertura del test unitario .

Quindi, potresti dire che finché non stai violando nessuno dei tuoi test unitari, stai eseguendo il refactoring . Quando interrompi i test, non esegui più il refactoring.

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

Sì, e in ogni caso dovrai considerare se tale interruzione è significativa. Se il tuo SUT è un API / SDK pubblico, allora un rinominare è, in effetti, un cambiamento sostanziale. In caso contrario, probabilmente è OK.

Tuttavia, considera che spesso i test non si interrompono perché hai cambiato il comportamento a cui tatti, ma perché i test sono Test fragili .

    
risposta data 19.08.2011 - 10:54
fonte
3

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

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

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

    
risposta data 22.08.2011 - 16:35
fonte
2

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

Quindi dovrebbe essere OK spostare le funzioni tra le classi finché non sono quelle che vedono gli utenti.

Finché la rielaborazione del codice non modifica i comportamenti esterni, aggiunge nuove funzioni o rimuove funzioni esistenti, suppongo che sia corretto chiamare la rilavorazione come refactoring.

    
risposta data 15.08.2011 - 00:31
fonte
1

Con tutto il dovuto rispetto, dobbiamo ricordare che gli utenti di una classe non sono gli utenti finali delle applicazioni che sono costruite con la classe, ma piuttosto le classi che sono implementate utilizzando - chiamandole o ereditandole - il classe di essere refactored.

Quando dici che "il comportamento esterno non dovrebbe cambiare" intendi che, per quanto riguarda gli utenti, la classe si comporta esattamente come prima. Può essere che l'implementazione originale (non refactored) fosse una singola classe, e la nuova implementazione (refactored) abbia una o più super classi 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 ha importanza per l'utente se è implementato dalla classe a cui si riferisce, o da una superclasse su cui è costruita tale classe. Tutto ciò che importa all'utente è che il nuovo (refactored) "doSomethingAmazing" ha lo stesso risultato del vecchio (non rifatto) "doSomethingAmazing.

Tuttavia, ciò che viene chiamato refactoring in molti casi non è un vero refactoring, ma forse una reinterpretazione che viene fatta per rendere il codice più facile da modificare per aggiungere alcune nuove funzionalità. Quindi in questo caso successivo di (pseudo) riscrittura, il nuovo codice (refactored) effettivamente fa qualcosa di diverso, o forse qualcosa di più del vecchio.

    
risposta data 15.08.2011 - 05:12
fonte
1

Per "comportamento esterno", in primo luogo, sta parlando dell'interfaccia pubblica, ma comprende anche gli output / artefatti del sistema. (cioè hai un metodo che genera un file, cambiando il formato del file cambierebbe il comportamento esterno)

e: Considererei il "metodo di spostamento" una modifica al comportamento esterno. Tieni presente che Fowler sta parlando delle basi di codice esistenti che sono state rilasciate in natura. A seconda della situazione, potresti essere in grado di verificare che le tue modifiche non infrangono alcun client esterno e procedi in modo allegro.

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

    
risposta data 15.08.2011 - 18:12
fonte
0

"Move method" è una tecnica di refactoring, non un refactoring di per sé. Un refactoring è il processo di applicazione di diverse tecniche di refactoring alle classi. Lì, quando dici, ho applicato il "metodo move" a una classe, in realtà non intendi "ho refactored (la classe)", in realtà intendi "ho applicato una tecnica di refactoring su quella classe". Refactoring, in esso il significato più puro è applicato al design, o più specifico, ad una parte del design dell'applicazione che può essere vista come una scatola nera.

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

In conclusione, "il confine", citato nella domanda, è attraversato se confondi (mix: D) refactoring-the-technique con refactoring-the-process.

PS: buona domanda, btw.

    
risposta data 18.08.2011 - 17:51
fonte
0

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

    
risposta data 18.08.2011 - 22:38
fonte
0

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

Puoi refactoring:

  • Codice sorgente
  • Architettura del programma
  • Interfaccia utente / Interazione utente

e sono sicuro molte altre cose.

Forse il refactor potrebbe essere definito come

"an action or set of actions which decrease the entropy in a particular system"

    
risposta data 19.08.2011 - 01:37
fonte
0

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

People usually regard algorithms as more abstract than the programs that implement them. The natural way to formalize this idea is that algorithms are equivalence classes of programs with respect to a suitable equivalence relation. We argue that no such equivalence relation exists.

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 di questo, eh?

    
risposta data 19.08.2011 - 03:03
fonte
0

Puoi pensare al significato "esterno"

External to the daily stand up.

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 che viene utilizzata da ogni programmatore .net, è una questione molto diversa.

    
risposta data 22.08.2011 - 17:59
fonte

Leggi altre domande sui tag