Nieinwazyjny monkey-patching
8 komentarzy | Kategorie: Ruby, Techblog, Tips & tricks | trackbackTagi: monkey-patching ruby tips&tricks
Ostatnimi czasy cicho w polskiej blogosferze programistów Rubiego. Mój blog też nie jest wyjątkiem. Zdaje się czas jest sporym problemem, bo o czym pisać to jest... No nieważne :). Dzisiaj krótko, ale że kiedyś obiecałem sobie pisać nie tylko długo i treściwie...
monkey-patching wcieleniem zła?
Monkey-patching to technika, w której pozwalamy sobie na wprowadzanie zmiany w cudzym kodzie, na przykład podmianę istniejącej metody, dodanie nowej. W społeczności Rubiego ta technika jest stosunkowo popularna, z kolei Pythonistas jej nie lubią. Tym razem nie będę zagłębiać się w szczegóły i rozstrzygał czy ta technika jest dobra, zła, niebezpieczna (być może kiedyś). Chciałbym pokazać prosty sposób na nieinwazyjne ulepszanie istniejących klas/metod.
W moim kodzie chciałem użyć metody Array#index. Metoda zwraca index w tablicy podanego obiektu lub nil jeśli elementu nie znaleziono.
a = [3, 7, 2, 10]
puts a.index(7) # => 1
Jednak jak się okazało metoda ta nie obsługuje bloków, zatem poniższy kod nie zadziała.
a = [4, 8, 10, 7, 1]
puts a.index {|e| e % 2 == 1 } # szukamy indeksu pierwszej nieparzystej liczby
monkey-patching na ratunek
W prosty sposób możemy tą dziurę załatać. Należy jednak dobrze przemyśleć strategię. Najważniejsze jest to, żeby nie zepsuć aktualnej wersji.
class Array
alias_method :__old_index__, :index
def index(obj = nil)
if obj.nil?
self.each_with_index do |e, i|
return i if yield(e)
end
return nil
else
return __old_index__(obj)
end
end
end
a = [4, 8, 10, 7, 1]
puts a.index {|e| e % 2 == 1 } # szukamy indeksu pierwszej nieparzystej liczby
Myślę, że kod jest prosty w zrozumieniu, jednakże dla tych mniej wprawnych programistów Rubiego pozwolę sobie na mały opis. Poprzez alias_method tworzymy alias do metody index, tak by móc do niej się potem odwołać. Następnie nadpisujemy metodę index, zauważ, że parametr jest opcjonalny (obj = nil). Jest to przygotowanie do wersji z blokiem. W ciele metody sprawdzamy czy parametr został podany. Jeśli nie (obj.nil? == true) to wykona się kod obsługujący blok, w przeciwnym wypadku odpalamy starą wersję metody index poprzez jej alias.
W Rubym 1.8.7 (już niedługo oficjalne wydanie) i 1.9 poprawiono wiele metod w tym i Array#index by obsługiwały blok. Można zatem dodać kod sprawdzający obsługę bloku i nie dodawać tej metody w takim wypadku.
class Array
begin
[1].index {|e| e == 1}
rescue ArgumentError
puts "(monkey-)patching Array#index (RUBY_VERSION = #{RUBY_VERSION})"
alias_method :__old_index__, :index
def index(obj = nil)
if obj.nil?
self.each_with_index do |e, i|
return i if yield(e)
end
return nil
else
return __old_index__(obj)
end
end
end
end
a = [4, 8, 10, 7, 1]
puts a.index {|e| e % 2 == 1 } # szukamy indeksu pierwszej nieparzystej liczby
Masz rację! Ja również bardzo sobie cenię otwarte klasy w Rubim.
Na goldenline.pl na grupie języków skryptowych była (cały czas jest) dyskusja o tym
@Seban, mógłbyś zapodać linkiem?
Mówisz, masz: http://www.goldenline.pl/forum/jezyki-skryptowe/325194
(Komentarz zmodyfikowany 16.10.2008 o 17:27)
mozna tez z finda skorzystać:
Kodując w Railsach pewien system e-commerce, zacząłem mieć nagle bardzo dziwne błędy związane ze stringami. Okazało się, że pewna (nie pamiętam która – od razu wyleciała z hukiem) biblioteka od PDF-ów sobie monkey-pacznęła klasę String. Konsekwencje były opłakane, ale dało rade szybko naprawić.
Istnieją inne języki niż Ruby, które umożliwiają monkey-patching, ale społeczności niektórych z nich uważają taki zabieg za poniżający, za ostateczność.
Może i monkey-patching umożliwia sprytne i szybkie naprawienie/dodanie jakiegoś ficzera, ale moim zdaniem nie powinno się tak entuzjastycznie reklamować tej możliwości w Rubim. Programiści bywają bardzo nieodpowiedzialni, żonglując fajerwerkami Rubiego.
Jak najbardziej się z Tobą @RazorJack zgadzam. To tylko utwierdza mnie w przekonaniu, że efektywne i świadome korzystanie z możliwości Rubiego wymaga dosyć dogłębnego poznania go. Osobiście nie uznaję monkey-patchingu jako leku na wszelkie zło, a nawet denerwuje mnie jeśli widzę w cudzych bibliotekach jeśli klasy wbudowane są przy byle okazji modyfikowane (a można daną funkcjonalność umieścić w swoich klasach). Jednak umiarkowane stosowanie we własnych końcowych aplikacjach (tak jak pokazałem we wpisie) wydaje się ok :).
Możesz napisać o alias_method_chain w railsach :) Tego jest od groma w kodzie railsów i w sumie się przydaje często. Sam miałem napisać, ale cholera czasu mało ostatnio…
Symbol :__old_index__ sugeruje pythonowe inspiracje :)