Ho cambiato una firma del metodo e ora ho oltre 25.000 errori. E adesso?


166

Di recente ho iniziato un nuovo lavoro in cui sto lavorando su un'applicazione molto grande (15M loc). Nel mio lavoro precedente avevamo un'applicazione altrettanto grande ma (nel bene o nel male) usavamo OSGi, il che significava che l'applicazione era suddivisa in molti microservizi che potevano essere modificati, compilati e distribuiti in modo indipendente. La nuova applicazione è solo una grande base di codice, con forse un paio di DLL.

Quindi devo cambiare l'interfaccia di questa classe, perché è quello che il mio capo mi ha chiesto di fare. Inizialmente l'hanno scritto con alcuni presupposti che non si sono generalizzati troppo bene, e per un po 'hanno evitato il problema del refactoring perché è così strettamente accoppiato. Ho cambiato l'interfaccia e ora ci sono oltre 25000 errori. Alcuni degli errori sono in classi con nomi che suonano importanti come "XYZPriceCalculator" che in realtànon dovrebbe rompersi. Ma non riesco ad avviare l'applicazione per verificare se funziona fino a quando tutti gli errori non vengono risolti. E molti dei test unitari fanno riferimento direttamente a quell'interfaccia, oppure sono associati a classi di base che fanno riferimento a quell'interfaccia, quindi il solo aggiustamento di questi è un compito piuttosto grande in sé. Inoltre, non so davvero come tutti questi pezzi si incastrino, quindi anche se potessi farlo iniziare, non so davvero come sarebbe se le cose fossero rotte.

Non ho mai affrontato un problema come questo nel mio ultimo lavoro. Cosa faccio?


7
Avrai bisogno di darci maggiori informazioni sui tipi di errori e su cosa sia "questa classe", non siamo dei lettori di mente.
whatsisname

137
"oltre 25000 errori" tali numeri mostrati in VS sono spesso sbagliati. Ci sono alcuni errori, ma con la generazione di una dll spesso usata non funzionante, anche altre build falliscono, dando luogo a numeri di errori astronomici. Comincio sempre a correggere con il primo messaggio di errore trovato nell'output di generazione, non nell'elenco degli errori.
Bernhard Hiller il

13
Mi piacerebbe vedere le firme prima e dopo sul metodo che hai modificato. A proposito: errori 25k non sembrano davvero troppi da affrontare. Tedioso sì, scoraggiante, sì, ingestibile, no.
jmoreno,

46
Un'interfaccia pubblica è un contratto. Non rompere tali contratti - crearne di nuovi.
Matteo Leggi il

6
25000 errori !? Houston, abbiamo un problema. Annulla definitivamente la modifica e parla con il tuo manager, spiegagli che potresti dover creare una nuova interfaccia del tutto.
Codice Whisperer,

Risposte:


349

25000 errori in pratica significano "non toccarlo". Cambia di nuovo. Crea una nuova classe con l'interfaccia desiderata e sposta lentamente i consumatori della classe in quella nuova. A seconda della lingua, puoi contrassegnare la vecchia classe come obsoleta, il che può causare tutti i tipi di avvisi del compilatore, ma in realtà non romperà la tua build.

Sfortunatamente queste cose accadono nelle vecchie basi di codice. Non c'è molto che puoi fare al riguardo se non lentamente migliorare le cose. Quando crei le nuove classi, assicurati di testarle correttamente e di crearle utilizzando i principi SOLID in modo che possano essere modificati più facilmente in futuro.


79
Non deve necessariamente essere una nuova classe . A seconda della lingua e delle circostanze potrebbe essere una funzione, un'interfaccia, un tratto, ecc.
Jan Hudec,

33
Aiuta anche se la vecchia interfaccia può essere sostituita da un wrapper di compatibilità attorno a quello nuovo.
Jan Hudec,

79
A volte, 25000 errori in realtà significano che non abbiamo mai osato toccarlo, ma ora che è arrivato un nuovo arrivato, diamo a lui quel compito di ripulire le stalle di Augusto.
mouviciel,

13
@theDmi: Sono d'accordo, 1 cambio e 25k errori molto probabilmente significano 2,5k cambi al massimo, e non sarei sorpreso di scoprire che era circa 250. Un sacco di lavoro in entrambi i modi, ma fattibile.
jmoreno,

33
Questi 25000 errori potrebbero essere tutti risolvibili con una singola modifica. Se rompo la classe base di un'enorme gerarchia, ognuna delle classi derivate emetterà errori sulla base non valida, ogni uso di quelle classi produrrà errori sulla classe inesistente, ecc. Immagina che fosse "getName", e ho aggiunto un argomento. La sostituzione nella classe di implementazione "HasAName" ora non funziona (errore) e tutto ciò che eredita da essa ora genera errori (tutte le 1000 classi), nonché ogni volta che creo un'istanza di una di esse (24x per classe su media). La correzione è ... una riga.
Yakk,

