Czy to dobry styl, aby jawnie powrócić w Rubim?

155

Wychodząc z Pythona, gdzie zawsze istnieje „właściwy sposób” („Pythonic”), jeśli chodzi o styl, zastanawiam się, czy to samo istnieje dla Rubiego. Używam własnych wskazówek dotyczących stylu, ale myślę o wydaniu kodu źródłowego i chciałbym, aby był zgodny z wszelkimi niepisanymi regułami, które mogą istnieć.

Czy jawne wpisywanie returnmetod w metodach to „The Ruby Way” ? Widziałem to zrobione zi bez, ale czy jest na to właściwy sposób? Czy jest może na to odpowiedni moment ? Na przykład:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end
Sasha Chedygov
źródło
3
To jest bardzo stare pytanie, ale tym, którzy mają podobne pytania, gorąco polecamy książkę Eloquent Ruby. To nie jest przewodnik po stylu, ale wprowadzenie do pisania idiomatycznego Rubiego. Chciałbym, żeby więcej ludzi to przeczytało!
Brian Dear
Jako osoba, która odziedziczyła dużą starszą aplikację opracowaną w języku Ruby, bardzo doceniam to, że zadałeś to pytanie. To zasługuje na pochwałę. Jest to zaśmiecone w naszej starszej aplikacji. Jednym z moich zadań w tym zespole jest ulepszanie bazy kodu (trudne, gdy jestem nowy w Rubim).
Stevers

Odpowiedzi:

226

Stare (i „z odpowiedzią”) pytanie, ale w odpowiedzi dorzucę moje dwa centy.

TL; DR - nie musisz, ale w niektórych przypadkach może to uczynić twój kod bardziej przejrzystym.

Chociaż nieużywanie wyraźnego zwrotu może być „sposobem Rubiego”, jest to mylące dla programistów pracujących z nieznanym kodem lub niezaznajomionych z tą funkcją Rubiego.

To nieco wymyślony przykład, ale wyobraź sobie taką małą funkcję, która dodaje jeden do przekazanej liczby i przypisuje ją do zmiennej instancji.

def plus_one_to_y(x)
    @y = x + 1
end

Czy miała to być funkcja zwracająca wartość, czy nie? Naprawdę trudno powiedzieć, co miał na myśli programista, ponieważ zarówno przypisuje zmienną instancji, jak i zwraca również przypisaną wartość.

Załóżmy, że dużo później, inny programista (być może nie tak dobrze zaznajomiony z tym, jak Ruby zwraca na podstawie ostatniej linii wykonanego kodu) pojawia się i chce umieścić kilka instrukcji print do logowania, a funkcja staje się następująca ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
end

Teraz funkcja jest zepsuta, jeśli cokolwiek oczekuje zwróconej wartości . Jeśli nic nie oczekuje zwróconej wartości, wszystko jest w porządku. Oczywiście, jeśli gdzieś dalej w łańcuchu kodu coś wywołującego to oczekuje zwróconej wartości, zakończy się niepowodzeniem, ponieważ nie odzyska tego, czego oczekuje.

Prawdziwe pytanie brzmi teraz: czy coś naprawdę spodziewało się zwróconej wartości? Czy to coś zepsuło, czy nie? Czy to coś zepsuje w przyszłości? Kto wie! Dowiesz się tylko o pełnym przeglądzie kodu wszystkich połączeń.

Tak więc przynajmniej dla mnie najlepszym podejściem jest albo bardzo wyraźne, że zwracasz coś, jeśli ma to znaczenie, albo w ogóle nic nie zwracasz, gdy tak nie jest.

Więc w przypadku naszej małej funkcji demo, zakładając, że chcemy, aby zwracała wartość, byłoby to zapisane w ten sposób ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    return @y
end

Dla każdego programisty byłoby bardzo jasne, że zwraca wartość, a znacznie trudniej byłoby mu ją złamać, nie zdając sobie z tego sprawy.

Alternatywnie można napisać to w ten sposób i pominąć instrukcję return ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    @y
end

