Це гарний стиль явно повернутися в Ruby?


155

Походячи з фона Python, де завжди є "правильний спосіб зробити це" ("Pythonic"), коли справа стосується стилю, мені цікаво, чи те саме існує для Ruby. Я використовую свої власні рекомендації щодо стилю, але я замислююся над тим, щоб випустити свій вихідний код, і я хотів би, щоб він дотримувався будь-яких неписаних правил, які можуть існувати.

Це "Рубіновий шлях" явно набирати returnметоди? Я бачив, як це робилося з і без, але чи є правильний спосіб це зробити? Чи може бути потрібний час для цього? Наприклад:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end

3
Це дуже давнє запитання, але для тих, у кого є подібні запитання, Книга Красномовна Рубі настійно рекомендується. Це не посібник зі стилю стільки, скільки вступ до написання ідіоматичної Рубі. Я б хотів, щоб більше людей читали це!
Брайан Шановний

Як хтось, хто успадкував велику застарілу програму, розроблену в Ruby, я дуже вдячний, що ви задали це питання. Це заслуговує на надбавку. Це засмічено в нашій застарілій програмі. Одне з моїх завдань в цій команді - вдосконалити кодову базу (важко, коли я новачок у Ruby).
Stevers

Відповіді:


226

Старе (і "відповів") питання, але я підкину два центи як відповідь.

TL; DR - Вам не доведеться, але це може зробити ваш код набагато більш зрозумілим у деяких випадках.

Хоча не використовувати явне повернення може бути "способом Ruby", це заплутує програмістів, які працюють з незнайомим кодом, або незнайомих з цією функцією Ruby.

Це дещо надуманий приклад, але уявіть собі таку функцію, як ця, яка додає одне до переданого числа та призначає його змінній екземпляра.

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

Це означало бути функцією, яка повертала значення, чи ні? Справді важко сказати, що мав на увазі розробник, оскільки він обидва призначає змінну екземпляра, а також повертає призначене значення.

Припустимо, набагато пізніше приходить інший програміст (можливо, не той, хто знайомий з тим, як Ruby повертається на основі останнього рядка коду, виконаного) і хоче ввести деякі заяви друку для ведення журналу, і функція стає цією ...

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

Тепер функція порушена, якщо щось очікує поверненого значення . Якщо нічого не очікує поверненого значення, це добре. Ясна річ, якщо десь далі в ланцюзі коду щось, що викликає це, очікує повернене значення, воно вийде з ладу, оскільки не поверне те, що очікує.

Справжнє запитання зараз таке: чи дійсно щось очікував поверненої вартості? Це щось зламало чи ні? Чи це щось порушить у майбутньому? Хто знає! Лише повний огляд коду всіх дзвінків повідомить вас про це.

Так що для мене, принаймні, найкращим підходом є або бути явним, що ви щось повертаєте, якщо це має значення, або взагалі нічого не повертаєте, коли цього не відбувається.

Так, у випадку нашої малої демонстраційної функції, припускаючи, що ми хотіли, щоб вона повернула значення, було б написано так ...

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

І будь-якому програмісту було б зрозуміло, що він повертає значення, і набагато важче їм зламати його, не усвідомлюючи цього.

Як варіант, можна написати це так і залишити зворотну заяву ...

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

Але навіщо турбуватися, залишаючи слово повернення? Чому б просто не помістити його туди і зробити 100% зрозумілим, що відбувається? Це буквально не матиме впливу на здатність вашого коду.


6
Цікавий момент. Я думаю, що я насправді зіткнувся з подібною ситуацією, коли я задав це питання, де оригінальний розробник використовував неявне повернене значення у своєму коді, і було дуже важко зрозуміти, що відбувається. Дякую за вашу відповідь!
Саша Чедигов

6
Я знайшов це питання під час гуглінгу на неявних поверненнях, оскільки мене тільки що спалило це. Я додав деяку логіку до кінця функції, яка неявно щось повертала. Більшість дзвінків до нього не хвилювали (або перевіряли) повернене значення, але один - і не вдалося. На щастя, принаймні це виявилося в деяких функціональних тестах.
Тім Холт