80

Dividi e conquista con i refactoring

Spesso, interrompere il cambiamento che è necessario eseguire in passaggi più piccoli può essere utile perché è quindi possibile eseguire la maggior parte dei passaggi più piccoli in modo da non interrompere affatto il software. Gli strumenti di refactoring aiutano molto in tali compiti.

Dividere

Innanzitutto, identifica le modifiche più piccole possibili (in termini di modifiche logiche, non in termini di LoC modificato) che si sommano alla modifica che desideri apportare. In particolare, cerca di isolare i passaggi che sono puri refactoring e che possono essere eseguiti con strumenti.

Conquistare

Per casi complicati come il tuo, potrebbe essere logico eseguire un piccolo refactoring alla volta e quindi lasciare che il problema si riposi, in modo che tutti i sistemi di integrazione continua possano verificare la modifica e forse anche il team di test abbia dato un'occhiata. Ciò convalida i passaggi effettuati.

Per eseguire un particolare refactoring, è assolutamente necessario il supporto degli strumenti per un caso in cui si dispone di 25.000 siti di chiamata del metodo che si desidera modificare. Forse funziona anche la ricerca e sostituzione, ma per un caso così critico, ne sarei spaventato.

Esempio

In C #, ad esempio, è possibile utilizzare Resharper per modificare la firma di un metodo. Se la modifica è abbastanza semplice, ad esempio aggiungendo un nuovo parametro, è possibile specificare quale valore deve essere utilizzato nei siti di chiamata che altrimenti avrebbero un errore di compilazione.

Sei quindi in una base di codice senza errori immediatamente e puoi eseguire tutti i test unitari, che passeranno, perché era solo un refactoring.

Una volta che la firma del metodo ha un bell'aspetto, puoi andare e sostituire i valori che Resharper ha aggiunto come argomenti al parametro appena introdotto. Questo non è più un refactoring , ma hai una base di codice priva di errori e puoi eseguire i test dopo ogni riga modificata.

A volte non funziona

Questo è un approccio molto utile per casi come il tuo, dove hai molti siti di chiamata. Ora disponi di un software funzionante, quindi potrebbe essere possibile eseguire piccoli passaggi di refactoring per modificare la firma solo un po ', quindi eseguirne un altro.

Sfortunatamente non funzionerà se la modifica della firma è troppo complessa e non può essere suddivisa in modifiche più piccole. Ma questo è raro; dividere il problema in piccoli problemi di solito mostra che è possibile.


12
Questo. Rifattorizza se puoi (in sicurezza), altrimenti hai bisogno di una migrazione adeguata.
sleske,

3
E per amore di qualunque cosa, si prega di utilizzare gli strumenti di refactoring integrati del proprio IDE, anziché semplicemente modificare la firma del metodo (ad esempio) localmente e poi andare in giro a correggere i conseguenti errori.
KlaymenDK,

36

Chiarisci il tuo compito con il tuo capo per aiutarlo a capire il problema e le tue esigenze come sviluppatore di software professionale.

Se fai parte di una squadra, cerca lo sviluppatore principale e chiedigli consiglio.

In bocca al lupo.


15
Questo è il primo passo che farei: "Sono un giovane e il mio capo mi ha detto di fare una cosa e ora sto ricevendo un messaggio di errore davvero grande, ma il mio capo non me ne ha parlato." Step 1: "Ehi capo, ho fatto la cosa, ma adesso ho 25k errori. Dovrebbe succedere, o ..."
Pimgd

28

Non toccarlo. Non commettere nulla.

Invece, siediti sulla sedia e grida "Heeeeelp !!!!!" più forte che puoi.

Beh, non esattamente così, ma chiedi consiglio ai tuoi colleghi senior. Se si verificano 25.000 errori, non si riparano gli errori, si corregge ciò che ha causato gli errori. E un collega anziano dovrebbe essere in grado di consigliarti come effettuare il cambiamento che il tuo capo desidera senza i 25.000 errori coinvolti. Esistono diversi modi per farlo, ma ciò che è un buon modo dipende dalla tua situazione specifica.

E potrebbe essere il capo a dire ai tuoi colleghi senior di fare lo stesso cambiamento, e loro hanno detto "no". Perché sapevano cosa sarebbe successo. Ecco perché ti è stato dato il lavoro.