Ale po co zawracać sobie głowę pomijaniem słowa zwrot? Dlaczego nie po prostu go tam umieścić i wyjaśnić w 100%, co się dzieje? Dosłownie nie będzie to miało wpływu na zdolność twojego kodu do działania.

Tim Holt
źródło
6
Ciekawy punkt. Myślę, że faktycznie trafiłem na podobną sytuację, gdy zadałem to pytanie, w którym pierwotny programista użył niejawnej wartości zwracanej w swoim kodzie i bardzo trudno było zrozumieć, co się dzieje. Dziękuję za odpowiedź!
Sasha Chedygov
6
Znalazłem to pytanie podczas wyszukiwania w Google ukrytych zwrotów, ponieważ właśnie to mnie spaliło. Dodałem trochę logiki na końcu funkcji, która niejawnie coś zwracała. Większość wywołań nie przejmowała się (lub sprawdzała) zwracaną wartość, ale jedno tak - i zawiodło. Na szczęście przynajmniej pokazało się to w niektórych testach funkcjonalnych.
Tim Holt
16
Chociaż to prawda, ważne jest, aby kod był czytelny, unikanie dobrze znanej funkcji języka programowania na rzecz gadatliwości jest moim zdaniem złą praktyką. Jeśli programista nie jest zaznajomiony z Rubim, może powinien zapoznać się przed dotknięciem kodu. Uważam za trochę głupie, że muszę zwiększać bałagan w moim kodzie tylko na przyszłe wydarzenie, które jakiś programista spoza Rubiego będzie musiał zrobić później. Nie powinniśmy sprowadzać języka do najniższego wspólnego mianownika. Piękno Rubiego polega na tym, że nie musimy używać 5 linijek kodu, aby powiedzieć coś, co powinno zająć tylko 3 lata.
Brian Dear
25
Jeśli słowo returndodaje znaczenie semantyczne do kodu, dlaczego nie? Nie jest to zbyt rozwlekłe - nie mówimy o 5 wierszach na 3, tylko jedno słowo więcej. Wolałbym widzieć returnprzynajmniej w funkcjach (może nie w blokach), aby wyrazić, co faktycznie robi kod. Czasami musisz zwrócić funkcję mid-function - nie pomijaj ostatniego returnsłowa kluczowego w ostatniej linii tylko dlatego, że możesz. W ogóle nie preferuję gadatliwości, ale wolę spójność.
mindplay.dk
6
To świetna odpowiedź. Ale nadal istnieje ryzyko, że napiszesz metodę Ruby, która ma być procedurą (inaczej jednostka zwracająca funkcję), np. side_effect()Inny programista zdecyduje się (ab) użyć niejawnej wartości zwracanej przez twoją procedurę. Aby to naprawić, możesz jawnie określić swoje procedury return nil.
Alex Dean,
66

Nie. Dobry styl Ruby generalnie używa jawnych zwrotów tylko do wczesnego powrotu . Ruby specjalizuje się w minimalizmie kodu / ukrytej magii.

To powiedziawszy, jeśli wyraźny zwrot sprawiłby, że rzeczy byłyby jaśniejsze lub łatwiejsze do odczytania, nic nie zaszkodzi.

Ben Hughes
źródło
116
Jaki jest sens minimalizmu kodu, jeśli prowadzi do maksymalizmu zamieszania?
jononomo
@JonCrowell Powinno zamiast tego skutkować maksymalizacją dokumentacji.
jazzpi
29
@jazzpi Jeśli musisz dokładnie udokumentować swój kod, aby był dobrze zrozumiany, nie praktykujesz minimalizmu, ćwiczysz golfa kodu .
Schwern
2
Wykluczenie końcowego powrotu nie jest mylące, mylące jest włączenie go - jako programista Ruby returnma już znaczące znaczenie semantyczne jako WCZESNE WYJŚCIE z funkcji.
Devon Parsons,
14
@DevonParsons Jak w ogóle ktoś mógłby pomylić a returnw ostatniej linii jako WCZESNE WYJŚCIE?
DarthPaghius
31

