Il codice più vecchio dovrebbe essere aggiornato per usare costrutti di linguaggio più recenti o dovrebbero essere bloccati con costrutti obsoleti?


15

Voglio apportare alcuni miglioramenti ad alcuni codici ancora funzionali che sono stati scritti molto tempo fa, prima che il linguaggio di programmazione in cui era scritto aumentasse di funzionalità. In teoria, l'intero progetto utilizza la versione aggiornata del linguaggio; tuttavia, questo particolare modulo (e in effetti molti altri moduli) sono ancora scritti nel dialetto più vecchio.

Dovrei:

  • Non toccare le parti del codice che non devo toccare, ma scrivere la mia patch facendo uso delle nuove funzionalità del linguaggio che semplificano la scrittura della patch ma non vengono utilizzate in situazioni analoghe in qualsiasi altra parte del modulo? (Questa è la soluzione che sceglierei intuitivamente.)
  • Ignorare il fatto che sono trascorsi anni e riflettere lo stile che viene utilizzato nel resto del codice mentre si scrive la mia patch, comportandosi effettivamente come se stessi facendo lo stesso compito molti anni prima? (Questa soluzione considererei intuitivamente sciocca, ma data la quantità di confusione che tutti coloro che parlano di "buon codice" fanno per mantenere la coerenza a tutti i costi, forse questo è ciò che dovrei fare.)
  • Aggiornare l'intero modulo per utilizzare i costrutti e le convenzioni del linguaggio più recenti? (Questa è probabilmente la soluzione migliore, ma potrebbe richiedere molto tempo ed energia che potrebbero essere spesi meglio per un compito diverso.)

1
@JerryCoffin Non continuerò a discutere nei commenti: ho pubblicato su meta in cui possiamo avere una discussione adeguata.

Risposte:


10

È impossibile dare una risposta definitiva a questa domanda, perché dipende troppo dai dettagli della situazione.

La coerenza nello stile della base di codice è importante perché aiuta a semplificare la comprensione del codice, che è uno degli aspetti più importanti della manutenibilità.
Non saresti il ​​primo programmatore che è stato maledetto all'inferno per aver reso il codice difficile da capire mescolando stili diversi. Potresti anche essere te stesso a fare la maledizione tra anni.

D'altra parte, l'utilizzo di costrutti di nuovi linguaggi che non esistevano quando il codice è stato scritto per la prima volta non implica automaticamente che stai riducendo la manutenibilità o addirittura che stai rompendo lo stile del codice. Tutto dipende dalle caratteristiche linguistiche particolari che si desidera utilizzare e dalla familiarità del team con il vecchio stile del codice e le nuove funzionalità.

Ad esempio, in genere non sarebbe consigliabile iniziare a introdurre concetti di programmazione funzionale come mappare / ridurre a una base di codice completamente in stile OO, ma potrebbe funzionare per aggiungere una funzione lambda qua e là a un programma che non ha usato niente del genere prima.


1
Non sono parzialmente d'accordo. Dovremmo seguire un principio aperto vicino. Quindi dovremmo cercare di isolare il nuovo codice il più possibile e allo stesso tempo scrivere il nuovo codice con il costrutto della lingua più recente possibile.
Anand Vaidya,

3
@AnandVaidya: questa è l'IMHO un'aspettativa non realistica, dal momento che presuppone che il vecchio codice segua l'OCP o possa essere facilmente modificato per seguire l'OCP, che raramente è il caso o semplicemente non vale la pena.
Doc Brown,

1
Quando ho detto ocp, non intendo oops ocp, ma ocp in generale. Fondamentalmente, cerca di non modificare il codice esistente il più possibile e fai isolare il tuo codice. Questo è persino possibile con il vecchio codice procedurale. Capisco che i codici spaghetti non possono essere modificati in questo modo, ma per un codice ben progettato in qualsiasi paradigma, aprire vicino dovrebbe essere facilmente applicabile. Possa essere oops o procedurale o funzionale.
Anand Vaidya,

2
@AnandVaidya: quando non intendi OCP, non dovresti chiamarlo in questo modo. Immagino che ciò che realmente intendi sia ciò che chiama Feather scrivendo un nuovo codice in un metodo sprout , quindi uno può limitare il nuovo stile di codice a un nuovo metodo separato. Quando questo è possibile e ragionevole, hai ragione, va bene. Purtroppo questo approccio non è sempre applicabile, almeno non se si desidera mantenere il vecchio codice DRY.
Doc Brown,

@DocBrown: non credo che il principio di apertura / chiusura sia limitato alla programmazione orientata agli oggetti. Puoi avere moduli in cui aggiungi nuove procedure ma non modifichi quelle esistenti, ovvero mantieni intatta la semantica del codice esistente e, se hai bisogno di nuove funzionalità, scrivi nuovo codice.
Giorgio,

5

In larga misura, il codice e il suo aspetto sono irrilevanti . Che il codice fa è ciò che conta.

Se puoi garantire che cambiare / riscrivere il codice non cambierà ciò che fa il codice, allora puoi andare avanti e riformattare il codice in base al contenuto del tuo cuore. Tale "garanzia" è un insieme esaustivo di unit test che è possibile eseguire prima e dopo le modifiche, senza alcuna differenza percepibile.

Se non riesci a garantire quella stabilità (non hai questi test), lascialo da solo.

Nessuno ti ringrazierà per "aver rotto" un software business-critical, anche se stavi cercando di renderlo "migliore". "Lavorare" batte "meglio" ogni volta.

Ovviamente, non c'è nulla che ti impedisca di creare una serie di tali test in preparazione per un tale esercizio ...


