MetaProgramming Refactoring
La metaprogrammazione riflessiva ha fatto parte della coscienza del programmatore per molto tempo. E’ stata possibile in molti linguaggi in un modo o in un altro. Qualcuno l’ha abbracciata più di altri e tra i maggiori abbiamo Lisp, SmallTalk, Python e Ruby. Ma non è da quando il Ruby è entrato nella mente dei programmatori comuni che la Metaprogrammazione è diventata famosa. Anche la discussione sui linguaggi DSL è importante per questioni di metaprogrammazione, dato che implementare un DSL (nello stesso linguaggio, naturalmente) è molto difficile senza la metaprogrammazione riflessiva.
Ho recentemente riletto la mia copia di Refactoring e come sempre sono rimasto colpito da come era in tema e come fossero facili e utili i suggerimenti all’interno. Ma ho anche iniziato a pensare che mancasse qualcosa. La rifattorizzazione riguarda specificatamente la programmazione orientata agli oggetti (OOP), ma mi sto muovendo sempre più verso linguaggi orientati alla programmazione, con DSL, metaprogrammazione riflessiva, introspezione e estensioni di Meta classe. Questi approcci rendono il sistema OOP molto più potente. Questo è molto importante anche nelle fasi iniziali nei sistemi più piccoli. Ho visto che inizio con lo scrivere metodi in un modo che mi porta a fare molto codice a mano e nelle fasi successive raggruppo il codice più possibile sia per leggibilità che per fatica.
Allora su cosa è questo blog? Credo che sia il momento giusto per un catalogo di Metaprogramming Refactoring. Non sto dicendo che dovrei farli, non tutti almeno, ma sarebbe molto interessante da avere. Forse sottoforma di wiki, dove qualche luminario sulla metaprogrammazione potrebbe scrivere le proprie esperienze. DHH? _why? Weirich? Dave Thomas?
In ogni caso, anche solo per far capire il tipo di rifattorizzazioni di cui sto parlando, vi proporrò un esempio. Questo è più o meno quello che dovrebbe essere una implementazione fittizia di qualcosa. Qualche chiamate al log e qualche ripetizione.
1 2 @log.info {"-startup()" } 3 self.startup_foo 4 self.startup_bar 5 end 6 7 8 @log.info {"-init()" } 9 self.init_vars 10 self.init_constants 11 self.init_other 12 end 13 14 15 @log.info {"-main()" } 16 self.run_main 17 puts "hello from main" 18 end 19 20 21 @log.info {"-close()" } 22 self.close_bar 23 self.close_foo 24 end 25 26 27 @log.info {"-shutdown()" } 28 self.all_shutdown 29 self.run_shutdown 30 end
Presento adesso il modello di codice estratto per la rifattorizzazione. Il primo passo è quello di prendere tutti i nomi dei metodi che dovrebbero essere gestiti e metterli in un lista come questa:
1 [:startup, :init, :main, :close, :shutdown]
Poi scorriamo queste definizioni e forniamo un corpo vuoto per ognuna di esse, così:
1 [:startup, :init, :main, :close, :shutdown].each do |name| 2 define_method(name) do 3 end 4 end
Poi dobbiamo cambiare la lista in un hash, facendo in modo che ogni nome di metodo punti ai metodi da chiamare nel metodo, in questo modo:
1 {:startup => [:startup_foo, :startup_bar], 2 :init => [:init_vars, :init_constants, :init_other], 3 :main => [:run_main], 4 :close => [:close_bar, :close_foo], 5 :shutdown => [:all_shutdown, :run_shutdown] }
Quando questo è stato fatto, dobbiamo aggiungere la parte del metodo principale che non può essere ricavata nello stesso modo. Questo lo facciamo con una proc:
1 {:startup => [:startup_foo, :startup_bar], 2 :init => [:init_vars, :init_constants, :init_other], 3 :main => [:run_main, lambda {puts "hello from main" }], 4 :close => [:close_bar, :close_foo], 5 :shutdown => [:all_shutdown, :run_shutdown] }
Il passo successivo è di scorrere i nomi e i valori dei metodi e definire il contenuto dei metodi. Possiamo poi rimuovere i metodi originali. Alla fine il nostro codice sarà:
1 {:startup => [:startup_foo, :startup_bar], 2 :init => [:init_vars, :init_constants, :init_other], 3 :main => [:run_main, lambda {puts "hello from main" }], 4 :close => [:close_bar, :close_foo], 5 :shutdown => [:all_shutdown, :run_shutdown] }.each do |name, methods| 6 define_method(name) do 7 @log.info {"-()" } 8 methods.each do |m| 9 if m.is_a? Proc 10 m.call 11 else 12 self.send m 13 end 14 end 15 end 16 end
In questo caso non so se avrei mai fatto la rifattorizzazione. Questo serve come esempio per capire il tipo di rifattorizzazioni che mi piacerebbe avere nel catalogo. Rifattorizzazioni tipo Extract DSL, creare classi dinamicamente, estendere da classi anonime e molti altri. Questo credo che sia una cosa molto utile negli ambienti di programmazione attuali. Commenti e suggerimenti sono molto graditi.

