È utile per il mini-refattore del codice nella speranza di migliorare la qualità o è semplicemente "spostare il codice in giro" senza molti vantaggi?


12

Esempio

Mi sono imbattuto in un codice monolitico che fa "tutto" in un unico posto - caricando i dati dal database, mostrando markup HTML, fungendo da router / controller / azione. Ho iniziato ad applicare SRP spostando il codice del database nel suo file, fornendo una migliore denominazione delle cose, e tutto sembrava a posto, ma poi ho iniziato a dubitare del perché lo stia facendo.

Perché refactor? Qual è lo scopo? È inutile? Qual è il vantaggio? Si noti che ho lasciato per lo più il file monolitico così com'è, ma ho eseguito il refactoring solo della porzione più piccola che era rilevante per l'area in cui avevo bisogno di fare un po 'di lavoro.

Codice originale:

Per fare un esempio concreto, mi sono imbattuto in questo frammento di codice: carica le specifiche del prodotto da un ID prodotto noto o da un ID versione selezionato dall'utente:

if ($verid)
    $sql1 = "SELECT * FROM product_spec WHERE id = " . clean_input($verid);
else
    $sql1 = "SELECT * FROM product_spec WHERE product_id = " . clean_input($productid) ;
$result1 = query($sql1);
$row1 = fetch_array($result1);
/* html markup follows */

refactoring:

Dal momento che sto facendo un lavoro che mi richiede di cambiare le cose in questa parte specifica del codice, l'ho modificato per utilizzare il modello di repository e l'ho aggiornato per utilizzare le strutture MySQL orientate agli oggetti:

//some implementation details omitted 
$this->repository = new SpecRepository($mysql);
if ($verid)
    $row1 = $this->repository->getSpecByVersion($verid);
else 
    $row1 = $this->repository->getSpecByProductId($productid);
/* html markup follows to be refactored or left alone till another time*/

//added new class:
class SpecRepository extends MySqlRepository
{

