Any fool can make things bigger, more complex, and more violent. It takes a touch of genius and a lot of courage to move in the opposite direction. Albert Einstein

o Ruby

Jaka to metoda?

17 komentarzy | Kategorie: Narzędzia, Ruby, Techblog, Tips & tricks | trackback
Tagi:

Nikt nie jest nieomylny

Praca z Rubym (także Railsami i innymi rubinowymi tworami) to nie tylko pisania kodu, uruchamianie czy testowanie. To także bardzo często zabawa bezpośrednio z kodem w konsoli. Języki dynamiczne, które posiadają taką konsolę (w Rubym oczywiście to irb) dają możliwość poczucia i zrozumienia co tak na prawdę dzieje się z naszym kodem kiedy jest uruchamiany. Dają możliwość szybkiego wskoczenia 'w temat', a także bezpośredniego eksperymentowania z kodem. Namiastką tego w takich językach jak Java czy C# jest debugowanie w IDE, gdzie krok po kroku możemy przyglądać się co się dzieje w kodzie.

Im więcej pracujemy tym bardziej denerwują nas wszelkie literówki ('m' zamiast 'n', 'a' zamiast 'A' i tak dalej). Jeśli mamy włączone rozszerzenie 'readline' wystarczy nacisnąć strzałkę w górę, przesunąć kursor w odpowiednie miejsce i poprawić błąd. Okazuje się, że można jeszcze prościej. Ba! Ruby może zrobić to za nas... może nie dosłownie, ale zaraz zobaczycie o co mi chodzi.

Guess Method na ratunek

Odpowiedzią jest Guess Method, gem który stara się automatycznie wykryć błędnie wpisane nazwy metod oraz stałych. Przykładowo jeśli zamiast 1.to_s napiszemy 1.tos, błąd zostanie wykryty, a zamiast NoMethodError zostanie wywołana poprawna (domniemana) metoda 'to_s' (sic!).

Zanim zaczniemy zabawę - instalacja. Klasycznie za pomocą gem (użytkownicy windows oczywiście pomijają 'sudo').

sudo gem install guessmethod

Zobacz kilka przykładów z sesji irb:

>> require 'guessmethod'
=> true
>> Stirng.tos
attention: replacing non-existant constant Stirng with String for Object
attention: sending to_s instead of tos to String:Class
=> "String"
>> ['1','2','3'].mp {|x| x.tof}
attention: sending map instead of mp to ["1", "2", "3"]:Array
attention: sending to_f instead of tof to "1":String
attention: sending to_f instead of tof to "2":String
attention: sending to_f instead of tof to "3":String
=> [1.0, 2.0, 3.0]
>> 1.tos
attention: sending to_s instead of tos to 1:Fixnum
=> "1"
>> eixt
attention: sending exit instead of eixt to main:Object

(dodatkowo Guess Method dosyć ładnie koloruje wyświetlane komunikaty)

Trzeba od razu sobie powiedzieć jasno: to nie nadaje się do 'produkcyjnego' użycia. Żeby Ci nie przyszło do głowy używać tego w normalnym kodzie źródłowym. Chyba, że lubisz wyzwania i szukasz nowych doznań ;).

Ale jak to działa?

Nie byłbym sobą gdybym nie dowiedział się 'jak oni to zrobili'. Moje podejrzenia się sprawdziły. Algorytm jest następujący:
1. Przechwyć wywołanie nieistniejącej metody
2. Znajdź metodę, która istnieje w aktualnym obiekcie i jest 'najbliższa' metodzie, która została wywołana
3. Wywołaj znalezioną metodę lub rzuć wyjątek jeśli takiej nie ma

Punkt nr 1 jest prosty. Jak wiadomo, w Rubym można przechwycić wywołanie nieistniejącej metody za pomocą 'method_missing':

class MyClass
  def method_missing(name, *args)
    puts "przechwycono wywołanie: #{name} z argumentami [#{args.join(',')}]"
  end
end

MyClass.new.test
MyClass.new.test(1, 2)

Punkt nr 2 wydaje się (i rzeczywiście jest) kluczowym. Po pierwsze, żeby znaleźć jakąś metodę, wpierw trzeba pobrać jakąś listę, która zostanie przeszukana. Czy już pisałem, że Ruby jest w takich sprawach po prostu świetny?

s = "hello"
puts s.methods