16
Хоча правда, важливо, щоб код був читабельним, але, на мою думку, поганою практикою є уникнення відомої особливості мови програмування на користь багатослівності. Якщо розробник не знайомий з Ruby, то, можливо, вони повинні ознайомитись, перш ніж торкатися коду. Мені здається, що це трохи дурніше, щоб збільшити захаращеність мого коду тільки для майбутньої події, яку дещо розробник, який не має Ruby, повинен зробити щось пізніше. Ми не повинні зводити мову до найнижчого загального знаменника. Частина краси Рубі полягає в тому, що нам не потрібно використовувати 5 рядків коду, щоб сказати щось, що повинно пройти лише 3.
Брайан Шановний

25
Якщо слово returnдодає коду смислове значення, чому б ні? Це навряд чи дуже багатослівний - ми не говоримо 5 рядків проти 3, лише ще одне слово. Я вважаю за краще бачити returnпринаймні у функціях (можливо, не в блоках), щоб виразити, що насправді робить код. Іноді доводиться повертати середину функції - не залишайте останнього returnключового слова в останньому рядку лише тому, що можете. Я взагалі не віддаю перевагу багатослівності, але я віддаю перевагу послідовності.
mindplay.dk

6
Це чудова відповідь. Але це все ще залишає ризик написання методу Ruby, який має намір стати процедурою (він же називає функцію повернення блоку), наприклад, side_effect()і інший розробник вирішує (ab) використовувати неявне повернене значення вашої процедури. Щоб виправити це, ви можете зробити свої процедури чітко return nil.
Алекс Дін

66

Ні. Добрий стиль Ruby, як правило, використовував лише явні повернення для раннього повернення . Рубі - великий мінімалізм коду / неявна магія.

Однак, якщо явне повернення зробить речі зрозумілішими чи легшими для читання, це нічого не зашкодить.


116
Який сенс мінімалізму коду, якщо це призводить до плутанини максималізму?
jononomo

@JonCrowell Натомість це повинно призвести до документального максималізму.
джазпі

29
@jazzpi Якщо вам доведеться сильно документувати свій код, щоб він добре зрозумів, що ви не практикуєте мінімалізм, ви практикуєте код гольфу .
Шверн

2
Це не заплутано, щоб виключити остаточне повернення, це заплутано ВКЛЮЧИТИ його - як розробник Ruby, returnвже має суттєве смислове значення як РІШНИЙ ВИХІД до функції.
Девон Парсонс

14
@DevonParsons Як же хтось може переплутати в returnостанньому рядку як РІЧНИЙ ВИХІД?
DarthPaghius

31

Я особисто використовую returnключове слово, щоб розрізняти те, що я називаю функціональними методами , тобто методами, які виконуються в основному для їх повернення, і процедурними методами , які виконуються в основному для їх побічних ефектів. Отже, методи, в яких значення повернення важливе, отримують додаткове returnключове слово, щоб звернути увагу на повернене значення.

Я використовую те саме розрізнення при виклику методів: функціональні методи отримують круглі дужки, процедурні методи - ні.

І останнє, але не менш важливе, я також використовую це розрізнення з блоками: функціональні блоки отримують фігурні дужки, процедурні блоки (тобто блоки, які щось "роблять") do/ end.

Однак я намагаюся не бути релігійним щодо цього: з блоками, фігурними дужками та do/ endмають різний пріоритет, і замість того, щоб додавати явні круглі дужки, щоб розмежувати вираз, я просто переходжу до іншого стилю. Те саме стосується виклику методу: якщо додавання дужок навколо списку параметрів робить код більш читабельним, я це роблю, навіть якщо відповідний метод має процедурний характер.


1
Зараз я пропускаю повернення в будь-якому "однолінійці", подібному до того, що ви робите, а все інше я роблю так само. Приємно знати, що я не повністю в своєму стилі. :)
Саша Чедигов

@Jorg: Чи використовуєте ви раннє повернення у своєму коді? Чи викликає це плутанина з вашою процедурною / функціональною схемою?
Ендрю Грімм