    function getSpecByVersion(int $verid)
    {
        return $this->getMySql()->paramQuery("
            SELECT * FROM product_spec WHERE id = ?
        ", $verid)->getSingleArray();
    }

    function getSpecByProductId(int $productid)
    {
        return $this->getMySql()->paramQuery("
            SELECT * FROM product_spec WHERE product_id = ?
        ", $productid)->getSingleArray();
    }
}

Dovrei farlo?

Guardando indietro alle modifiche, il codice è ancora lì, codice con la stessa funzionalità, ma in file diversi, nomi, luoghi diversi, usando uno stile più orientato agli oggetti piuttosto che procedurale. In realtà è divertente notare che il codice refactored sembra molto più gonfio nonostante abbia la stessa funzionalità.

Prevedo alcune risposte che dicono "se non conosci i motivi per cui fai il refactoring, non farlo", e forse potrei essere d'accordo. Le mie ragioni sono di migliorare la qualità del codice nel tempo (e la mia speranza è che lo farò seguendo SRP e altri principi).

Sono abbastanza buone ragioni o sto sprecando il mio tempo a "riorganizzare il codice in giro" in questo modo? Nel complesso il refactoring sembra un po 'come calpestare l'acqua per essere onesti - ci vuole tempo e diventa più "separato" per quanto riguarda la SRP, ma nonostante le mie buone intenzioni non mi sento come se stessi facendo incredibili miglioramenti. Quindi, discutendo se è meglio lasciare il codice come prima e non refactoring.

Perché ho refactoring in primo luogo?

Nel mio caso sto aggiungendo nuove funzionalità per una nuova linea di prodotti, quindi devo seguire la struttura del codice esistente per linee di prodotti simili o scrivere la mia.


OT, ma qualcosa da considerare è che Select * from ..può essere considerato un anti-pattern. Vedi stackoverflow.com/q/3639861/31326
Peter M

1
@PeterM: A meno che tu non stia scrivendo un ORM, nel qual caso select *diventa una "best practice".
Robert Harvey,

@RobertHarvey Nel qual caso mi sto chiedendo perché stai scrivendo il tuo ORM: D
Peter M

Ho fatto dei rifattori per cause più irrilevanti. Ridurre il debito tecnico è positivo non appena i benefici superano i costi. Sembra che questo sia il tuo caso. La domanda è: hai i test giusti per assicurarti che il codice funzioni ancora come previsto?
Laiv

1
L'affidabilità dovrebbe essere la metrica predominante nel refactoring dell'IMO, non la manutenibilità che si riferisce alla mutevolezza, poiché l'affidabilità riduce in primo luogo le ragioni per cui le cose cambiano. Migliora la stabilità e il codice che è perfettamente stabile e affidabile e soddisfa tutto ciò che deve fare comporta pochi costi di manutenzione, poiché ha pochissimi motivi per dover mai cambiare. Cerca di ridurre le ragioni per cui le cose cambiano invece di cercare di renderle il più facili possibile da cambiare. Cercare il primo tenderà anche a dare un po 'del secondo.

Risposte:


13

Nell'esempio concreto che hai fornito, il refactoring potrebbe non essere stato necessario, almeno non ancora. Ma hai visto che c'era un potenziale per un codice più disordinato in futuro che avrebbe portato a un refactoring più lungo e noioso, quindi hai preso l'iniziativa e ripulito le cose ora.

Direi anche che il vantaggio che hai ottenuto qui era il codice che è più facile da capire, almeno dal mio punto di vista, come qualcuno che non conosce la tua base di codice.


In generale:

Il refactoring semplifica la comprensione del codice da parte di altri sviluppatori?

Il refactoring semplifica il miglioramento del codice?

Il refactoring semplifica la gestione del codice?

Il refactoring semplifica il debug?

Se la risposta a una di queste era "sì", ne valeva la pena, ed era più che un semplice "spostamento del codice?

Se la risposta a tutte queste domande fosse "no", forse non è stato necessario riformattarla.

La dimensione finale della base di codice potrebbe essere maggiore in termini di LoC (anche se alcuni refactoring possono ridurre la base di codice semplificando il codice ridondante), ma ciò non dovrebbe essere un fattore se ci sono altri guadagni.


9

Il refactoring si gonfia se si sta smontando un monolite.

Tuttavia, hai già trovato il vantaggio.

"Nel mio caso sto aggiungendo nuove funzionalità per una nuova linea di prodotti."

Non è possibile aggiungere funzionalità a un monolite molto facilmente senza rompere le cose.

Hai detto che lo stesso codice sta ottenendo i dati e facendo il markup. Fantastico, fino a quando non vuoi cambiare le colonne che stai selezionando e manipolando per mostrare in determinate condizioni. Improvvisamente, il codice di markup smette di funzionare in un determinato caso e devi eseguire il refactoring.


sì, ma posso anche continuare ad aggiungere al monolito anche per nuove funzionalità :) Vale a dire aggiungerei un nuovo if/elseblocco e seguirò lo stile originale del codice per aggiungere istruzioni SQL, e quindi inserirei un nuovo markup HTML nello stesso file monolite. E per cambiare le colonne troverei il blocco if / else responsabile dell'alloggiamento delle righe SQL e lo avrei modificato per apportare modifiche alle colonne, senza suddividere il file.
Dennis,

Con un approccio refactored, probabilmente andrei prima al blocco if / else nel monolito per vedere che devo andare al file repository separato per cambiare SQL. O se di recente ho lavorato su quel monolito saprei invece di andare direttamente nel file del repository, bypassando il monolito. O se avessi cercato nel mio codebase lo specifico SQL la ricerca mi avrebbe inserito nel nuovo file di repository bypassando anche il monolito
Dennis

2

Considero il refactoring se so che dovrò lavorare mesi o addirittura anni con quel progetto. Se devo fare un cambiamento qua e là, allora mi oppongo all'impulso di spostare il codice.

Il modo preferito di eseguire il refactoring è quando ho un codice di base con una sorta di test automatizzati. Anche così devo stare molto attento a ciò che cambio per assicurarmi che le cose funzionino come prima. Perderai la fiducia molto presto se fornisci bug in aree diverse da quella che dovresti lavorare.

Apprezzo il refactoring per i seguenti motivi:

  • Capisco meglio il codice
  • Rimuovo il codice duplicato, quindi se ho un bug in un posto non devo cercare ogni dove era stato incollato il codice
  • Creo classi che hanno il loro ruolo ben definito. Ad esempio, posso riutilizzare lo stesso repository per idratare una pagina HTML, un feed XML o un processo in background. Ciò non sarebbe stato possibile se il codice di accesso al database risiedesse solo nella classe HTML
  • Rinomino variabili, metodi, classi, moduli, cartelle se non corrispondono alla realtà attuale; I commentil codice tramite nomi di variabili, nomi di metodi
  • Risolvo bug complessi con una combinazione tra unit test e refactoring
  • Ho la flessibilità di sostituire interi moduli, pacchetti. Se l'ORM corrente è obsoleto, difettoso o non aggiornato, è più facile sostituirlo se non è distribuito su tutto il progetto.
  • Scopro nuove funzionalità o miglioramenti che portano a proposte per il cliente.
  • Ho creato componenti riutilizzabili. Questo è stato uno dei maggiori vantaggi perché il lavoro svolto in quel progetto avrebbe potuto essere riutilizzato per un altro cliente / progetto.
  • Comincio spesso con la soluzione più stupida, codificata e stravagante e la refactoring più tardi una volta appreso di più. Questo momento successivo potrebbe essere oggi o forse dopo diversi giorni. Non voglio dedicare troppo tempo alla architectsoluzione. Questo arriverà durante la scrittura, riscrivendo il codice avanti e indietro.

In un mondo perfetto, i refactoring dovrebbero essere verificati mediante test unitari, test di integrazione e così via. Se non ce ne sono, prova ad aggiungerli. Anche alcuni IDE potrebbero essere di grande aiuto. Di solito uso un plugin che costa denaro. Voglio passare poco tempo con i refactoring ed essere molto efficiente al riguardo.

Ho anche introdotto dei bug. Vedo perché alcuni AQ chiedono are you guys doing refactoring? because everything stopped working!Questo è il rischio che il team deve affrontare e dobbiamo sempre essere trasparenti al riguardo.

Nel corso degli anni ho scoperto che il refactoring continuo ha migliorato la mia produttività. È stato molto più semplice aggiungere nuove funzionalità. Anche le grandi ricostruzioni non hanno richiesto un'intera riscrittura, come spesso accadeva quando la base di codice non era adattata all'evoluzione del prodotto. Inoltre è divertente.


Questo è molto vicino a un voto negativo - Non c'è "io" in "Programmatore di computer". Per "io", io io "io" (voto negativo) o intendi gli sviluppatori che lavorano sul codice ora e in futuro?
Mattnz,

L'ho notato anche a me, ma l'ho messo sul testo. Inoltre ho lavorato come sviluppatore solista per molti anni e quando ho scritto questa risposta ho ribadito principalmente da quell'esperienza. Potrei sostituirlo Icon toperò.
Adrian Iftode,

1

Penso che la domanda che ti devi porre non sia tanto "C'è un vantaggio?" ma "Chi pagherà per questo lavoro?"

Tutti possiamo discutere dei benefici percepiti fino alla fine dei giorni, ma a meno che tu non abbia un cliente che crede in tali benefici e li valorizzi abbastanza per pagare i cambiamenti, non dovresti farli.

È fin troppo facile quando fai parte di un team di sviluppo da qualche parte per vedere il codice e vuoi riformattarlo per renderlo "migliore". Ma dare un valore al "codice migliore" quando il prodotto funziona già è davvero difficile.

Cose come lo "sviluppo più veloce di nuove funzionalità" non lo riducono perché sono solo costi ridotti. Devi generare vendite.


1

Secondo me il refactoring è un buon investimento.

Altrimenti riceverai presto debiti tecnici (problemi irrisolti, codice errato che funziona solo in determinate condizioni, ecc.). I debiti tecnici diventeranno presto sempre più grandi, fino a quando il software non sarà più mantenibile.

Ma per far funzionare il refactoring, devi anche investire in test. Dovresti creare test automatici, idealmente dovresti avere unit test e test di integrazione. Questo ti impedisce di violare il codice esistente.

Se devi convincere il tuo capo o i tuoi colleghi, dovresti leggere alcuni libri sui metodi agili (ad esempio SCRUM) e test-driven development.

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.