4

Quasi tutto può essere analizzato in termini di costi e benefici, e penso che questo si applichi qui.

Gli ovvi vantaggi della prima opzione sono che minimizza il lavoro a breve termine e minimizza le possibilità di rompere qualcosa riscrivendo il codice di lavoro. Il costo evidente è che introduce incoerenza nella base di codice. Quando si esegue un'operazione X, viene eseguita in un modo in alcune parti del codice e in un modo diverso in una parte diversa del codice.

Per il secondo approccio, hai già notato l'ovvio vantaggio: coerenza. Il costo ovvio è che devi piegare la mente per lavorare in modi che altrimenti potresti aver abbandonato anni fa, e il codice rimane costantemente illeggibile.

Per il terzo approccio, il costo evidente è semplicemente dover fare molto più lavoro. Un costo meno ovvio è che potresti rompere le cose che funzionavano. Ciò è particolarmente probabile se (come spesso accade) il vecchio codice presenta test inadeguati per garantire che continui a funzionare correttamente. L'ovvio vantaggio è che (supponendo che tu lo faccia con successo) hai un nuovo codice piacevole e brillante. Oltre all'utilizzo di nuovi costrutti di linguaggio, hai la possibilità di refactoring della base di codice, che fornirà quasi sempre miglioramenti in sé, anche se hai ancora usato il linguaggio esattamente come esisteva quando è stato scritto - aggiungi nuovi costrutti che rendono il lavoro più semplice e potrebbe anche essere una vittoria importante.

Un altro punto importante: in questo momento, sembra che questo modulo abbia avuto una manutenzione minima per molto tempo (anche se il progetto nel suo complesso viene mantenuto). Ciò tende ad indicare che è abbastanza ben scritto e relativamente privo di bug - altrimenti, nel frattempo, avrebbe probabilmente subito più manutenzione.

Ciò porta a un'altra domanda: qual è la fonte del cambiamento che stai facendo ora? Se stai risolvendo un piccolo bug in un modulo che nel complesso soddisfa ancora bene i suoi requisiti, ciò tenderebbe ad indicare che il tempo e gli sforzi per il refactoring dell'intero modulo saranno probabilmente sprecati in gran parte - quando qualcuno dovrà fare confusione anche in questo caso, potrebbero trovarsi all'incirca nella stessa posizione in cui ti trovi ora, mantenendo un codice che non soddisfa le aspettative "moderne".

È anche possibile, tuttavia, che i requisiti siano cambiati e si sta lavorando sul codice per soddisfare tali nuovi requisiti. In questo caso, è probabile che i tuoi primi tentativi non soddisfino effettivamente i requisiti attuali. Esiste anche una probabilità sostanzialmente maggiore che i requisiti subiscano alcuni cicli di revisione prima che si stabilizzino nuovamente. Ciò significa che è molto più probabile che svolgiate un lavoro significativo in questo modulo nel (relativamente) breve termine, e molto più probabilità di apportare modifiche in tutto il resto del modulo, non solo nell'area che conoscete bene adesso. In questo caso, è molto più probabile che il refactoring dell'intero modulo abbia benefici tangibili a breve termine che giustificano il lavoro extra.

Se i requisiti sono cambiati, potrebbe anche essere necessario esaminare quale tipo di cambiamento è coinvolto e cosa sta guidando quel cambiamento. Ad esempio, supponiamo che stiate modificando Git per sostituire il suo uso di SHA-1 con SHA-256. Si tratta di una modifica dei requisiti, ma l'ambito è chiaramente definito e abbastanza limitato. Dopo averlo archiviato e utilizzato correttamente SHA-256, è improbabile che si verifichino altre modifiche che influiscono sul resto della base di codice.

Nella direzione opposta, anche quando inizia in modo discreto e discreto, una modifica a un'interfaccia utente ha la tendenza a sovrapporsi, quindi ciò che è iniziato come "aggiungi una nuova casella di controllo a questa schermata" finisce più come: "cambia questa IU fissa per supportare modelli definiti dall'utente, campi personalizzati, combinazioni di colori personalizzate, ecc. "

Per il primo esempio, probabilmente ha più senso ridurre al minimo le modifiche e gli errori dal punto di vista della coerenza. Per quest'ultimo, il refactoring completo ha molte più probabilità di ripagare.


1

Vorrei scegliere la tua prima opzione. Quando scrivo codice seguo la regola per lasciarlo in uno stato migliore di quello che era.

Quindi il nuovo codice segue le migliori pratiche, ma non toccherò il codice non correlato ai problemi su cui sto lavorando. Se lo fai bene, il tuo nuovo codice dovrebbe essere più leggibile e gestibile rispetto al vecchio codice. Ho scoperto che la manutenibilità totale migliora, perché se continui così, il codice che devi toccare per il tuo lavoro è sempre più spesso il codice "migliore". Ciò porta a una risoluzione più rapida dei problemi.


1

La risposta, come tante altre cose, è che dipende . Se ci sono grandi vantaggi nell'aggiornamento dei costrutti più vecchi, come la manutenibilità a lungo termine notevolmente migliorata (evitando l'inferno del callback, per esempio) che andare per esso. Se non ci sono grandi vantaggi, la coerenza è probabilmente tua amica

Inoltre, dovresti anche evitare di incorporare due stili nella stessa funzione più di quanto vorresti evitare all'interno di due funzioni separate.

Riassumendo: la tua decisione finale dovrebbe essere basata su un'analisi "costo" / "beneficio" del tuo caso particolare.

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.