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

Ruby a metody z '?' i '!' w nazwie

12 komentarzy | Kategorie: Ruby, Techblog | trackback
Tagi:

Jednym z częstych pytań, jakie rodzą się wśród adeptów programowania w Ruby, jest kwestia znaków '?' i '!' w nazwach metod. Pojawiają się także wątpliwości czy Ruby traktuje jakoś specjalnie te znaki, czy stoi za nimi jakaś czarna magia. Nawet wśród bardziej doświadczonych programistów Rubiego zauważam, że nie zawsze poprawnie jest interpretowana idea tych znaków. Tym razem wyjaśnię te i inne wątpliwości związane z tymi dwoma znakami. Zapraszam zatem do lektury (nie tylko wspomnianych adeptów).

Pierwsza rzecz, na którą chciałem zwrócić uwagę jest fakt, że znaki te nie są w żaden specjalny sposób traktowane przez Ruby. Dla Rubiego to taki sam znam jak 'a', 'C', '0' czy '_'. Różnica jest tylko taka, że znak ten może występować tylko i wyłącznie na końcu nazwy (ale nie może być to jedyny znak w nazwie). Dlatego poprawne są nazwy zero?, deleted?, flatten!, map!, ale już nie foo?!, foo?bar, !bar itp.

ruby.awesome?

Na pierwszy rzut idzie łatwiejsza kwestia - pytajnik. Niepisana zasada mówi, że powinno się go używać dla metod, które są predykatami, czyli metodami które zwracają wartość true lub false. W innych językach najczęściej używa prefixu "is" w nazwie (np. is_active lub isDeleted). Oto kilka przykładów:

a = [1, 2, 3]
if a.include?(2)
  puts "Found!"
end

# przykład z rails
if request.post?
  # ...
end

user.awesome = user.active? && user.paid?

ruby.awesome!

Jakkolwiek kwestia pytajnika jest powszechnie rozumiana, tak z wykrzyknikiem jest gorzej. Jest to prawdopodobnie spowodowane tym, że przy przekazywaniu zasady, jaką powinno się kierować, zapomina się o bardzo ważnej części, bez której zasada ta ma inne znaczenie.

Spotykam się bardzo często ze stwierdzeniem, że "wykrzyknika należy używać w nazwach metod, które są potencjalnie niebezpieczne lub destrukcyjnie". To nieprawda! Pełna wersja powinna brzmieć tak: wykrzyknika należy używać w nazwach metod, które są bardziej niebezpieczną wersją metody bez wykrzyknika. Właśnie ta druga część jest kluczowa. Ale co to znaczy "potencjalnie niebezpieczna"? Czy chodzi o możliwość zepsucia komputera? A może wykasowanie ważnych danych, albo wejście na przestępczą ścieżkę?;-) Sęk w tym, że nie da się odpowiedzieć na to pytanie, bo dla jednego niebezpieczne będzie kasowanie plików na dysku, a dla drugiego aktualizacja danych w bazie.

Dlatego napisałem, że druga część jest kluczowa. Niektóre metody, które coś robią, mogą mieć dodatkowe wersje, które są w pewnym sensie bardziej niebezpieczne. Mając dwie wersje danej metody, możemy łatwiej określić co to znaczy "bardziej niebezpieczny", chociaż wciąż nie dostaniemy jednej odpowiedzi (ale nie o to w tym chodzi).

Jak przykład weźmy metodę Array#sort. Metoda ta sortuje tablicę i zwraca nową, posortowaną już, kopię. Istnieje wersja tej metody z wykrzyknikiem, tj. Array#sort!, która sortuje tablicę w miejscu, nadpisując oryginalną.

arr = [2, 3, 1]
arr.sort
p arr

arr = [2, 3, 1]
arr.sort!
p arr

Metoda Array#sort! sam w sobie nie jest niebezpieczna, ale w pewnym sensie jest bardziej niebezpieczna aniżeli wersja bez wykrzyknika bo, w przeciwieństwie do niej, zmienia obiekt odbiorcy (jest destrukcyjna).

Jeszcze raz podkreślam jak bardzo ważny jest fakt istnienia wersji bez wykrzyknika. Gdyby tak nie było, to nasze programy naszpikowane byłyby wywołaniami metod z wykrzyknikiem. Ruby posiada bardzo wiele wbudowanych metod, które można by uznać za niebezpieczne (bo np. modyfikują obiekt w miejscu), a które nie mają w nazwie '!'. Przykład? Proszę: Array#clear, Array#concat, Array.push, String#insert. To tylko kilka przykładów metod, które zmieniają bezpośrednio obiekt. Inne metody, które mogłyby zostać uznane za niebezpieczne, to dla przykładu FileUtils.rm_rf czy też nawet Kernel.exit.

Aktualizacja 21.02.2009, godz. 14:00.
W rozmowie z okim, słusznie zasugerował mi, że być może to moja interpretacja konwencji jest błędna. Doszukałem się zatem słów matza, autora Rubiego, na ten temat:

The bang (!) does not mean "destructive" nor lack of it mean non destructive either. The bang sign means "the bang version is more dangerous than its non bang counterpart; handle with care". Since Ruby has a lot of "destructive" methods, if bang signs follow your opinion, every Ruby program would be full of bangs, thus ugly.

Źródło: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/325912

Pozytywnie

Przykładem prawidłowego wykorzystania tej techniki jest railsowa metoda ActiveRecord#save, która zwraca wartość true lub false w zależności od tego czy obiekt został zapisy do bazy czy nie. Metoda ta posiada także wersję "bardziej niebezpieczną" ActiveRecord#save!, która robi to samo co poprzednia, ale w przypadku niepowodzenia rzuca wyjątkiem. Uważam, to za świetne podejście, ponieważ programista ma większą elastyczność w sposobie wyboru obsługi błędów.