Osobiście używam returnsłowa kluczowego do rozróżnienia między tym, co nazywam metodami funkcjonalnymi , tj. Metodami, które są wykonywane głównie dla ich wartości zwracanej, od metod proceduralnych, które są wykonywane głównie ze względu na ich skutki uboczne. Tak więc metody, w których zwracana wartość jest ważna, otrzymują dodatkowereturn słowo kluczowe, aby zwrócić uwagę na wartość zwracaną.

Używam tego samego rozróżnienia przy wywoływaniu metod: metody funkcjonalne mają nawiasy, a metody proceduralne nie.

I wreszcie, używam tego rozróżnienia również w przypadku bloków: bloki funkcyjne otrzymują nawiasy klamrowe, bloki proceduralne (tj. Bloki, które coś „robią”) otrzymują do/ end.

Jednak staram się nie podchodzić do tego religijnie: z blokami, nawiasami klamrowymi i do/ endmają inny priorytet, i zamiast dodawać wyraźne nawiasy, aby ujednoznacznić wyrażenie, po prostu przełączam się na inny styl. To samo dotyczy wywoływania metod: jeśli dodanie nawiasów wokół listy parametrów sprawia, że ​​kod jest bardziej czytelny, robię to, nawet jeśli dana metoda ma charakter proceduralny.

Jörg W Mittag
źródło
1
W tej chwili pomijam zwrot w jakichkolwiek „jednokierunkowych”, co jest podobne do tego, co robisz, a wszystko inne robię tak samo. Dobrze wiedzieć, że nie jestem w moim stylu. :)
Sasha Chedygov
@Jorg: Czy korzystasz z wczesnych zwrotów w swoim kodzie? Czy powoduje to jakiekolwiek zamieszanie w Państwa schemacie proceduralnym / funkcjonalnym?
Andrew Grimm,
1
@Andrew Grimm: Tak i tak. Myślę o odwróceniu tego. Zdecydowana większość moich metod jest referencyjnie przezroczysta / czysta / funkcjonalna / jakkolwiek chcesz to nazwać, więc sensowne jest użycie tam krótszej formy. A metody napisane w stylu funkcjonalnym zwykle nie przynoszą wczesnych korzyści, ponieważ implikuje to pojęcie „kroku”, które nie jest zbyt funkcjonalne. Chociaż nie lubię zwrotów w środku. Używam ich na początku jako strażników lub na końcu, aby uprościć przepływ kontroli.
Jörg W Mittag,
Świetna odpowiedź, ale w rzeczywistości lepszą praktyką jest wywoływanie metod proceduralnych z () i metod funkcjonalnych bez - zobacz moją odpowiedź poniżej, aby wyjaśnić, dlaczego
Alex Dean
13

Właściwie ważne jest rozróżnienie między:

  1. Funkcje - metody wykonywane dla ich wartości zwracanej
  2. Procedury - metody wykonywane z powodu ich skutków ubocznych

Ruby nie ma natywnego sposobu ich rozróżniania - co czyni cię podatnym na napisanie procedury side_effect()i innego programistę decydującego się na nadużycie niejawnej wartości zwracanej przez twoją procedurę (w zasadzie traktując ją jako nieczystą funkcję).

Aby rozwiązać ten problem, wyjmij kartkę z książki Scali i Haskella i poproś o jawne zwrócenie procedurnil (aka Unitlub ()w innych językach).

Jeśli będziesz postępować zgodnie z tym, używanie jawnej returnskładni lub nie będzie po prostu kwestią osobistego stylu.

Aby dalej rozróżnić funkcje i procedury:

  1. Skopiuj fajny pomysł Jörga W Mittaga na pisanie bloków funkcyjnych za pomocą nawiasów klamrowych i bloków proceduralnych za pomocą do/end
  2. Kiedy wywołujesz procedury, używaj (), podczas gdy wywołując funkcje, nie