1
@Andrew Grimm: Так, і так. Я думаю про це перевернути. Переважна більшість моїх методів є відносно прозорими / чистими / функціональними / як би ви не хотіли це назвати, тому має сенс використовувати там більш коротку форму. І методи, написані у функціональному стилі, як правило, не мають раннього повернення, оскільки це передбачає поняття "крок", яке не дуже функціонально. Хоча, я не люблю повернення в середині. Я або використовую їх на початку в якості охоронців, або в кінці, щоб спростити потік управління.
Йорг W Міттаг

Чудова відповідь, але насправді краща практика називати процедурні методи за допомогою () та функціональних методів без - дивіться мою відповідь нижче, чому
Алекс Дін

13

Насправді важливо розрізняти:

  1. Функції - методи, виконані для їх повернення
  2. Процедури - методи, виконані для їх побічних ефектів

У Ruby немає власного способу їх розрізнення - це залишає вас вразливим до написання процедури, side_effect()а інший розробник вирішує зловживати неявним поверненням вашої процедури (в основному трактуючи це як нечисту функцію).

Щоб вирішити це, візьміть лист із книги Скали та Хаскелла і переконайтесь, що ваші процедури явно повертаються nil(ака Unitчи ()іншими мовами).

Якщо ви дотримуєтесь цього, то використання явного returnсинтаксису чи не просто стає справою особистого стилю.

Для подальшого розмежування функцій та процедур:

  1. Скопіюйте хорошу ідею Йорга У Міттага про написання функціональних блоків фігурними дужками, а процедурні блоки - за допомогою do/end
  2. Коли ви викликаєте процедури, використовуйте (), тоді як, коли ви викликаєте функції, не робіть цього

Зауважте, що Йорг W Міттаг насправді виступав навпаки - уникаючи ()процедур s - але це не доцільно, оскільки ви хочете, щоб виклики методу стороннього ефекту були чітко відмінні від змінних, особливо коли значення arity дорівнює 0. Дивіться посібник зі стилю Scala щодо виклику методу для деталі.


Якщо мені потрібне повернене значення з function_a, і function_aбуло написано для дзвінка (і можна діяти лише за допомогою дзвінка) procedure_b, то це function_aсправді функція? Він повертає значення, виконуючи ваші вимоги до функцій, але має побічний ефект, виконуючи вимоги процедур.
Девон Парсонс

2
Ви маєте рацію Девон - function_aміг би містити дерево procedure_...дзвінків, як і справді procedure_aможе містити дерево function_...дзвінків. Як і Scala, але на відміну від Haskell, в Ruby не може бути гарантовано, що функція не має побічної дії.
Алекс Дін

8

Посібник зі стилів зазначає, що ви не повинні використовувати returnв останньому заяві. Ви все одно можете використовувати його, якщо він не останній . Це одна з конвенцій, яких спільнота дотримується суворо, і тому ви повинні, якщо ви плануєте співпрацювати з будь-ким, хто використовує Ruby.


Однак, головний аргумент для використання явного returns полягає в тому, що це заплутано для людей, які походять з інших мов.

  • По-перше, це не зовсім виключно Рубі. Наприклад, Perl також має неявну віддачу.
  • По-друге, більшість людей, до яких це стосується, походять з алгольських мов. Більшість із них є "нижчим рівнем", ніж Рубі, тому вам доведеться написати більше коду, щоб щось зробити.

Поширена евристика для довжини методу (за винятком геттерів / сеттерів) в java - це один екран . У цьому випадку ви, можливо, не бачите визначення методу та / або вже забули про те, звідки ви поверталися.

З іншого боку, у Ruby найкраще дотримуватися методів довжиною менше 10 рядків . Враховуючи це, варто задатися питанням, чому йому доводиться писати ~ 10% більше заяв, коли вони чітко маються на увазі.


Оскільки у Ruby немає недійсних методів, і все набагато стисліше, ви просто додаєте накладні витрати на жодну з переваг, якщо переходите з явними returns.


1
"Це одна з конвенцій, яких громада чітко дотримується." Мій досвід полягає в тому, що ні, люди не дотримуються цього строго, якщо вони не дуже досвідчені розробники Ruby.
Тім Холт