Negatywnie

Z kolei metoda Rails::Configuration#threadsafe! jest przykładem błędnej interpretacji tej zasady. Ta metoda nie ma wersji "bezpiecznej". Podejrzewam, że zamiarem autorów było zwrócenie uwagi (w końcu od tego mamy znaki interpunkcyjne) na niebezpieczeństwo jakie się za tym kryje. Mimo to podtrzymuję swoje zdanie. W końcu to nie jedyna opcja, której włączenie musi dobrze przemyśleć programista (dla przykładu config.action_mailer.raise_delivery_errors). W ten sposób do wcześniej wymienionych nazw, destrukcyjnych metod, należałoby dodać '!'.

Mam nadzieję, że po tej lekturze zarówno '?', a zwłaszcza '!', będą poprawnie stosowane w nazwach metod. A Was proszę o prawidłowe przekazywanie tej zasady z pokolenia na pokolenie. Pomożecie? Pomożecie!

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

Komentarze

1. avatar icon Michał Górny napisał(a) 21 Lut 2009 o godz. 10:51:

Dla mnie wykrzyknik zawsze oznaczał metodę, która nadpisuje zamiast duplikować… a tu takie jajca, Panie.

2. avatar icon Wasacz napisał(a) 21 Lut 2009 o godz. 11:39:

O, dzięki ;]

3. avatar icon himn1 napisał(a) 21 Lut 2009 o godz. 13:09:

A myślałem, że wiem o co kaman.. dobry wpis!

4. avatar icon Radarek napisał(a) 21 Lut 2009 o godz. 14:05:

Zaktualizowałem wpis, dodając cytat matza nt. „bang method”.
Zrobiłem także mały research w polskim internecie i wszędzie jest źle opisana ta konwencja.

5. avatar icon Dodek napisał(a) 21 Lut 2009 o godz. 15:52:

A bo to jest ze Scheme zerżnięte, a tam jest raczej jednoznacznie ;) ? kończy predykaty, a ! funkcje z efektami ubocznymi (w tym destrukcyjne).

6. avatar icon (s)zymon napisał(a) 21 Lut 2009 o godz. 19:42:

hm, zawsze myślałem że ludność wie o co biega, jako że sicp od lat leżakuje po polskich księgarniach. Btw, to jedna z bardzo niewielu książek które nie straciły na przekładzie na polski. Tłumaczenie jest bardzo dobre.

7. avatar icon Paweł Kondzior napisał(a) 23 Lut 2009 o godz. 01:30:

Skąd pochodzi ta błędna opinia o ? a szczególnie o !, to chyba w Pick Axe jest opisane dokładnie, już nie pamiętam.

Jeśli chodzi o metody ktore wymieniles Array#clear, Array#concat, Array.push, String#insert to zadna z nich w jezyku nie posiada swojego nie-destrukcyjnego odpowiednika, a wiec wrzucenie metody Array#clear! nie mialo by sensu. bo niby po co nam metoda w stylu:

arr = [1,2,3]
arr = arr.clear

Natomaist jesli chodzi o Rails, to mamy straszny zament, bo jest zlamana konwencja ! i nie jest to robione nawet w konsekwentny sposob bo jest save!, create!, update_attributes!, ale brakuje juz update_attribute

8. avatar icon Radarek napisał(a) 23 Lut 2009 o godz. 09:26:

Paweł, w kwestii ‘?’ nie spotkałem się z błędną interpretacją.

Odnośnie metod Array#clear itp. Nie chodziło mi o to, że metody z ‘!’ powinny zostać dodane (bo tu się zgodzę, że nie miałoby to sensu). Chodzi o to, że są one destrukcyjne a nie zawierają ‘!’ (co potwierdza co napisałem, że metoda destrukcyjna niekoniecznie musi zawierać ‘!’ w nazwie).

Według dokumentacji istnieje zarówno update_attribute jak i update_attributes (http://apidock.com/rails).

9. avatar icon imploz napisał(a) 23 Lut 2009 o godz. 11:35:

Może z ‘?’ chodzi o pomysł używania go w nazwach akcesorów do atrybutów, które mają z założenia przechowywać wartość true/false (swego czasu ktoś wyskoczył z taką sugestią na comp.lang.ruby, ale z tego co pamiętam nie spotkało się to z aprobatą).

10. avatar icon imploz napisał(a) 23 Lut 2009 o godz. 11:44:

Poprawka: mowa była oczywiście tylko o readerach (trochę w stylu isCośtam(), jak nazywa się często gettery javowe do atrybutów bool).

11. avatar icon Kefas napisał(a) 26 Mar 2009 o godz. 19:50:

Tak czytam sobie całego twojego bloga na temat Rubiego i nasuwa mi się jedna rzecz. Jaka jest różnica między językiem Ruby a Pythonem?. Mam właśnie teraz dylemat, którego języka się uczyć. Bloga piszesz w specyficzny sposób, jakbym nie wiedział, że istnieje taka alternatywa jak Python to na pewno od razu wziął bym się za naukę Ruby:-). Jest to twoim największym atutem:P. Ale jako, że tak chwalisz ten język i pisałeś kiedyś, że zapoznasz się z Pythonem to może wyjaśnisz, który język jest bardziej „miły” dla początkującego. Chodzi tutaj o programowanie aplikacji do użytku domowego, skryptów, sieci, web etc. Może to taki niechciany offtop do twojego wpisu, za co z góry przepraszam:).

12. avatar icon JJ napisał(a) 12 Lut 2010 o godz. 16:06:

Doskonale!

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).