2
Questa è sicuramente una cosa importante da tenere a mente. Se un nuovo dipendente è veramente ambizioso in larga misura, potrebbe essere sufficientemente laborioso (credulone) da svolgere il compito veramente grande che finisce per ottenere un vantaggio di memoria di 5 byte o essere un po 'più logico da ricodificare e mantenere in seguito.
The Great Duck,

@TheGreatDuck: Dimmi che non hai mai visto un'interfaccia usata ovunque e sbagliata ovunque. Ho sicuramente.
Giosuè l'

@Joshua non è nemmeno rilevante per quello che ho appena detto. Ci sono momenti in cui correggere un bug minore non è sempre la più intelligente idea che ciò significhi riscrivere oltre 10.000 righe di codice. Sfortunatamente, un nuovo dipendente potrebbe essere abbastanza credulone da trascinare 10 notte fuori dal lavoro riscrivendo quel codice per impressionare il loro capo e farlo. Certo, è una grande cosa da fare, ma a volte basta abbastanza. Devi solo accettare l'esistenza del bug.
The Great Duck

1
@Joshua a proposito, mi sono unito a questa community solo perché questa domanda è arrivata sul mio feed di domande popolari. Non ho esperienza con un design su larga scala come questo. Sono appena d'accordo con l'opinione di questa persona.
The Great Duck

22

Le API registrate non possono essere semplicemente modificate. Se hai davvero bisogno di cambiarli, anootali e / o documentali come deprecati (in qualunque modo la lingua lo consenta) e documenta quale API dovrebbe invece essere usata. La vecchia API può quindi essere gradualmente eliminata ... forse molto lentamente a seconda del budget di tempo per il refactoring.


10

Valutare

Valuta se questa modifica è necessaria o se puoi aggiungere un nuovo metodo e deprecare l'altro.

Inoltrare

Se è necessario cambiare; quindi è necessario un piano di migrazione.

Il primo passo è introdurre il nuovo metodo e fare in modo che il vecchio metodo massaggia i suoi argomenti in modo che possa chiamarne uno nuovo. Ciò potrebbe richiedere alcune codifiche hardware; va bene.

Questo è un punto di commit: controlla che tutti i test superino, eseguano il commit, spingano.

Migrare

Il lavoro impegnato è la migrazione di tutti i chiamanti del vecchio metodo a quello nuovo. Per fortuna può essere fatto gradualmente grazie allo spedizioniere.

Quindi andate avanti; non esitate a utilizzare gli strumenti per assistere ( sedessendo il più semplice, ce ne sono altri).

Contrassegna il vecchio metodo come obsoleto (con un suggerimento per passare al nuovo metodo); ti aiuterà a capire se hai dimenticato qualcosa e ti aiuterà se un collega introduce una chiamata al vecchio metodo mentre ci stai lavorando.

Questo è un punto di commit (o forse più punti di commit): controlla che tutti i test superino, eseguano il commit, spingano.

Rimuovere

Dopo che è trascorso un po 'di tempo (forse solo un giorno), è sufficiente rimuovere il vecchio metodo.


4
sedè probabilmente una cattiva idea ... Qualcosa che in realtà "capisce" la lingua e non farà cambiamenti non intenzionali è meglio.
wizzwizz4,

1
@ wizzwizz4: Sfortunatamente, ho trovato pochissimi strumenti che comprendono effettivamente il linguaggio abbastanza bene per C ++; la maggior parte degli strumenti sembra provvedere alla ridenominazione e basta. Certo, rinominare solo le chiamate di metodo esatte (e non eventuali sovraccarichi o chiamate di metodi non correlate ma simili) è già impressionante, ma non è sufficiente per qualcosa di più complicato. Per lo meno, avresti bisogno delle capacità di (1) mescolare argomenti, (2) applicare una trasformazione a un dato argomento (chiamare .c_str()ad esempio) e introdurre nuovi argomenti. sedfunziona un po ', il compilatore rileva i suoi problemi in seguito.
Matthieu M.

1
sed(o ed) può essere adeguato per questo genere di cose, a patto che rivediate correttamente il diff prima di impegnarvi.
Toby Speight,

Questo è il TCRR di html, sì? :)
Daniel Springer,

8

Se la modifica alla firma del metodo è semplicemente una modifica del nome, la soluzione semplice consiste nell'utilizzare strumenti per automatizzare la modifica nelle 25.000 classi che fanno riferimento al metodo in questione.

Presumo che tu abbia semplicemente modificato il codice manualmente, il che ha dato origine a tutti gli errori. Presumo anche che tu abbia familiarità con Java (vedendo il tuo riferimento a OSGi), quindi ad esempio in Eclipse (non so quale ambiente di programmazione usi, ma altri ambienti hanno strumenti di refactoring simili) puoi usare "Refactoring -> Rename" per aggiornare tutti i riferimenti al metodo, che dovrebbe lasciarti senza errori.