Zwróć uwagę, że Jörg W Mittag faktycznie zalecał odwrotną stronę - unikanie ()s dla procedur - ale to nie jest zalecane, ponieważ chcesz, aby wywołania metod wywołujących efekt uboczny były wyraźnie odróżnialne od zmiennych, szczególnie gdy arity wynosi 0. Zobacz przewodnik stylu Scala dotyczący wywoływania metod dla Detale.

Alex Dean
źródło
Jeśli potrzebuję zwróconej wartości z function_ai function_azostała napisana, aby wywołać (i może działać tylko przez wywołanie) procedure_b, to czy function_anaprawdę jest funkcją? Zwraca wartość, spełniając wymagania dotyczące funkcji, ale ma efekt uboczny, spełniając wymagania procedur.
Devon Parsons
2
Masz rację Devon - function_amoże zawierać drzewo procedure_...wezwań, tak jak rzeczywiście procedure_amogłoby zawierać drzewo function_...wezwań. Podobnie jak Scala, ale w przeciwieństwie do Haskella, w Rubim nie ma gwarancji, że funkcja nie wywołuje skutków ubocznych.
Alex Dean
8

Przewodnik po stylach stwierdza, że ​​nie powinieneś używać returnw swoim ostatnim oświadczeniu. Nadal możesz go używać, jeśli nie jest ostatni . Jest to jedna z konwencji, których społeczność ściśle przestrzega, i Ty też powinieneś, jeśli planujesz współpracować z kimkolwiek używającym Rubiego.


Biorąc to pod uwagę, głównym argumentem przemawiającym za używaniem jawnego returns jest to, że jest to mylące dla osób pochodzących z innych języków.

  • Po pierwsze, nie dotyczy to wyłącznie Rubiego. Na przykład Perl również ma niejawne zwroty.
  • Po drugie, większość ludzi, których to dotyczy, pochodzi z języków Algol. Większość z nich jest na "niższym poziomie" niż Ruby, dlatego musisz napisać więcej kodu, aby coś zrobić.

Typowa heurystyka długości metod (z wyłączeniem metod pobierających / ustawiających) w java to jeden ekran . W takim przypadku możesz nie widzieć definicji metody i / lub już zapomniałeś o tym, skąd wracałeś.

Z drugiej strony w Rubim najlepiej trzymać się metod krótszych niż 10 linii . Biorąc to pod uwagę, można by się zastanawiać, dlaczego musi pisać ~ 10% więcej stwierdzeń, skoro są one wyraźnie zasugerowane.


Ponieważ Ruby nie ma metod void i wszystko jest o wiele bardziej zwięzłe, po prostu dodajesz narzut bez żadnych korzyści, jeśli wybierzesz jawnereturn s.

