Ruby a metody z '?' i '!' w nazwie
12 komentarzy | Kategorie: Ruby, Techblog | trackbackTagi: metoda nazwa pytajnik ruby wykrzyknik
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!
Dla mnie wykrzyknik zawsze oznaczał metodę, która nadpisuje zamiast duplikować… a tu takie jajca, Panie.
O, dzięki ;]
A myślałem, że wiem o co kaman.. dobry wpis!
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.
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).
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.
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
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).
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ą).
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).
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:).
Doskonale!