Come faccio a gestire le variabili globali nel codice legacy esistente (o, cosa c'è di meglio, inferno globale o modello infernale)?

3

Quindi ... Abbiamo questo progetto abbastanza complesso (~ 10k LOC, ma c'è un codice duplicato quindi è difficile da dire) con centinaia di variabili globali. Il progetto ha più dipendenze da altri progetti e molti altri progetti dipendono anche da questo. Ho principalmente ereditato la responsabilità di refactoring solo una parte di questo progetto da solo, una "sezione chiusa" di moduli. Nessuno degli sviluppatori originali rimane.

Ho escogitato un modo per strutturare le routine in classi (suddividendo megamoth , formando alcune gerarchie di classi , una piccola strategia qua e là, niente di stravagante (spero), l'obiettivo è rendere più semplice per gli altri sviluppatori aggiungere funzionalità e rendere possibile l'aggiunta di test di unità adeguati).

Le mie nuove classi forniscono calcoli applicati agli array di dati attualmente globali che vengono aggiornati e i calcoli stessi devono mantenere uno stato (una somma, l'ultimo valore dell'ultimo vettore elaborato, ecc.). Potresti vederli come funtori.

Tuttavia non sono sicuro su come gestire i globals. Non penso che sarò in grado di trasformare tutti i globali in non globali a causa delle dipendenze da altri moduli, che non ho intenzione di ridefinire solo ora. Inoltre, molte delle mie nuove classi dovranno condividere i dati. Quindi, posso ...

  • Per le globali che non sono condivise tra le mie nuove classi, lasciale così come sono. Stavo pensando di utilizzare un registro , o almeno usare alcune # define o altre variabili per limitare l'ambito e fornire il contesto ovunque siano utilizzati.
  • Per i globals che saranno condivisi tra le mie nuove classi, puoi creare una classe base con i riferimenti, o usare un Singleton per passarli, o una combinazione di entrambi.

Non sono sicuro che l'utilizzo di questi modelli farà più male che bene.

Quindi la mia domanda è: cosa c'è di meglio, vivere con le variabili globali esistenti o regolarle in modo aggressivo con modelli come Singleton o Registry? Hai qualche suggerimento su questi schemi o hai uno schema migliore?

    
posta ArthurChamz 06.03.2014 - 23:51
fonte

5 risposte

13

Un modo per spostarsi lentamente e in modo relativamente sicuro da globali a non globali potrebbe essere raggruppare globalmente in classi semi-ragionevoli nel miglior modo possibile. Dichiarare un'istanza globale della nuova classe, con membri che corrispondono alle globali che si stanno sostituendo. Rimuovi i globali. Guadare un numero potenzialmente elevato di errori del compilatore, cambiando ogni riferimento al globale con un riferimento al membro corrispondente dell'istanza globale.

Ora puoi passare gradualmente dall'avere le tue classi a conoscenza dell'istanza globale della tua nuova classe direttamente, e invece lanciare il barattolo verso il basso facendo passare l'istanza globale. Con il passare del tempo puoi sperare di dare un calcio alla lattina fino alla fine della strada, rimuovendo tutte le conoscenze dirette dell'istanza globale da ogni classe e, infine, eliminando l'istanza globale, dopo averla lentamente sostituita da un registro o dipendenza (a seconda di cosa hai fatto ogni volta che hai rimosso la conoscenza diretta dell'istanza globale da una delle tue classi).

Nota che ogni volta che una delle tue classi perde conoscenza diretta di come trovare l'istanza globale diventa più modificabile e verificabile, anche se l'istanza globale esiste ancora là fuori nella tua base di codice. In questo modo si ottengono dei vantaggi senza dover fare tutto il lavoro in anticipo, il che potrebbe richiedere troppo tempo con una base di codice di grandi dimensioni se il / i globale / i sono ampiamente referenziati.

Ovviamente, devi finire di fare tutto nel primo paragrafo. Con una grande base di codice tu e il tuo compilatore potrete trascorrere molto tempo insieme.

    
risposta data 07.03.2014 - 04:30
fonte
7

Quanto vai lontano dipende in gran parte dalla natura dei Globali. Se sono letti principalmente e raramente modificati (solo da poche posizioni facili da trovare), esiterei a fare cambiamenti radicali e fornirò un quadro sulle modifiche, basandomi sulla documentazione, sulla disciplina dei programmatori e sulla revisione del codice per garantire che il framework venga utilizzato.

Se tuttavia le variabili globali vengono cambiate da più posizioni o sempre (o entrambe), è necessario diventare più aggressivi con il refactoring.

Singletons - IMHO è un nome di fantasia per un globale per aggirare il dogma dei "Globali sono la via di tutti i mali" e per le lingue come Java che non ha alcun concetto di globale. Non saranno di grande aiuto in questo caso, a meno che il modello di utilizzo tenda naturalmente verso un Singleton (dalla lettura tra le righe dubito che questo sia il caso).

Registro - Opzione migliore, ma meno che ideale, a meno che gli articoli debbano essere veramente globali e lontani dall'ideale se vengono scritti regolarmente.

Dai un'occhiata attenta a questi aspetti globali e in primo luogo cerca di capire se dovrebbero essere globali. Se non c'è una ragione chiara, qualsiasi tentativo di risolverlo si sta occupando del sintomo, non della causa: sarà solo un bel po 'globale. Se decidi che risolverlo (come in, renderlo non globale) non è un'opzione, ragioni valide sono "non vale la pena" e "potrebbe infrangere altri codici in modi imprevisti" ecc., Quindi mettere un cerotto su di esso potrebbe essere una buona idea

5K SLOC non è un progetto di grandi dimensioni: prova a gestire 5 milioni di SLOC in questo stato. Con solo 5K SLOC, un importante refactoring non è un grosso sforzo se si dispone di un valido supporto per gli strumenti.

    
risposta data 07.03.2014 - 04:05
fonte
5

what's better, live with existing global variables or aggressively tune them with patterns like Singleton or Registry? Do you have any suggestions to these schemes, or have a better scheme?

Uno schema migliore sarebbe un'iniezione di dipendenza senza globali (o statici) di qualsiasi tipo.

Codice vecchio:

int DEFAULT_QUUX = 5;

int main() {
    Blip b;
    b.do_stuff(); // makes DEFAULT_QUUX = 6
    Qlip q;
    q.do_things(); // reads DEFAULT_QUUX
}

Nuovo codice:

int main() {
    int quux = 5; // no longer global
    Blip b{quux};
    quux = b.do_stuff(); // returns 6
    Qlip q;
    q.do_things(quux); // quux is dependency injected.
}

Se l'ipotetico Blip e Qlip usano o comunicano con altri oggetti nella loro implementazione, e quegli oggetti hanno bisogno di "quux" o di altri globali, questi dovrebbero essere iniettati nella catena di chiamate dall'alto.

    
risposta data 07.03.2014 - 12:57
fonte
1

Un problema serio con le variabili globali può essere il fatto che la variabile globale viene riutilizzata per controllare "sottosistemi" diversi a seconda del contesto corrente del programma. Quindi, cosa puoi fare:

  1. Aggiungi una classe di registro globale per le variabili globali.
  2. Verifica se la resposabilità per la variabile globale è in realtà solo per un dominio testando il software (ad esempio in un debugger o in un unittest).
  3. Verifica se ha senso dividere la resposibilità della variabile globale.

E dovresti provare a creare test automatici quando esegui il refactoring a livello di unità o di test di integrazione.

Kim

    
risposta data 07.03.2014 - 17:08
fonte
1

Contrassegna tutte le globali con l'attributo [Obsoleto] (o qualsiasi altra cosa se non usi c #)

Imposta il cablaggio dell'infrastruttura per Iniezione delle dipendenze, in questo modo:

using (var mainForm = factory.Get<MainForm>()) {
      Application.Run(mainForm);
}

Quindi vai sulla tua attività implementando funzionalità dal backlog. Man mano che modifichi il codice esistente, l'IDE indicherà i cattivi attori (vars obsoleti). Quando incrociano il tuo percorso, prova ad ottenerlo dal contenitore di iniezione delle dipendenze. Non è necessario sostituire tutte le istanze in un grande cambiamento radicale che è garantito avere una ricaduta indesiderata.

Mentre procederai, avrai sempre più fiducia nei casi d'uso per uno o due di questi malvagi. Contemporaneamente, costruisci skilz con il tuo contenitore DI di tua scelta.

Poi, un giorno, quando il tuo contenitore DI viene cablato attraverso la maggior parte della tua base di codice, prendi una classe piuttosto scadente e cancelli tutti i [obsoleti] varselli. Lo fai estinguendo un dispositivo di prova con il più semplice dei test, l'istanziazione attraverso il tuo contenitore DI.

    var factory = new NinjectFactory();
    var mainForm = factory.Get<MainForm>();
    Assert.That(mainForm, Is.Not.Null, "MainForm"); 

Quindi trova una var globale e passa attraverso la funzione di costruzione. Il test fallirà se non hai configurato DI per la classe del var globale. Ovviamente, quella classe avrà delle dipendenze che dovrai srotolare, ma prova a svolgere solo quanto necessario per eseguire test (e si spera che App).

BTW - Sono al collo in una situazione simile in questo momento. (Un progetto molto più grande, però, il modulo principale da solo era 3k di codice!) È abbastanza soddisfacente una volta capito.

    
risposta data 24.04.2014 - 05:37
fonte

Leggi altre domande sui tag