27 Sep
matte

matte il 27 September 2006 parla di Rubylation

MetaProgramming Refactoring

Questo articolo è stato precedentemente pubblicato da Ola Bini su MetaProgramming Refactoring. E’ stato rielaborato da Rubylation Network ed è disponibile in più lingue.

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 def startup
    2   @log.info { "-startup()" }
    3   self.startup_foo
    4   self.startup_bar
    5 end
    6 
    7 def init
    8   @log.info { "-init()" }
    9   self.init_vars
   10   self.init_constants
   11   self.init_other
   12 end
   13 
   14 def main
   15   @log.info { "-main()" }
   16   self.run_main
   17   puts "hello from main"
   18 end
   19 
   20 def close
   21   @log.info { "-close()" }
   22   self.close_bar
   23   self.close_foo
   24 end
   25 
   26 def shutdown
   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 { "-#{name}()" }
    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.

Scrivi un commento