Это хороший стиль, чтобы явно вернуться в Ruby?

155

Исходя из фона Python, где всегда есть «правильный способ сделать это» («Pythonic»), когда дело доходит до стиля, мне интересно, существует ли то же самое для Ruby. Я использовал свои собственные рекомендации по стилю, но я думаю о выпуске своего исходного кода, и я хотел бы, чтобы он придерживался любых неписаных правил, которые могли бы существовать.

Это "Рубиновый Путь" явно печатать returnв методах? Я видел, как это было сделано с и без, но есть ли правильный способ сделать это? Есть ли подходящее время, чтобы сделать это? Например:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end
Саша Чедыгов
источник
3
Это очень старый вопрос, но для тех, у кого есть подобные вопросы, книга Eloquent Ruby настоятельно рекомендуется. Это не руководство по стилю, а введение в написание идиоматического Ruby. Я хотел бы, чтобы больше людей читало это!
Брайан Дорогой
Как человек, унаследовавший большое унаследованное приложение, разработанное в 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

И любому программисту было бы очень ясно, что оно возвращает значение, и им гораздо сложнее его сломать, даже не осознавая.

В качестве альтернативы, можно написать это так и пропустить оператор return ...

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

Но зачем вообще опускать слово «вернуться»? Почему бы просто не вставить это туда и не дать на 100% понять, что происходит? Это буквально не повлияет на способность вашего кода выполнять.

Тим Холт
источник
6
Интересный момент. Я думаю, что на самом деле столкнулся с подобной ситуацией, когда задал этот вопрос, когда первоначальный разработчик использовал неявное возвращаемое значение в своем коде, и было очень трудно понять, что происходит. Спасибо за Ваш ответ!
Саша Чедыгов
6
Я нашел вопрос, в то время как Гуглил на неявных возвращениях, поскольку я только что сгорел этим. Я добавил немного логики в конец функции, которая неявно что-то возвращала. Большинство обращений к нему не заботились (или проверяли) возвращаемое значение, но один обращался - и это не удавалось. К счастью, по крайней мере, это обнаружилось в некоторых функциональных тестах.
Тим Холт
16
Хотя это и правда, важно, чтобы код был читабельным, отказ от хорошо известной функции языка программирования в пользу многословия, на мой взгляд, плохая практика. Если разработчик не знаком с Ruby, возможно, он должен ознакомиться с ним перед тем, как прикасаться к коду. Я считаю немного глупым увеличивать беспорядок в моем коде только для будущего события, когда какой-то не-Ruby разработчик должен что-то сделать позже. Мы не должны сокращать язык до наименьшего общего знаменателя. Отчасти прелесть Ruby в том, что нам не нужно использовать 5 строк кода, чтобы сказать что-то, что должно занять только 3.
Брайан Уважаемый
25
Если слово returnдобавляет семантическое значение в код, почему бы и нет? Это вряд ли очень многословно - мы не говорим 5 строк против 3, просто еще одно слово. Я бы предпочел видеть returnхотя бы в функциях (возможно, не в блоках), чтобы выразить, что на самом деле делает код. Иногда вам нужно возвращать среднюю функцию - не пропускайте последнее returnключевое слово в последней строке только потому, что можете. Я вообще не одобряю многословие, но я предпочитаю последовательность.
mindplay.dk
6
Это отличный ответ. Но все равно остается риск того, что вы напишете метод Ruby, предназначенный для процедуры (например, возвращающий модуль), side_effect()и другой разработчик решает (ab) использовать неявное возвращаемое значение вашей процедуры. Чтобы это исправить, вы можете сделать ваши процедуры явно return nil.
Алекс Дин
66

Нет. Хороший стиль Ruby обычно использует только явные возвраты для раннего возврата . Ruby хорош в минимализме кода / неявной магии.

Тем не менее, если явное возвращение сделает вещи более понятными или более легкими для чтения, это ничего не повредит.