1
@TimHolt Мені, мабуть, дуже пощастило. (:
ndnenkov

1
@ndn повністю згоден. Важливим є те, що коли метод (або клас) починає перевищувати 5/100 рядків, то це гарна ідея переглянути те, що ви намагаєтеся досягти. Дійсно Правила Санді - це пізнавальна база для роздумів про код на відміну від довільної догми. Більшість кодів, з якими я стикаюсь, не є проблематичним, оскільки це штучно обмежене - як правило, навпаки - класи з численними обов'язками, методи, що довші, ніж у багатьох класів. По суті, занадто багато людей "стрибають акули" - змішування обов'язків.
Брайан Шановний

1
Іноді умовність все ще є поганою ідеєю. Її стилістична умова, яка покладається на магію, робить труднощі здобуття повернення. не пропонує жодних переваг, окрім збереження 7 натискань клавіш, і є невідповідним майже для всіх інших мов. Якщо Ruby коли-небудь пройде консолідацію та спрощення стилю Python 3, я сподіваюся, що неявне все, що є першим у відбиваючому блоці.
Шейне

2
За винятком явно, це насправді не полегшує читання! Магія та зрозумілість - це не річ аме.
Шайне

4

Я погоджуюся з Бен Хьюз і не погоджуюся з Тімом Холтом, тому що в питанні згадується про остаточний спосіб, як це робить Питон, і запитує, чи є у Рубі подібний стандарт.

Це робить.

Це настільки відома особливість мови, що кожен, хто очікував налагодження проблеми в рубіні, повинен розумно очікувати про неї.


3
Я погоджуюся з вами теоретично, але на практиці програмісти роблять помилки і псують. Справа в тому, що всі ми знаємо, що ви використовуєте "==" в умовному режимі, а не "=", але ми ВСІХ помилялися раніше і не усвідомлювали її пізніше.
Тім Холт

1
@TimHolt Я не звертаюся ні до якого конкретного випадку використання, я просто відповідаю на питання. Це явно поганий стиль, який обирає спільнота, використовувати returnключове слово, коли це зайве.
Девон Парсонс

3
Досить справедливо. Я кажу, що стиль, який ви згадуєте, може призвести до помилок, якщо ви не будете обережні. Виходячи зі свого досвіду, я особисто виступаю за інший стиль.
Тім Холт

1
@TimHolt До речі, це питання було задано ще до того, як було написано посібник зі стилів, тому відповіді, які не погоджуються, не обов'язково помиляються, просто застаріли. Я натрапив сюди якось, хоча і не побачив наданого мною посилання, тож я зрозумів, що хтось інший теж може опинитися тут.
Девон Парсонс

1
Це не офіційний "стандарт". Посібник зі стилю рубіну, використовуваний rubocop і пов'язаний вище із заявою, яку там має Девон Парсонс, створений непрофільним хабіном на рубіни. Тож коментар Девона насправді просто неправильний. Звичайно, ви ВЖЕ можете використовувати його, але це не стандарт.
шеві

1

Це питання, якому стилю ви віддаєте перевагу здебільшого. Вам потрібно використовувати ключове слово return, якщо ви хочете повернутися звідкись із середини методу.


0

Як сказав Бен. Той факт, що «значення, що повертається методом рубіну, є значенням повернення останнього оператора в тілі функції», призводить до того, що ключове слово повернення рідко використовується в більшості методів рубіну.

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


  # function code 

  @list     # returns the list
end

4
Ви також можете написати "повернути нуль, якщо не вдалося_значити_послідок", якщо ваш намір все-таки зрозумілий
Майк Вудхаус

@Gishu Я насправді скаржився на оператор if, а не на ім'я методу.
Ендрю Грімм

@Andrew .. о пропущений кінець :). Зрозумів. виправлено, щоб заспокоїти інший коментар теж від Майка.
Gishu

Чому ні if failed_check then nil else @list end? Це справді функціонально .
cdunn2001

@ cdunn2001 Не зрозуміло, чому якщо тоді інше функціональне. Причиною цього стилю є те, що він зменшує вкладення через ранні виходи. ІМХО також легше читати.
Gishu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.