Teraz najtrudniejsza rzecz. Jak opisać 'najbliższą metodę'? Mózg człowieka potrafi w jednej chwili skojarzyć, że 'tos' to prawdopodobnie pomyłka i powinno być 'to_s', ale jak ma to zrobić program? Z pomocą przychodzi algorytm wyliczający odległość Levenshteina. Jest to algorytm, który dla zadanych dwóch wejściowych słów s1, s2 wyznacza pewną miarę ich odmienności (odległości). W praktyce algorytm wyznacza ilość kroków, takich jak usunięcie, dodanie i zamiana znaku, tak aby przekształcić słowo s1 na s2. Przykładowo aby przekształcić nasze 'tos' w 'to_s' potrzeba 1 kroku: dodanie '_' na 3 pozycji. Dodatkowo można ustalać pewne wagi dla operacji wstawiania, usuwania i zamiany. Po co? Spójrz na klawiaturę, zrobienie literówki 's' zamiast 'a' jest bardziej prawdopodobne niż 'z' zamiast 'p'!

W tym momencie sprawa powinna być prosta. Mając błędną metodę o nazwie s i zbiór X wszystkich metod obiektu (na którym metoda została wywołana) obliczamy wszystkie odległości dla pary [s, x], gdzie x jest słowem ze zbioru X. Następnie wybieramy najmniejszą odległość i.. gotowe :).

Dodam jeszcze, że algorytm Levenshteina może posłużyć do implementacji własnego spell-checkera. Ale to już wiecie, prawda?

Jeśli spodobał Ci się wpis to może umieścisz ten blog w swoim czytniku RSS?

Komentarze

1. avatar icon Radarek napisał(a) 20 Lis 2007 o godz. 16:17:

Implementację algorytmu Levenshteina w Rubym znajdziecie pod adresem: http://raa.ruby-lang.org/project/levenshtein/. Dostępne także jako gem (http://rubyforge.org/projects/text).

2. avatar icon Hoppke napisał(a) 20 Lis 2007 o godz. 17:05:

Tak co problemu „literówki w nazwie metody”, to do eliminowania takich rzeczy używa się IDE, w trakcie pisania kodu. Żeby potem nie tracić czasu.

I jeśli mówimy o namiastkach, to raczej irb nazwałbym namiastką prawdziwego debuggera. Jako interaktywna konsola jest super, ale porównajmy np. czynności potrzebne by ustawić „conditional breakpoint” w Javie pod Eclipsem z tą samą czynnością w irb. Albo nawigowanie po stosie wywołań metod…

Ech, jak ja bym chciał porządny zestaw narzędzi do pisania w rubym/pythonie…

3. avatar icon Radarek napisał(a) 20 Lis 2007 o godz. 17:49:

@Hoppke, Nie czytałeś dokładnie, albo nie zrozumiałeś. Chodzi o używanie interaktywnej konsoli irb. Nie nazwałem irb debuggerem.

„Ech, jak ja bym chciał porządny zestaw narzędzi do pisania w rubym/pythonie…”

Czego Ci brakuje?

4. avatar icon Stanisław 'dozzie' Klekot napisał(a) 20 Lis 2007 o godz. 17:51:

Namiastką tego w takich językach jak Java czy C# jest debugowanie w IDE, gdzie krok po kroku możemy przyglądać się co się dzieje w kodzie.

Porównujesz dwie nieporównywalne rzeczy. Wierszowy interpreter niektórych języków skryptowych służy do zupełnie innych rzeczy niż debugger. Pokaż mi, jak w takim interpreterze chcesz zatrzymać działanie programu po wejściu do jakiejś funkcji, żeby obejrzeć jej argumenty i aktualne wartości zmiennych globalnych, bo to jest nagminna operacja przy debuggowaniu.

5. avatar icon Radarek napisał(a) 20 Lis 2007 o godz. 17:53:

Dobra, przykład nie jest może zbyt dobry. Ale chodziło mi o to, że java czy c# nie mają interaktywnej konsoli, gdzie na bieżąco można wpisywać kod i wykonywać go. w Javie natomiast, możesz napisać wpierw kod, a potem go debugować krok po kroku, patrząc np co zwracają metody itp. Pomijam tu sam debugowania, czyli szukania błędów. Irb oczywiście do tego nie służy.

6. avatar icon Hoppke napisał(a) 20 Lis 2007 o godz. 17:56:

Sorry.
Mówienie o irb i debuggerze w jednym zdaniu mnie zmyliło.

A czego bym chciał? Jakiegoś fajnego IDE, w którym możnaby napisać duży projekt. Z sensownym debuggerem (takim jakie są do Javy). Z uprzyjemniaczami takimi jak np. refaktoryzacje (zmieniam nazwę jakiejś klasy, czy przenoszę metodę z jednej klasy do drugiej i chciałbym, żeby kod automatycznie poprawiło). Albo mam metodę która przyjmuje 5 parametrów, i chcę z nich zrobić jeden obiekt-pojemnik… i żeby mi to automat zrobił w całym kodzie, stworzył nową klasę-pojemnik, dodał jej instancje wszędzie gdzie poprzednio wywoływałem daną metodę... no, takich rzeczy mi brakuje.