Бен Хьюз
источник
116
Какой смысл минимализма кода, если он приводит к максимализму путаницы?
jononomo
@JonCrowell Вместо этого это должно привести к максимализму документации.
Jazzpi
29
@jazzpi Если вам нужно тщательно документировать свой код, чтобы его хорошо понимали, вы не практикуете минимализм, вы практикуете код-гольф .
Шверн
2
Это не сбивает с толку, чтобы исключить окончательное возвращение, это сбивает с толку, чтобы ВКЛЮЧИТЬ это - как разработчик Ruby, returnуже имеет значительное семантическое значение как РАННИЙ ВЫХОД в функцию.
Девон Парсонс
14
@DevonParsons Как вообще кто-то может перепутать returnпоследнюю строчку с РАННИМ ВЫХОДОМ ?
Дарт Пагийус
31

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

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

И последнее, но не менее важное, я также использую это различие с блоками: функциональные блоки получают фигурные скобки, процедурные блоки (то есть блоки, которые «делают» что-то) получают do/ end.

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

Йорг Миттаг
источник
1
Прямо сейчас я опускаю возврат в любых «однострочниках», который похож на то, что вы делаете, и я делаю все остальное так же. Приятно осознавать, что я не совсем в своем стиле. :)
Саша Чедыгов
@Jorg: вы используете ранний возврат в вашем коде? Это вызывает путаницу с вашей процедурной / функциональной схемой?
Эндрю Гримм
1
@ Андрей Гримм: Да, и Да. Я думаю перевернуть это. Подавляющее большинство моих методов в любом случае являются ссылочно-прозрачными / чистыми / функциональными / как бы вы их ни называли, поэтому имеет смысл использовать там более короткую форму. И методы, написанные в функциональном стиле, как правило, не имеют ранних результатов, поскольку это подразумевает понятие «шаг», которое не очень функционально. Хотя я не люблю возвращения в середине. Я использую их в начале как охрану или в конце, чтобы упростить управление.
Йорг Миттаг
Отличный ответ, но на самом деле лучше вызывать процедурные методы с () и функциональные методы без - см. Мой ответ ниже, почему
Алекс Дин
13

На самом деле важно различать:

  1. Функции - методы, выполняемые для их возвращаемого значения
  2. Процедуры - методы, выполненные для их побочных эффектов

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

Чтобы решить эту проблему, возьмите лист из книги Scala и Haskell и попросите явным образом вернуть ваши процедурыnil (иначе Unitили ()на других языках).

Если вы следуете этому, то использование явного returnсинтаксиса или не только становится вопросом личного стиля.

Для дальнейшего различения функций и процедур:

  1. Скопируйте замечательную идею Йорга В. Миттага о написании функциональных блоков с помощью фигурных скобок и процедурных блоков с помощью do/end
  2. Когда вы вызываете процедуры, используйте (), тогда как когда вы вызываете функции, не

Обратите внимание, что Jörg W Mittag на самом деле выступал с другой стороны - избегая ()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 заключается в том, что это сбивает с толку людей, пришедших с других языков.

  • Во-первых, это не совсем эксклюзивно для Ruby. Например, Perl также имеет неявные возвраты.
  • Во-вторых, большинство людей, к которым это относится, - выходцы из алгольских языков. Большинство из них более «низкоуровневые», чем Ruby, поэтому вам нужно написать больше кода, чтобы что-то сделать.

Распространенной эвристикой для длины метода (исключая методы получения / установки) в Java является один экран . В этом случае вы можете не увидеть определение метода и / или уже забыли о том, откуда вы возвращались.

С другой стороны, в Ruby лучше придерживаться методов длиной менее 10 строк . Учитывая это, можно задаться вопросом, почему он должен писать на ~ 10% больше заявлений, когда они явно подразумеваются.


Так как в Ruby нет методов void, и все намного проще, вы просто добавляете накладные расходы ни для одного из преимуществ, если вы используете явные returns.