ndnenkov
źródło
1
„To jedna z konwencji, których społeczność ściśle przestrzega” Z mojego doświadczenia wynika, że ​​nie, ludzie nie przestrzegają tego ściśle, chyba że są bardzo doświadczonymi programistami Rubiego.
Tim Holt
1
@TimHolt Musiałem wtedy mieć dużo szczęścia. (:
ndnenkov
1
@ndn całkowicie się zgadzam. Ważną rzeczą jest to, że kiedy metoda (lub klasa) zaczyna przekraczać 5/100 linii, dobrym pomysłem jest ponowne rozważenie tego, co próbujesz osiągnąć. Naprawdę Reguły Sandiego to ramy poznawcze służące do myślenia o kodzie w przeciwieństwie do arbitralnego dogmatu. Większość napotykanego kodu nie jest problematyczna, ponieważ jest sztucznie ograniczana - na ogół jest odwrotnie - klasy z wieloma obowiązkami, metody, które są dłuższe niż wiele klas. Zasadniczo zbyt wiele osób „skacze rekina” - mieszanie obowiązków.
Brian Dear
1
Czasami konwencja to wciąż zły pomysł. Jego stylistyczna konwencja, która opiera się na magii, sprawia, że ​​typy zwrotne są trudne do odgadnięcia. nie oferuje żadnych korzyści poza zapisaniem 7 naciśnięć klawiszy i jest niezgodny z prawie wszystkimi innymi językami. Jeśli Ruby kiedykolwiek przeprowadzi konsolidację i uproszczenie w stylu Pythona 3, mam nadzieję, że niejawne wszystko jest pierwsze w bloku cięcia.
Shayne
2
Z wyjątkiem, że wyraźnie nie ułatwia to czytania! Magia i zrozumiałość to nie wszystko.
Shayne
4

Zgadzam się z Benem Hughesem i nie zgadzam się z Timem Holtem, ponieważ pytanie dotyczy definitywnego sposobu, w jaki Python to robi i pyta, czy Ruby ma podobny standard.

To robi.

Jest to tak dobrze znana cecha języka, że ​​każdy, kto spodziewa się debugowania problemu w Ruby, powinien o tym wiedzieć.

Devon Parsons
źródło
3
W teorii zgadzam się z tobą, ale w praktyce programiści popełniają błędy i robią bałagan. Na przykład wszyscy wiemy, że używasz „==” w warunku, a nie „=”, ale WSZYSCY popełniliśmy ten błąd wcześniej i nie zdawaliśmy sobie z tego sprawy aż do później.
Tim Holt
1
@TimHolt Nie zajmuję się żadnym konkretnym przypadkiem użycia, po prostu odpowiadam na pytanie. Używanie returnsłowa kluczowego, gdy jest niepotrzebne, jest zdecydowanie złym stylem, wybranym przez społeczność .
Devon Parsons
3
Słusznie. Mówię, że styl, o którym wspominasz, może prowadzić do błędów, jeśli nie będziesz ostrożny. Opierając się na swoim doświadczeniu, osobiście opowiadam się za innym stylem.
Tim Holt
1
@TimHolt Nawiasem mówiąc, to pytanie zostało zadane na długo przed napisaniem przewodnika po stylu, więc odpowiedzi, które się ze sobą nie zgadzają, niekoniecznie są błędne, po prostu nieaktualne. Jednak jakoś się tu potknąłem i nie widziałem linku, który podałem, więc pomyślałem, że ktoś inny też może tu trafić.
Devon Parsons
1
Nie jest to oficjalny „standard”. Przewodnik po stylu ruby ​​używany przez rubocop i powiązany powyżej z twierdzeniem Devona Parsonsa, został stworzony przez hakera, który nie jest rdzeniem. Więc komentarz Devona jest po prostu błędny. Oczywiście nadal możesz go używać, ale nie jest to standard.
shevy
1

To kwestia preferowanego stylu. Musisz użyć słowa kluczowego return, jeśli chcesz powrócić gdzieś w środku metody.

Praveen Angyan
źródło
0

Jak powiedział Ben. Fakt, że „wartość zwracana przez metodę ruby ​​jest wartością zwracaną przez ostatnią instrukcję w treści funkcji” powoduje, że słowo kluczowe return jest rzadko używane w większości metod języka ruby.

def some_func_which_returns_a_list( x, y, z)
  return nil if failed_some_early_check


  # function code 

  @list     # returns the list
end
Gishu
źródło
4
możesz również napisać „return nil if failed_some_early_check”, jeśli twój zamiar jest nadal jasny
Mike Woodhouse
@Gishu Właściwie narzekałem na instrukcję if, a nie na nazwę metody.
Andrew Grimm,
@Andrew .. och brakujący koniec :). Rozumiem. naprawiono, aby uspokoić również drugi komentarz Mike'a.
Gishu
Dlaczego nie if failed_check then nil else @list end? To jest naprawdę funkcjonalne .
cdunn2001
@ cdunn2001 Nie jest jasne, dlaczego, skoro w przeciwnym razie, jest funkcjonalny. Powodem tego stylu jest to, że ogranicza zagnieżdżanie poprzez wczesne wyjścia. IMHO również bardziej czytelne.
Gishu