Come si fa a rifattorizzare in modo sicuro in una lingua con ambito dinamico?

13

Per quelli di voi che hanno la fortuna di non lavorare in una lingua con ambito dinamico, permettetemi di darvi un piccolo ripasso su come funziona. Immagina una pseudo-lingua, chiamata "RUBELLA", che si comporta in questo modo:

function foo() {
    print(x); // not defined locally => uses whatever value 'x' has in the calling context
    y = "tetanus";
}
function bar() {
    x = "measles";
    foo();
    print(y); // not defined locally, but set by the call to 'foo()'
}
bar(); // prints "measles" followed by "tetanus"

Cioè, le variabili si propagano su e giù liberamente nello stack di chiamate - tutte le variabili definite in foo sono visibili a (e mutabili da) il suo chiamante bar , e anche il contrario è vero. Questo ha gravi implicazioni per il codice refactability. Immagina di avere il seguente codice:

function a() { // defined in file A
    x = "qux";
    b();
}
function b() { // defined in file B
    c();
}
function c() { // defined in file C
    print(x);
}

Ora, le chiamate a a() stamperanno qux . Ma poi, un giorno, decidi che devi cambiare b un po '. Non conosci tutti i contesti di chiamata (alcuni dei quali potrebbero essere al di fuori della tua base di codice), ma dovrebbe essere tutto a posto - le tue modifiche saranno completamente interne a b , giusto? Quindi lo riscrivi in questo modo:

function b() {
    x = "oops";
    c();
}

E potresti pensare di non aver cambiato nulla, dal momento che hai appena definito una variabile locale. Ma, in effetti, hai rotto a ! Ora, a stampa oops anziché qux .

Tornando fuori dal regno delle pseudo-lingue, questo è esattamente il modo in cui MUMPS si comporta, anche se con una sintassi diversa.