ndnenkov
источник
1
«Это одно из соглашений, которым строго следует сообщество». Мой опыт показывает, что нет, люди не следуют этому строго, если они не очень опытные разработчики Ruby.
Тим Холт
1
@TimHolt Мне тогда очень повезло. (:
ndnenkov
1
@ndn полностью согласен. Важно то, что когда метод (или класс) начинает превышать 5/100 строк, тогда стоит пересмотреть то, что вы пытаетесь достичь. Действительно Правила Санди - это когнитивная основа для размышлений о коде, а не произвольная догма. Большая часть кода, с которым я сталкиваюсь, не является проблематичной, поскольку она искусственно ограничена - как правило, наоборот - классы с множеством обязанностей, методы, которые длиннее многих классов. По сути, слишком много людей «прыгают с акулы» - смешивая обязанности.
Брайан Дорогой
1
Иногда соглашение все еще плохая идея. Это стилистическое соглашение, которое опирается на магию, затрудняет угадывание типов возврата. не предлагает никаких преимуществ, кроме сохранения 7 нажатий клавиш и не совместим почти со всеми другими языками. Если когда - либо рубин делает Python 3 консолидации стиля и упрощение проход, я надеюсь , что неявное ничего является первым на плахе.
Шейн
2
За исключением случаев, когда это явно не делает вещи проще для чтения! Магия и понятность - не лучшая вещь.
Шейн
4

Я согласен с Беном Хьюзом и не согласен с Тимом Холтом, потому что в этом вопросе упоминается окончательный способ, которым Python делает это, и спрашивает, есть ли у Ruby аналогичный стандарт.

Оно делает.

Это настолько известная особенность языка, что любой, кто ожидал отладить проблему в ruby, должен был знать о ней.

Девон Парсонс
источник
3
Я согласен с вами в теории, но на практике программисты делают ошибки и путаются. В данном случае мы все знаем, что вы используете «==» в условных выражениях, а не «=», но мы ВСЕ делали эту ошибку раньше и не осознавали ее позже.
Тим Холт
1
@TimHolt Я не рассматриваю какой-либо конкретный случай использования, я просто отвечаю на вопрос. Это явно плохой стиль, выбранный сообществом, использовать returnключевое слово, когда в нем нет необходимости.
Девон Парсонс
3
Справедливо. Я говорю, что упомянутый вами стиль может привести к ошибкам, если вы не будете осторожны. Исходя из моего опыта, я лично защищаю другой стиль.
Тим Холт
1
@TimHolt Кстати, этот вопрос задавался задолго до того, как было написано руководство по стилю, поэтому несогласные ответы не обязательно являются неправильными, просто устарели. Я как-то тут споткнулся и не увидел предоставленную мной ссылку, поэтому решил, что кто-то еще тоже может оказаться здесь.
Девон Парсонс
1
Это не официальный "стандарт". Руководство по стилю ruby, используемое rubocop и связанное выше с утверждением Devon Parsons, разработанным неосновным хакером ruby. Таким образом, комментарий Девона на самом деле просто неверен. Конечно, вы все равно можете использовать его, но это не стандарт.
Шеви
1

Вопрос в том, какой стиль вы предпочитаете по большей части. Вы должны использовать ключевое слово return, если хотите вернуться откуда-то посередине метода.

Правин Ангьян
источник
0

Как сказал Бен. Тот факт, что «возвращаемое значение метода ruby ​​является возвращаемым значением последнего оператора в теле функции», приводит к тому, что ключевое слово return редко используется в большинстве методов 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
источник
4
Вы также можете написать «вернуть ноль, если не удалось», если ваше намерение все еще ясно
Майк Вудхаус
@Gishu Я действительно жаловался на оператор if, а не на имя метода.
Эндрю Гримм
@ Андрей .. о пропавший конец :). Понял. исправлено, чтобы успокоить другой комментарий тоже от Майка.
Гишу
Почему нет if failed_check then nil else @list end? Это действительно функционально .
cdunn2001
@ cdunn2001 Непонятно, почему, если тогда еще функционирует ... причина этого стиля в том, что он уменьшает вложение посредством ранних выходов. ИМХО тоже более читабельно.
Гишу