Plus wszelkiej maści profilery.

W miarę jak projekt się rozrasta coraz trudniej nad nim zapanować bez odpowiednich narzędzi. I dlatego nadal tak wiele projektów pisze się w językach toporniejszych niż Ruby czy Python… :(

7. avatar icon Stanisław 'dozzie' Klekot napisał(a) 20 Lis 2007 o godz. 18:00:

Hoppke, to ty chcesz języka ze statycznym typowaniem. W ogólnym przypadku nie wyobrażam sobie algorytmu refaktoryzacji dla języka z dynamicznym systemem typów.

8. avatar icon Radarek napisał(a) 20 Lis 2007 o godz. 18:00:

Próbowałeś Netbeans 6.0? Ma podpowiadanie kodu, robią coś w kwestii refaktoryzacji(*), ma debugowanie wizualne (tak, odpalasz server, ustalasz breakpoint, odpalasz przeglądarkę i jesteś w debuggerze :)). Profilowanie też widziałem, że jest możliwe. Może kiedyś coś napiszę o tym.

*Trzeba zawsze wziąć na poprawkę to, że języki dynamiczne nigdy nie będą tak łatwo dawały się refaktoryzować jak statyczne. To wynika z ich natury. Tutaj raczej trzeba pomyśleć o TDD czy BDD

9. avatar icon Hoppke napisał(a) 20 Lis 2007 o godz. 18:10:

Bez środowiska developerskiego dorównującego tym używanym do Javy czy .NET Ruby nadal będzie traktowany jak „taki lepszy PHP”.

Bez refaktoryzacji ciężko zarządzać większym projektem. Da się, ale ogranicza to wolność w wprowadzaniu rozleglejszych modyfikacji kodu. I siłą rzeczy koder zaczyna szukać rozwiązań, które da radę wprowadzić. Bo jak wiadomo praca programisty to obok kodowania głównie pałowanie się z narzędziami, a każdy kombinuje jak się najmniej namęczyć ;)

10. avatar icon Radarek napisał(a) 20 Lis 2007 o godz. 18:13:

Ale zrozum, że to jest język dynamiczny. Tu nigdy nie będzie pełnej refaktoryzacji. Są ludzie, którzy uważają, że potężne narzędzia (CASE?) załatwią za niego sprawę. Inni z kolei (Ci pragmatyczni) biorą sprawy w swoje ręce i piszą. No i trzeba sobie powiedzieć jasno: Ruby nie ma zamiaru wkraczać tam gdzie Java ma się dobrze, ale raczej tam gdzie użycie Javy jest toporne (np aplikacje webowe).

11. avatar icon sztywny napisał(a) 21 Lis 2007 o godz. 09:24:

„Ale zrozum, że to jest język dynamiczny. Tu nigdy nie będzie pełnej refaktoryzacji.”

Smalltalk.

12. avatar icon Radarek napisał(a) 21 Lis 2007 o godz. 16:52:

@sztywny, o smalltalku mam blade pojęcie, więc nie wiem co dokładnie chciałeś powiedzieć... ale Ruby nigdy nie będzie miał w pełni funkcjonajlną refaktoryzację (tzn. żadne IDE tego nie da). Jeśli się mylę to rozwiń proszę myśl :).

13. avatar icon sztywny napisał(a) 21 Lis 2007 o godz. 19:21:

Pierwsze narzędzia do automatycznej refaktoryzacji powstały właśnie w Smalltalku i z tego co wiem to do dziś wiele innych języków może tych narzędzi Smalltalkowi pozazdrościć. Jest to przy tym język tak dynamiczny jak tylko się da, dynamika Rubego wiąże się zresztą w dużej mierze z inspiracjami z tego języka. Ruby ma jednak dużo bardziej złożoną i elastyczną składnie, co jest dodatkowym problemem przy opracowywaniu tego typu narzędzi. Podsumowując: dynamika nie musi być przeszkodą dla tworzenia takich narzędzi, oraz uważam że mocno przesadziłeś z „nigdy” – dotąd nikt się poważnie rozwojem takich narzędzi dla Ruby nie zajmował, wystarczyło że pare miesięcy temu wziął się za to Sun z NetBeansem i już jakąś tam bazę mają. Gdyby pracował przy tym taki zespół jak np. budował refactoring w Eclipse, to pewnie byliby jeszcze dalej.

Pytanie czy w języku takim jak Ruby te narzędzią wogóle są aż tak bardzo koniecznie niezbędnie potrzebne. Steve Yegge bardzo ładnie to opisał:

http://steve.yegge.googlepages.com/transformation

14. avatar icon Hoppke napisał(a) 22 Lis 2007 o godz. 11:16:

Obecnie w programowaniu bardzo modne jest podejście „jak najszybciej przygotuj coś, co z grubsza działa, a potem to rozbudowuj” (no i „release early, release often”, model krótkich iteracji i częstego pokazywania aplikacji klientowi do akceptacji itp.).

Refactoring jest tu nieocenioną pomocą. Jasne, da się i bez tego pisać kod (tak samo jak da się i bez podświetlania/dopełniania składni i automatycznego formatowania kodu), ale fajnie gdy ma się takie dodatki.

15. avatar icon sztywny napisał(a) 24 Lis 2007 o godz. 09:44:

Porównanie refactoringu do podświetlania kodu i auto-indentu mnie zabiło :] Obawiam się, że nie przeczytałeś linka, więc w skrócie: refactoring to NIE JEST nazwa-parasol dla narzędzi transformujących kod wbudowanych w IDE. To pewien zestaw przekształceń, które są opisane na tyle ściśle, że niekiedy można ja zautomatyzować, ale równie dobrze można aplikować je ręcznie – ma to nawet pewne przewagi, widać wtedy jaki jest ich cel i mechanika działania.

No chyba że pisząc „Refactoring”, miałeś na myśli „Automatyczny refactoring”, wtedy przepraszam za wykład. Chociaż nie do końca rozumiem co same te narzędzia miałby mieć wspólnego z krótkimi iteracjami.

16. avatar icon Hoppke napisał(a) 25 Lis 2007 o godz. 01:43:

@sztywny: tekst przeczytałem. Widać byłem niedostatecznie wyraźny :) Więc:

Zacznę od fragmentu definicji refactoringu z wikipedii: „In extreme programming and other agile methodologies, refactoring is an integral part of the software development cycle: developers alternate between adding new tests and functionality and refactoring the code to improve its internal consistency and clarity. Automatic unit testing ensures that refactoring does not make the code stop working.”

Refactoring jest po prostu częścią procesu tworzenia kodu – przytoczony wcześniej linkowany tekst opiera się na książce Fowlera i sprowadza refactoring do zamieniania „bad code” w „good code” (spłycając strasznie to, co książka próbuje przekazać) i sugeruje, że jeśli od razu napiszesz good code, to refactoring jest zbędny. Wydaje mi się, że tamten pan nie miał okazji by pracować w środowisku, gdzie refactoring nie jest tylko teorią (inaczej nie odwoływałby się tylko do swojej interpretacji książki – której zresztą praktycznie żaden z jego kolegów inżynierów nie czytał, co chyba najlepiej świadczy o środowisku w którym przyszło mu się obracać).

Bo ja widzę w tekście wnioski, które osobiście uważam za błędne – bo po pierwsze refactoring to nie tylko przerabianie zepsutego kodu na dobry, po drugie nawet pisząc idealny (w danym momencie) kod i tak możesz mieć refactoring wpisany w proces i będzie to tylko świadczyło o tym, że kod/produkt rozwija się prawidłowo. Jak to możliwe? Ano, w Agile to norma.

W agile refactoring to nie jest „ratowanie” kodu ani jego „naprawianie”. To najzupełniej normalna część cyklu tworzenia oprogramowania. Pisanie testów, pisanie kodu, ew. refactoring, pisanie/zmienianie testów, pisanie/zmienianie kodu, ew. refactoring. I to wszystko jeszcze ujęte w ramach iteracji, po których zwiększa się szansa na konieczność zrefaktoryzowania kodu.

Refactoring będzie miał tu na celu podniesienie jakości kodu, ale również po prostu jego przetworzenie tak, by spełnił nowe wymogi. W agile wymogi mogą zmieniać się bardzo szybko, z jednej iteracji na drugą. Ta metodologia wręcz zakłada gotowość do przeprowadzania małych rewolucji w kodzie całkiem regularnie. I niezależnie od użytego języka będzie to wymagało jakiejś pracy.

Automatyczny refactoring oferowany przez IDE to mechanizmy z których koder może skorzystać w swojej pracy. I przydają się tak samo jak dopełnianie składni, formatowanie tekstu czy integracja z frameworkami od testów jednostkowych.

17. avatar icon sztywny napisał(a) 25 Lis 2007 o godz. 18:43:

Myśle, że główną myślą artykułu było to, że ludzie zbyt często mylą „Refactoring”, z klikanymi narzędziami w IDE które go automatyzują, a że wpis niżej odniosłeś ten termin do wcinania / podświetlania… Teraz rozumiem co miałeś na myśli, szacun ;)

Dodaj coś od siebie

Możesz korzystać ze składni Textile.

Pola oznaczone * są wymagane.

Proszę o dodawanie komentarzy związanych z tematem postu, sprawy osobiste proszę załatwiać przez maila bądź gg.

Zastrzegam sobie prawo do moderacji komentarzy (edycja, usuwanie).