Le versioni moderne ("moderne") di MUMPS includono la cosiddetta istruzione NEW , che consente di impedire alle variabili di filtrare da un chiamato a un chiamante. Quindi nel primo esempio sopra, se avessimo fatto NEW y = "tetanus" in foo() , quindi print(y) in bar() non stamperebbe nulla (in MUMPS, tutti i nomi puntano alla stringa vuota a meno che non siano impostati esplicitamente su qualcos'altro). Ma non c'è nulla che possa impedire alle variabili di passare da un chiamante a un chiamato: se abbiamo function p() { NEW x = 3; q(); print(x); } , per quanto ne sappiamo, q() potrebbe mutare x , nonostante non ricevesse esplicitamente x come parametro. Questa è ancora una brutta situazione in cui ci si trova, ma non as come di solito.

Tenendo presente questi pericoli, in che modo possiamo eseguire il refactoring del codice in modo sicuro in MUMPS o in qualsiasi altra lingua con ambito dinamico?

Ci sono alcune ovvie buone pratiche per rendere più facile il refactoring, come non usare mai variabili in una funzione diversa da quelle che hai inizializzato ( NEW ) o che sei passato come parametro esplicito e documentando esplicitamente tutti i parametri che sono passato implicitamente dai chiamanti di una funzione. Ma in un codebase decennale, ~ 10 8 -LOC, questi sono lussi che spesso non si hanno.

E, naturalmente, essenzialmente tutte le buone pratiche per il refactoring in lingue con scope lessicale sono applicabili anche in linguaggi con scope dinamico - test di scrittura e così via. La domanda, quindi, è questa: come possiamo mitigare i rischi specificamente associati con l'aumento della fragilità del codice con scope dinamiche durante il refactoring?

(Si noti che, mentre come si naviga e il codice refactoring è scritto in un linguaggio dinamico? ha un titolo simile a questa domanda, è del tutto estraneo.)

    
posta senshin 12.09.2015 - 15:38
fonte

3 risposte

4

Wow.

Non conosco i MUMPS come lingua, quindi non so se il mio commento si applica qui. In generale, devi refactoring dall'interno. Quei consumatori (lettori) dello stato globale (variabili globali) devono essere refactored in metodi / funzioni / procedure che usano parametri. Il metodo c dovrebbe apparire così dopo il refactoring:

function c(c_scope_x) {
   print c(c_scope_x);
}

tutti gli usi di c devono essere riscritti in (che è un'attività meccanica)

c(x)

questo è per isolare il codice "interno" dallo stato globale usando lo stato locale. Quando hai finito, dovrai riscrivere b in:

function b() {
   x="oops"
   print c(x);
}

l'assegnazione x="oops" è lì per mantenere gli effetti collaterali. Ora dobbiamo considerare b come inquinare lo stato globale. Se hai un solo elemento inquinato, considera questo refactoring:

function b() {
   x="oops"
   print c(x);
   return x;
}

termina riscrivi ogni uso di b con x = b (). La funzione b deve utilizzare solo i metodi già ripuliti (si potrebbe desiderare che il ro rinominare lo rendi chiaro) quando si esegue questo refactoring. Dopodiché dovresti rifattorizzare b per non inquinare l'ambiente globale.

function b() {
   newvardefinition b_scoped_x="oops"
   print c_cleaned(b_scoped_x);
   return b_scoped_x;
}

rinomina b in b_cleaned. Immagino che dovrai giocare un po 'con questo per essere abituato a quel refactoring. Sicuramente non tutti i metodi possono essere rielaborati da questo, ma dovrai iniziare dalle parti interne. Prova a farlo con Eclipse e java (metodi di estrazione) e "global state" a.k.a. membri della classe per avere un'idea.

function x() {
  fifth_to_refactor();
  {
    forth_to_refactor()
    ....
    {
      second_to_refactor();
    }
    ...
    third_to_refactor();
  }
  first_to_refactor()
}

hth.

Domanda: Tenendo presente questi pericoli, come possiamo rifattorizzare il codice in modo sicuro in MUMPS o in qualsiasi altra lingua con ambito dinamico?

  • Forse qualcun altro può dare un suggerimento.

Domanda: in che modo attenuiamo i rischi associati specificamente all'aumentata fragilità del codice con scope dinamiche durante il refactoring?

  • Scrivi un programma, che fa i refactoring sicuri per te.
  • Scrivi un programma, che identifica i candidati sicuri / i primi candidati.
risposta data 12.09.2015 - 22:29
fonte
2

Credo che la soluzione migliore sia portare sotto controllo il codice completo e accertarsi di avere una panoramica dei moduli e delle loro dipendenze.

Quindi almeno hai la possibilità di fare ricerche globali e avere la possibilità di aggiungere test di regressione per le parti del sistema in cui prevedi un impatto con un cambio di codice.

Se non vedi la possibilità di realizzare il primo, il mio miglior consiglio è: non rifattorizzare alcun modulo che viene riutilizzato da altri moduli, o per il quale non sai che altri si affidano a loro . In qualsiasi base di codice di dimensioni ragionevoli le possibilità sono alte si possono trovare moduli su cui non dipende nessun altro modulo. Quindi, se si ha una mod A in base a B, ma non viceversa, e nessun altro modulo dipende da A, anche in un linguaggio con ambito dinamico, è possibile apportare modifiche ad A senza interrompere B o altri moduli.

Questo ti dà la possibilità di sostituire la dipendenza di A a B da una dipendenza da A a B2, dove B2 è una versione riscritta e sanificata di B. B2 dovrebbe essere una nuova scritta con le regole in mente che hai menzionato sopra per rendere il codice più evolutivo e più facile da refactoring.

    
risposta data 13.09.2015 - 08:56
fonte
0

Per dichiarare l'ovvio: Come fare il refactoring qui? Procedi con molta attenzione.

(Come lo hai descritto, lo sviluppo e il mantenimento della base di codice esistente dovrebbero essere abbastanza difficili, per non parlare del tentativo di refactoring.)

Credo che applicherei retroattivamente un approccio basato sui test qui. Ciò implicherebbe la scrittura di una serie di test per garantire che le funzionalità attuali continuino a funzionare mentre si avvia il refactoring, innanzitutto per semplificare i test. (Sì, mi aspetto un problema di pollo e uova qui, a meno che il tuo codice sia già abbastanza modulare da testare senza modificarlo affatto.)

Quindi puoi procedere con altri refactoring, controllando che non hai interrotto alcun test mentre avanzi.

Infine, puoi iniziare a scrivere test che si aspettano nuove funzionalità e poi scrivere il codice per far funzionare quei test.

    
risposta data 16.09.2015 - 08:22
fonte

Leggi altre domande sui tag