Nel caso in cui si stiano apportando altre modifiche alla firma del metodo oltre alla semplice ridenominazione (modifica del numero o dei tipi di parametri), è possibile utilizzare "Rifattorizzazione -> Modifica firma metodo". Tuttavia, è probabile che dovrai stare più attento, come suggeriscono le altre risposte. Inoltre, a prescindere dal tipo di modifica, potrebbe comunque essere piuttosto impegnativo eseguire tutte queste modifiche in una base di codice occupata.


2
OP ha affermato in particolare che OSGi era nel suo precedente lavoro, quindi non è molto rilevante qui.
un CVn

3
@ MichaelKjörling Se l'OP era un programmatore Java nel loro lavoro precedente, c'è una possibilità ancora migliore di essere anche un programmatore Java in questo lavoro.
Ricco

@ MichaelKjörling Volevo dare una raccomandazione concreta, usando Eclipse per il refactoring perché OP ha familiarità con Java. Indipendentemente dal fatto che OP stia effettivamente utilizzando Java per l'attuale progetto, è meno importante, ma dovrei chiarire la mia risposta. Grazie.
MikkelRJ

6

Qui il mio contributo.

Di recente ho iniziato un nuovo lavoro in cui sto lavorando su un'applicazione molto grande (15 milioni di righe di codice).

Probabilmente non hai familiarità con il progetto e le sue "caratteristiche". Prima di digitare una singola riga di codice, è importante conoscere il progetto. Quindi esegui il rollback delle modifiche e inizia analizzando il codice . (Almeno quello interessato)

Comprendere la soluzione esistente ti offre una prospettiva migliore di dove stai entrando. Contestualizza la soluzione e la sua importanza.

Come sottolineato da @Greg, dovresti essere in grado di testare il codice esistente per avere un riferimento valido da confrontare (test di regressione). La tua soluzione dovrebbe essere in grado di generare gli stessi risultati di quella esistente. A questo punto, non ti importa se i risultati sono giusti . Il primo obiettivo è refactoring, non correggere bug. Se la soluzione esistente dice "2 + 2 = 42", anche la tua soluzione dovrebbe. Se non genera eccezioni, neanche le tue. Se restituisce valori null, anche i tuoi dovrebbero restituire valori null. E così via. Altrimenti, comprometterai 25k righe di codice.

Questo per motivi di retrocompatibilità.

Perché? Perché in questo momento, è la tua garanzia unica di un refattore di successo.

E molti dei test unitari fanno riferimento direttamente a tale interfaccia o sono associati a classi di base che fanno riferimento a tale interfaccia.

È urgentemente necessario un modo per garantire la retrocompatibilità. Ecco quindi la tua prima sfida. Isolare il componente per il test dell'unità.

Tieni presente che quelle 25k righe di codice sono state create assumendo i possibili risultati del codice esistente. Se non rompi questa parte del contratto, sei a metà strada per la soluzione finale. Se lo fai, beh: che la forza sia con te

Dopo aver progettato e implementato il nuovo "contratto", sostituire quello vecchio. Deprecarlo o eliminarlo.

Ho suggerito di lasciare da soli i bug a causa del refactoring e correggere i bug sono compiti diversi. Se provi a portarli avanti insieme potresti non riuscire ad entrambi. Potresti pensare di aver trovato dei bug, tuttavia, potrebbero essere "caratteristiche". Quindi lasciali soli (per un minuto).

25k righe di codice mi sembrano sufficienti per farmi concentrare su un solo compito.

Una volta terminata la prima attività. Esporre questi bug / funzionalità al tuo capo.

Infine, come ha detto @Stephen:

Non c'è molto che puoi fare al riguardo se non lentamente migliorare le cose. Quando crei le nuove classi, assicurati di testarle correttamente e di crearle utilizzando i principi SOLID in modo che possano essere modificati più facilmente in futuro


5

Provalo.

Tutti gli altri raccomandano come eseguire il refactoring in modo che l'impatto sia limitato. Ma con così tanti errori, anche se riesci a eseguire il refactoring con appena 10 righe di codice (probabilmente puoi), hai influenzato 25.000 flussi di codice , anche se non hai bisogno di riscriverli.

Quindi, la prossima cosa da fare è assicurarsi che la tua suite di test di regressione passi a pieni voti. E se non ne hai uno, creane uno che lo farà. L'aggiunta di una suite completa di test di regressione al progetto monolitico sembra noiosa, ma è un buon modo per aumentare la fiducia nei candidati al rilascio, nonché rilasciarli più velocemente se la suite è automatizzata bene.

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.