ruby style guide
Вступление
Role models are important.
– Офицер Алекс Мёрфи / Робот-полицейский
Один из вопросов, который меня всегда беспокоил как разработчика на Руби, — это то, что у разработчиков на Питоне есть великолепное руководству по стилю оформления (PEP-8), а у нас никогда не было официального руководства, описывавшего бы стиль оформления кода на Руби и дающего примеры его успешного применения. Я же уверен, что стиль оформления крайне важен. Также я верю, что такое замечательное сообщество разработчиков, которое есть у Руби, вполне имеет силы создать этот давно назревший документ.
Это наставление появилось на свет в нашей фирме в виде внутреннего руководства по оформлению кода на Руби (составленного вашим покорным слугой). И в какой-то момент я решил, что данная работа, которой я тогда занимался, может быть интересной и другим членам сообщества программистов на Руби и что миру вовсе не нужно еще одно руководство для внутреннего пользования: окружающий мир может получить пользу от совместно создаваемого и одобренного сообществом набора практик, идиом и стилистических предписаний для программирования на Руби.
Со времени опубликования этого руководства я получил многочисленные отклики от членов сообщества программистов на Руби из разных уголков со всего мира. Я очень благодарен им за полезные предложения и поддержку! Нашими общими усилиями мы сможем сделать этот ресурс полезным для всех и каждого разработчика на Руби.
И кстати, если вы работаете с Rails, вы можете взглянуть на дополняющее это руководство Ruby on Rails 3 & 4: Руководство по стилю оформления.
Руби: руководство по стилю оформления
Это руководство по оформлению кода на Руби дает передовые рекомендации, так что обычный программист на Руби сможет создавать код, который с легкостью смогут поддерживать другие обычные программисты на Руби. Руководство по оформлению, которое отражает повседневную практику, будет применяться постоянно, а руководство, стремящееся к идеалу, который не принимается обычными людьми, подвергается риску вообще быть забытым — не важно, насколько хорошим оно является.
Данное руководство разделено на несколько частей, состоящий из связанных по смыслу правил. В каждом случае я попытался обосновать появление этих правил (объяснение опущено в ситуациях, когда я посчитал его очевидным).
Все эти правила не появились из пустоты, они по большей части основываются на моем собственном обширном профессиональном опыте в качестве разработчика ПО, отзывах и предложениях других членов сообщества программистов на Руби и различных общепризнанных источниках по программированию на Руби, например, “Programming Ruby 1.9” и “Язык программирования Ruby” (в оригинале “The Ruby Programming Language”).
Во многих областях до сих пор нет единого мнения в среде разработчиков на Руби относительно конкретных аспектов стиля оформления (например, оформление строк в кавычках, пробелы при оформлении хешей, месторасположение точки при многострочном последовательном вызове методов и т.д.). В таких ситуациях мы рассматривали все распространенные стили, вам же решать, какой из этих стилей вы будете применять последовательно в вашем коде.
Это руководство все еще находится в процессе создания: у многих правил нет примеров, у других нет примеров, достаточно ясно объясняющих эти правила. В свое время каждое правило найдет свое объяснение, а пока просто примите их к сведению.
Вы можете создать копию этого руководства в форматах PDF или HTML при помощи Transmuter.
RuboCop — это анализатор кода, основывающийся на правилах этого руководства по оформлению.
Переводы данного руководства доступны на следующих языках:
- английский (исходная версия)
- вьетнамский
- испанский
- китайский традиционный
- китайский упрощенный
- корейский
- французский
- португальский
- русский (данный документ)
- японский
Оглавление
- Организация исходного кода
- Синтаксис
- Наименование
- Комментарии
- Классы и модули
- Исключения
- Коллекции
- Строки
- Регулярные выражения
- Процентные литералы
- Метапрограммирование
- Разное
- Инструментарий
Организация исходного кода
Nearly everybody is convinced that every style but their own is ugly and unreadable. Leave out the “but their own” and they’re probably right…
– Jerry Coffin (on indentation)
-
Используйте
UTF-8
в качестве кодировки для исходного кода.[ссылка] -
Используйте два пробела на уровень отступа (т.е. мягкую табуляцию). Никаких знаков табуляции. [ссылка]
```Ruby # плохо (четыре пробела) def some_method do_something end
хорошо
def some_method do_something end ```
-
Используйте стиль Unix для строк (пользователи *BSD/Solaris/Linux/OS X используют их по умолчанию, пользователям Windows нужно обратить особое внимание).[ссылка]
-
Если вы используете Git, вы можете добавить следующие настройки в вашу конфигурацию, чтобы предотвратить ненамеренное проникновение в ваш код строк, оканчивающихся в стиле Windows:
bash $ git config --global core.autocrlf true
-
-
Не используйте
;
для разделения директив и выражений. Отсюда непосредсвенно следует, что каждая директива должна занимать свою отдельную строку.[ссылка]```Ruby # плохо (точка с запятой избыточна) puts ‘foobar’;
puts ‘foo’; puts ‘bar’ # две директивы на одной строке
хорошо
puts ‘foobar’
puts ‘foo’ puts ‘bar’
puts ‘foo’, ‘bar’ # это частное правило для
puts
``` -
Используйте преимущественно однострочный формат для определений классов с пустым телом. [ссылка]
```Ruby # плохо class FooError < StandardError end
сносно
class FooError < StandardError; end
хорошо
FooError = Class.new(StandardError) ```
-
Избегайте однострочных методов. И хотя они достаточно популярны в среде программистов, существует множество неприятных мелочей, связанных с синтаксисом их определения, которые делают применение таких методов нежелательным. В любом случае однострочные методы не должны содержать больше одного выражения. [ссылка]
```Ruby # плохо def too_much; something; something_else; end
сносно (обратите внимание, что первая
;
обязательна)def no_braces_method; body end
сносно (обратите внимание, что вторая
;
опциональна)def no_braces_method; body; end
сносно (корректный синтаксис, но отсутствие
;
создает трудности при прочтении)def some_method() body end
хорошо
def some_method body end ```
Одним исключение в этом правиле являются методы с пустым телом.
Ruby # хорошо def no_op; end
-
Вставляйте пробелы вокруг операторов, после запятых, двоеточий и точек с запятыми, вокруг
{
и перед}
. Пробелы (по большей части) игнорируются интерпретатором Руби, но их правильное использование является ключом к написанию легко читаемого кода. [ссылка]Ruby sum = 1 + 2 a, b = 1, 2 [1, 2, 3].each { |e| puts e } class FooError < StandardError; end
Единственным исключением для операторов является оператор степени:
```Ruby # плохо e = M * c ** 2
хорошо
e = M * c**2 ```
{
и}
заслуживают некоторого пояснения, так как они используются для блоков и для литералов хешей, а также для включаемых в строки выражений.Для литералов хешей два стиля являются общепринятыми:
```Ruby # хорошо (пробел после { и до }) { one: 1, two: 2 }
хорошо (пробелы отсутствуют после { и перед })
{one: 1, two: 2} ```
Первый вариант несколько проще для чтения и, по всей вероятности, более распространен среди членов сообщества программистов на Руби. Второй вариант имеет преимущество в том, что создается видимое различие между блоками и литералами хешей. Какой бы стиль вы ни выбрали, применяйте его единообразно.
В случае включаемых в строки выражений существует также два приемлемых варианта:
```Ruby # хорошо (без пробелов) “string#{expr}”
ok (наверное, читается лучше)
“string#{ expr }” ```
Стиль первого примера гораздо более популярен, и вам следует в целом придерживаться именно его. С другой стороны, второй пример, вероятно, будет прочитан легче. Как и в случает с хешами, выберите один стиль и применяется его последовательно.
-
Не используйте пробел после
(
,[
или перед]
,)
.[ссылка]Ruby some(arg).other [1, 2, 3].size
-
Не используйте пробел после
!
.[ссылка]```Ruby # плохо ! something
хорошо
!something ```
-
Записывайте литералы диапазонов без пробелов.[link]
```Ruby # плохо 1 .. 3 ‘a’ … ‘z’
# хорошо 1..3 ‘a’..’z’ ```
-
Делайте отступ для
when
таким же, как и дляcase
. Я знаю, что многие не согласятся с этим, то этот стиль предписывается как “Языком программирования Ruby”, так и “Programming Ruby”.[ссылка]```Ruby # плохо case when song.name == ‘Misty’ puts ‘Not again!’ when song.duration > 120 puts ‘Too long!’ when Time.now.hour > 21 puts “It’s too late” else song.play end
хорошо
case when song.name == ‘Misty’ puts ‘Not again!’ when song.duration > 120 puts ‘Too long!’ when Time.now.hour > 21 puts “It’s too late” else song.play end ```
-
Присваивая результат условного выражения переменной, сохраняйте соответствие уровней отступа.[ссылка]
```Ruby # плохо (слишком запутано) kind = case year when 1850..1889 then ‘Blues’ when 1890..1909 then ‘Ragtime’ when 1910..1929 then ‘New Orleans Jazz’ when 1930..1939 then ‘Swing’ when 1940..1950 then ‘Bebop’ else ‘Jazz’ end
result = if some_cond calc_something else calc_something_else end
хорошо (намерения очевидны)
kind = case year when 1850..1889 then ‘Blues’ when 1890..1909 then ‘Ragtime’ when 1910..1929 then ‘New Orleans Jazz’ when 1930..1939 then ‘Swing’ when 1940..1950 then ‘Bebop’ else ‘Jazz’ end
result = if some_cond calc_something else calc_something_else end
хорошо (и не так расточительно)
kind = case year when 1850..1889 then ‘Blues’ when 1890..1909 then ‘Ragtime’ when 1910..1929 then ‘New Orleans Jazz’ when 1930..1939 then ‘Swing’ when 1940..1950 then ‘Bebop’ else ‘Jazz’ end
result = if some_cond calc_something else calc_something_else end ```
-
Используйте пустые строки для разделения определений методов и выделения логических частей определений внутри них.[ссылка]
```Ruby def some_method data = initialize(options)
data.manipulate!
data.result end
def some_method result end ```
-
Избегайте запятых после последнего параметра в вызове метода, особенно когда параметры расположены в отдельных строках.[ссылка]
```Ruby # плохо, хотя проще перемещать/добавлять/удалять строки some_method( size, count, color, )
плохо
some_method(size, count, color, )
хорошо
some_method(size, count, color) ```
-
Вставляйте пробелы вокруг оператора присваивания
=
, когда назначаете параметрам метода значения по умолчанию:[ссылка]```Ruby # плохо def some_method(arg1=:default, arg2=nil, arg3=[]) # do something… end
хорошо
def some_method(arg1 = :default, arg2 = nil, arg3 = []) # do something… end ```
Хотя в некоторых книгах по Ruby рекомендуют первый стиль, второй гораздо более нагляден.
-
Не используйте символ продления строк
\
везде, где можно обойтись без него. Практически не используйте его нигде, кроме как при конкатенации строк.[ссылка]```Ruby # плохо result = 1 - \ 2
возможно (но ужасно)
result = 1 \ - 2
long_string = ‘First part of the long string’ \ ‘ and second part of the long string’ ```
-
Используйте единый стиль многострочных последовательных цепочек вызовов методов. В сообществе Руби популярны два взаимоисключающих стиля их оформления: с точкой в начале (вариант A) и с точкой в конце (вариант B). [ссылка]
-
A Когда продолжаете цепочку вызовов методов на следующую строку, начинайте её с точки.
```Ruby # плохо - нужно посмотреть на предыдущую строку, чтобы понять # смысл последующей one.two.three. four
хорошо - сразу ясно, что происходит во второй строке
one.two.three .four ```
-
B Соответственно, наоборот, когда продолжаете цепочку вызовов на следующей строке, завершайте строку точкой
.
, давая понять, что продолжение выражения следует```Ruby # плохо - чтобы понять, что выражение не окончено, необходимо # посмотреть на следующую строку. one.two.three .four
хорошо - сразу видно, что выражение будет продолжено на
# следующей строке one.two.three. four ```
-
C аргументами за и против обоих стилей можно ознакомиться в дискуссии здесь.
-
Выравнивайте параметры вызова метода, если вызов занимает более одной строки. Если выравнивание невозможно из-за ограничений на длину строки, то используйте одинарный отступ. [ссылка]
```Ruby # первоначальный вариант (строка слишком длинная) def send_mail(source) Mailer.deliver(to: ‘bob@example.com’, from: ‘us@example.com’, subject: ‘Important message’, body: source.text) end
плохо (двойной отступ)
def send_mail(source) Mailer.deliver( to: ‘bob@example.com’, from: ‘us@example.com’, subject: ‘Important message’, body: source.text) end
хорошо
def send_mail(source) Mailer.deliver(to: ‘bob@example.com’, from: ‘us@example.com’, subject: ‘Important message’, body: source.text) end
хорошо (одинарный отступ)
def send_mail(source) Mailer.deliver( to: ‘bob@example.com’, from: ‘us@example.com’, subject: ‘Important message’, body: source.text ) end ```
-
Выравнивайте элементы литералов массива, если они занимают несколько строк.[ссылка]
```Ruby # плохо menu_item = [‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Baked beans’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’]
хорошо
menu_item = [ ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Baked beans’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’ ]
хорошо
menu_item = [‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Baked beans’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’, ‘Spam’] ```
-
Добавляйте символ подеркивания в большие числовые константы для улучшения их восприятия. [ссылка]
```Ruby # плохо (Сколько тут нолей?) num = 1000000
хорошо (число воспринимается гораздо легче)
num = 1_000_000 ```
-
Используйте устоявшиеся правила RDoc для описания интерфейсов. Не отделяйте блок комментария от начала определения метода
def
пустой строкой.[ссылка] -
Ограничивайте длину строк 80-ю символами.[ссылка]
-
Не оставляйте пробелы в конце строки. [ссылка]
-
Завершайте каждый файл переводом строки. [ссылка]
-
Не пользуйтесь блочными комментариями. Их нельзя разместить на необходимом уровне отступа. К тому же их сложнее воспринимать, чем обычные комментарии.[ссылка]
```Ruby # плохо =begin строка комментария еще одна строка комментария =end
хорошо
# строка комментария # другая строка комментария ```
Синтаксис
-
Используйте
::
только для обращения к константам (в том числе к классам и модулям) и конструкторам класса (например,Array()
orNokogiri::HTML()
). Никогда не используйте::
для обычного вызова методов.[ссылка]```Ruby # плохо SomeClass::some_method some_object::some_method
хорошо
SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONST SomeModule::SomeClass() ```
-
Используйте
def
со скобками, когда у метода есть аргументы. Опускайте скобки, когда метод не принимает аргументов. [ссылка]```Ruby # плохо def some_method() # body omitted end
# хорошо def some_method # body omitted end
# плохо def some_method_with_arguments arg1, arg2 # body omitted end
# хорошо def some_method_with_arguments(arg1, arg2) # body omitted end ```
-
Используйте оператор
for
только в случаях, когда вы точно знаете, зачем вы это делаете. В подавляющем большинстве остальных случаев стоит применять итераторы. Операторfor
реализуюется при помощиeach
(таким образом вы добавляете еще один уровень абстракции), но с некоторыми отличиями: не создается отдельная область видимости (в отличии отeach
) и переменные, объявленные в телеfor
, будут видны за пределами блока. [ссылка]```Ruby arr = [1, 2, 3]
плохо
for elem in arr do puts elem end
Учтите, elem доступен за пределами цикла
elem #=> 3
хорошо
arr.each { |elem| puts elem }
elem недоступен за пределами блока each
elem #=> NameError: undefined local variable or method
elem'
`` -
Не используйте
then
для условийif/unless
, объявленных на нескольких строках.[ссылка]```Ruby # плохо if some_condition then # некоторое действие end
хорошо
if some_condition # некоторое действие end ```
-
Всегда записывайте условие для
if/unless
на той же строке, что содержитif/then
в многострочном условии. [ссылка]```Ruby # плохо if some_condition do_something do_something_else end
хорошо
if some_condition do_something do_something_else end ```
-
Предпочитайте тернарный оператор (
?:
) конструкциям сif/then/else/end
. Он используется чаще и по определению более краток.[ссылка]```Ruby # плохо result = if some_condition then something else something_else end
хорошо
result = some_condition ? something : something_else ```
-
Используйте только одно выражение в каждой ветви тернарного оператора. Отсюда следует, что лучше избегать вложенных тернарных операторов. При возникновении такой необходимости применяйте конструкции с
if/else
.[ссылка]```Ruby # плохо some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
хорошо
if some_condition nested_condition ? nested_something : nested_something_else else something_else end ```
-
Не используйте
if x: ...
— в Руби 1.9 эту синтаксическую конструкцию удалии, используйте вместо нее тернарные операторы.[ссылка]```Ruby # плохо result = if some_condition: something else something_else end
хорошо
result = some_condition ? something : something_else ```
-
Не используйте точку с запятой в
if x; ...
. Применяйте тернарные операторы.[ссылка] -
Применяйте на пользу тот факт, что
if
andcase
являются выражениями, возвращающими результирующие значения. [ссылка]```Ruby # плохо if condition result = x else result = y end
хорошо
result = if condition x else y end ```
-
Применяйте
when x then ...
для однострочных выражений. Вариант записиwhen x: ...
был удален, начиная с Руби 1.9. [ссылка] -
Не используйте
when x; ...
по аналогии с предыдущим правилом.[ссылка] -
Используйте
!
вместоnot
. [ссылка]```Ruby # плохо (необходимы скобки из-за неоднозначности приоритетов операторов) x = (not something)
хорошо
x = !something ```
-
Не используйте
!!
. [ссылка]```Ruby # плохо x = ‘test’ # неявная проверка на nil if !!x # некоторое выражение end
x = false # двойное отрицание бессмысленно для булевых значений !!x # => false
хорошо
x = ‘test’ unless x.nil? # некоторое выражение end ```
-
Ключевые слова
and
иor
следует забыть. Они не несут дополнительной пользы. Всегда используйте&&
и||
вместо них. [ссылка]```Ruby # плохо # булево выражение if some_condition and some_other_condition do_something end
управление потоком исполнения
document.saved? or document.save!
хорошо
# булево выражение if some_condition && some_other_condition do_something end
управление потоком исполнения
document.saved? || document.save! ```
-
Избегайте многострочных тернарных операторов
?:
Используйте вместо нихif/unless
.[ссылка] -
Для однострочных выражений по возможности модификатор
if/unless
. Другим хорошим вариантом являются операторы управления потоком исполнения&&/||
.[ссылка]```Ruby # плохо if some_condition do_something end
хорошо
do_something if some_condition
еще хороший вариант
some_condition && do_something ```
-
Избегайте
if/unless
в конце нетривиального многострочного блока. [ссылка]```Ruby # плохо 10.times do # multi-line body omitted end if some_condition
хорошо
if some_condition 10.times do # multi-line body omitted end end ```
-
Используйте
unless
вместоif
для отрицательных условий (или||
для управления потоком исполнения). [ссылка]```Ruby # плохо do_something if !some_condition
плохо
do_something if not some_condition
хорошо
do_something unless some_condition
тоже хорошо
some_condition || do_something ```
-
Не используйте
unless
вместе сelse
. Перепишите такие выражение с положительной проверкой. [ссылка]```Ruby # плохо unless success? puts ‘failure’ else puts ‘success’ end
хорошо
if success? puts ‘success’ else puts ‘failure’ end ```
-
Не используйте скобки для ограничения условных выражений в
if/unless/while/until
.[ссылка]```Ruby # плохо if (x > 10) # код опущен для краткости end
хорошо
if x > 10 # код опущен для краткости end ``` Однако в этом правиле есть некоторые исключения, например, надежные присвоения в условных выражениях.
-
Не используйте
while/until УСЛОВИЕ do
для многострочных циклов сwhile/until
. [ссылка]```Ruby # плохо while x > 5 do # код опущен для краткости end
until x > 5 do # код опущен для краткости end
хорошо
while x > 5 # код опущен для краткости end
until x > 5 # код опущен для краткости end ```
-
Используйте
while/until
для однострочный выражений.[ссылка]```Ruby # плохо while some_condition do_something end
хорошо
do_something while some_condition ```
-
Используйте
until
вместоwhile
для условий на отрицания.[ссылка]```Ruby # плохо do_something while !some_condition
хорошо
do_something until some_condition ```
-
Используйте
Kernel#loop
вместоwhile/until
для бесконечного цикла.[ссылка]```Ruby # плохо while true do_something end
until false do_something end
# хорошо loop do do_something end ```
-
Используйте
Kernel#loop
сbreak
вместоbegin/end/until
илиbegin/end/while
для циклов с постусловием. [ссылка]```Ruby # плохо begin puts val val += 1 end while val < 0
хорошо
loop do puts val val += 1 break unless val < 0 end ```
-
Не используйте скобки при вызове методов, являющихся частью таких DSL, как Rake, Rails, RSpec, методов, имеющих статус ключевого слова, например,
attr_reader
,puts
и при вызове аксессоров. Используйте скобки при вызове прочих методов. [ссылка]```Ruby class Person attr_reader :name, :age
# omitted end
temperance = Person.new(‘Temperance’, 30) temperance.name
puts temperance.age
x = Math.sin(y) array.delete(e)
bowling.score.should == 0 ```
-
Не используйте фигурные скобки для ограничения хешей, передаваемых методу.[ссылка]
```Ruby # плохо user.set({ name: ‘John’, age: 45, permissions: { read: true } })
хорошо
user.set(name: ‘John’, age: 45, permissions: { read: true }) ```
-
Не используйте фигурные скобки для ограничения хешей, передаваемых методу, и скобки вокруг параметров для методов, являющихся частью DSL.[ссылка]
```Ruby class Person < ActiveRecord::Base # плохо validates(:name, { presence: true, length: { within: 1..10 } })
# хорошо validates :name, presence: true, length: { within: 1..10 } end ```
-
Опускайте скобки при вызове метода без параметров. [ссылка]
```Ruby # плохо Kernel.exit!() 2.even?() fork() ‘test’.upcase()
хорошо
Kernel.exit! 2.even? fork ‘test’.upcase ```
-
Используйте преимущественно
{...}
в случае одностроных блоков, аdo...end
в случае многострочных блоков (многострочные последовательности вызовов методов всегда выглядят ужасно). Старайтесь применятьdo...end
для логических операций и определений методов (например, для Rakefile и некоторых DSL). Не используйтеdo...end
в цепочках вызовов. [ссылка]```Ruby names = [‘Bozhidar’, ‘Steve’, ‘Sarah’]
плохо
names.each do |name| puts name end
хорошо
names.each { |name| puts name }
плохо
names.select do |name| name.start_with?(‘S’) end.map { |name| name.upcase }
хорошо
names.select { |name| name.start_with?(‘S’) }.map { |name| name.upcase } ```
Некоторые из нас поспорят, что многострочные последовательные вызовы с блоками при использовании {…} выглядят неплохо, но тогда стоит себя спросить, а читается ли такой код и не стоит ли выделить эти блоки в отдельные специальные методы.
-
Consider using explicit block argument to avoid writing block literal that just passes its arguments to another block. Beware of the performance impact, though, as the block gets converted to a Proc. [ссылка]
```Ruby require ‘tempfile’
плохо
def with_tmp_dir Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments end end
хорошо
def with_tmp_dir(&block) Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir, &block) end end
with_tmp_dir do |dir| puts “dir is accessible as a parameter and pwd is set: #{dir}” end ```
-
Avoid
return
where not required for flow of control.[ссылка]```Ruby # плохо def some_method(some_arr) return some_arr.size end
хорошо
def some_method(some_arr) some_arr.size end ```
-
Avoid
self
where not required. (It is only required when calling a self write accessor.) [ссылка]```Ruby # плохо def ready? if self.last_reviewed_at > self.last_updated_at self.worker.update(self.content, self.options) self.status = :in_progress end self.status == :verified end
хорошо
def ready? if last_reviewed_at > last_updated_at worker.update(content, options) self.status = :in_progress end status == :verified end ```
-
As a corollary, avoid shadowing methods with local variables unless they are both equivalent.[ссылка]
```Ruby class Foo attr_accessor :options
# ok def initialize(options) self.options = options # both options and self.options are equivalent here end
# плохо def do_something(options = {}) unless options[:when] == :later output(self.options[:message]) end end
# хорошо def do_something(params = {}) unless params[:when] == :later output(options[:message]) end end end ```
-
Don’t use the return value of
=
(an assignment) in conditional expressions unless the assignment is wrapped in parentheses. This is a fairly popular idiom among Rubyists that’s sometimes referred to as.[ссылка]```Ruby # плохо (+ a warning) if v = array.grep(/foo/) do_something(v) … end
хорошо (MRI would still complain, but RuboCop won’t)
if (v = array.grep(/foo/)) do_something(v) … end
хорошо
v = array.grep(/foo/) if v do_something(v) … end ```
-
Use shorthand self assignment operators whenever applicable.[ссылка]
```Ruby # плохо x = x + y x = x * y x = x**y x = x / y x = x || y x = x && y
хорошо
x += y x *= y x **= y x /= y x ||= y x &&= y ```
-
Use
||=
to initialize variables only if they’re not already initialized.[ссылка]```Ruby # плохо name = name ? name : ‘Bozhidar’
плохо
name = ‘Bozhidar’ unless name
хорошо - set name to Bozhidar, only if it’s nil or false
name ||= ‘Bozhidar’ ```
-
Don’t use
||=
to initialize boolean variables. (Consider what would happen if the current value happened to befalse
.) [ссылка]```Ruby # плохо - would set enabled to true even if it was false enabled ||= true
хорошо
enabled = true if enabled.nil? ```
-
Use
&&=
to preprocess variables that may or may not exist. Using&&=
will change the value only if it exists, removing the need to check its existence withif
.[ссылка]```Ruby # плохо if something something = something.downcase end
плохо
something = something ? something.downcase : nil
сносно
something = something.downcase if something
хорошо
something = something && something.downcase
еще лучше
something &&= something.downcase ```
-
Avoid explicit use of the case equality operator
===
. As its name implies it is meant to be used implicitly bycase
expressions and outside of them it yields some pretty confusing code. [ссылка]```Ruby # плохо Array === something (1..100) === 7 /something/ === some_string
хорошо
something.is_a?(Array) (1..100).include?(7) some_string =~ /something/ ```
-
Avoid using Perl-style special variables (like
$:
,$;
, etc. ). They are quite cryptic and their use in anything but one-liner scripts is discouraged. Use the human-friendly aliases provided by theEnglish
library. [ссылка]```Ruby # плохо $:.unshift File.dirname(FILE)
хорошо
require ‘English’ $LOAD_PATH.unshift File.dirname(FILE) ```
-
Never put a space between a method name and the opening parenthesis.[ссылка]
```Ruby # плохо f (3 + 2) + 1
хорошо
f(3 + 2) + 1 ```
-
If the first argument to a method begins with an open parenthesis, always use parentheses in the method invocation. For example, write
f((3 + 2) + 1)
. [ссылка] -
Always run the Ruby interpreter with the
-w
option so it will warn you if you forget either of the rules above! [ссылка] -
Use the new lambda literal syntax for single line body blocks. Use the
lambda
method for multi-line blocks. [ссылка]```Ruby # плохо l = lambda { |a, b| a + b } l.call(1, 2)
correct, but looks extremely awkward
l = ->(a, b) do tmp = a * 7 tmp * b / 50 end
хорошо
l = ->(a, b) { a + b } l.call(1, 2)
l = lambda do |a, b| tmp = a * 7 tmp * b / 50 end ```
-
Prefer
proc
overProc.new
.[ссылка]```Ruby # плохо p = Proc.new { |n| puts n }
хорошо
p = proc { |n| puts n } ```
-
Prefer
proc.call()
overproc[]
orproc.()
for both lambdas and procs.[ссылка]```Ruby # плохо - looks similar to Enumeration access l = ->(v) { puts v } l[1]
also плохо - uncommon syntax
l = ->(v) { puts v } l.(1)
хорошо
l = ->(v) { puts v } l.call(1) ```
-
Prefix with
_
unused block parameters and local variables. It’s also acceptable to use just_
(although it’s a bit less descriptive). This convention is recognized by the Ruby interpreter and tools like RuboCop and will suppress their unused variable warnings. [ссылка]```Ruby # плохо result = hash.map { |k, v| v + 1 }
def something(x) unused_var, used_var = something_else(x) # … end
хорошо
result = hash.map { |_k, v| v + 1 }
def something(x) _unused_var, used_var = something_else(x) # … end
хорошо
result = hash.map { |_, v| v + 1 }
def something(x) _, used_var = something_else(x) # … end ```
-
Используйте переменные
$stdout/$stderr/$stdin
вместо константSTDOUT/STDERR/STDIN
.STDOUT/STDERR/STDIN
являются константами, поэтому при их переопределении (вы это можете сделать, например, для перенаправления ввода-вывода) интерпретатор будет выдавать предупреждения. [ссылка] -
Используйте
warn
вместо$stderr.puts
. Это не только короче, но и позволит вам скрыть все предупреждения, если вам это понадобится (для этого задайте уроверь предупреждений равный0
при помощи опции-W0
). [ссылка] -
Используйте
sprintf
и его алиасformat
вместо довольно запутанного методаString#%
.[ссылка]```Ruby # плохо ‘%d %d’ % [20, 10] # => ‘20 10’
хорошо
sprintf(‘%d %d’, 20, 10) # => ‘20 10’
хорошо
sprintf(‘%{first} %{second}’, first: 20, second: 10) # => ‘20 10’
format(‘%d %d’, 20, 10) # => ‘20 10’
хорошо
format(‘%{first} %{second}’, first: 20, second: 10) # => ‘20 10’ ```
-
Favor the use of
Array#join
over the fairly crypticArray#*
with a string argument. [ссылка]```Ruby # плохо %w(one two three) * ‘, ‘ # => ‘one, two, three’
хорошо
%w(one two three).join(‘, ‘) # => ‘one, two, three’ ```
-
Use
[*var]
orArray()
instead of explicitArray
check, when dealing with a variable you want to treat as an Array, but you’re not certain it’s an array. [ссылка]```Ruby # плохо paths = [paths] unless paths.is_a? Array paths.each { |path| do_something(path) }
хорошо
[*paths].each { |path| do_something(path) }
хорошо (and a bit more readable)
Array(paths).each { |path| do_something(path) } ```
-
Use ranges or
Comparable#between?
instead of complex comparison logic when possible.[ссылка]```Ruby # плохо do_something if x >= 1000 && x <= 2000
хорошо
do_something if (1000..2000).include?(x)
хорошо
do_something if x.between?(1000, 2000) ```
-
Favor the use of predicate methods to explicit comparisons with
==
. Numeric comparisons are OK. [ссылка]```Ruby # плохо if x % 2 == 0 end
if x % 2 == 1 end
if x == nil end
хорошо
if x.even? end
if x.odd? end
if x.nil? end
if x.zero? end
if x == 0 end ```
-
Don’t do explicit non-
nil
checks unless you’re dealing with boolean values.[ссылка]```Ruby # плохо do_something if !something.nil? do_something if something != nil
# хорошо do_something if something
# хорошо - dealing with a boolean def value_set? !@some_boolean.nil? end ```
-
Avoid the use of
BEGIN
blocks.[ссылка] -
Never use
END
blocks. UseKernel#at_exit
instead.[ссылка]```Ruby # плохо END { puts ‘Goodbye!’ }
хорошо
at_exit { puts ‘Goodbye!’ } ```
-
Avoid the use of flip-flops.[ссылка]
-
Avoid use of nested conditionals for flow of control.[ссылка]
Prefer a guard clause when you can assert invalid data. A guard clause is a conditional statement at the top of a function that bails out as soon as it can.
```Ruby # плохо def compute_thing(thing) if thing[:foo] update_with_bar(thing) if thing[:foo][:bar] partial_compute(thing) else re_compute(thing) end end end
хорошо
def compute_thing(thing) return unless thing[:foo] update_with_bar(thing[:foo]) return re_compute(thing) unless thing[:foo][:bar] partial_compute(thing) end ```
Prefer
next
in loops instead of conditional blocks.```Ruby # плохо [0, 1, 2, 3].each do |item| if item > 1 puts item end end
хорошо
[0, 1, 2, 3].each do |item| next unless item > 1 puts item end ```
Наименование
The only real difficulties in programming are cache invalidation and naming things.
– Phil Karlton
-
Name identifiers in English.[ссылка]
```Ruby # плохо - identifier using non-ascii characters заплата = 1_000
плохо - identifier is a Bulgarian word, written with Latin letters (instead of Cyrillic)
zaplata = 1_000
хорошо
salary = 1_000 ```
-
Use
snake_case
for symbols, methods and variables.[ссылка]```Ruby # плохо :’some symbol’ :SomeSymbol :someSymbol
someVar = 5
def someMethod … end
def SomeMethod … end
хорошо
:some_symbol
def some_method … end ```
-
Use
CamelCase
for classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.) [ссылка]```Ruby # плохо class Someclass … end
class Some_Class … end
class SomeXml … end
хорошо
class SomeClass … end
class SomeXML … end ```
-
Use
snake_case
for naming files, e.g.hello_world.rb
.[ссылка] -
Use
snake_case
for naming directories, e.g.lib/hello_world/hello_world.rb
.[ссылка] -
Aim to have just a single class/module per source file. Name the file name as the class/module, but replacing CamelCase with snake_case. [ссылка]
-
Используйте
SCREAMING_SNAKE_CASE
для констант.[ссылка]```Ruby # плохо SomeConst = 5
хорошо
SOME_CONST = 5 ```
-
The names of predicate methods (methods that return a boolean value) should end in a question mark. (i.e.
Array#empty?
). Methods that don’t return a boolean, shouldn’t end in a question mark. [ссылка] -
The names of potentially dangerous methods (i.e. methods that modify
self
or the arguments,exit!
(doesn’t run the finalizers likeexit
does), etc.) should end with an exclamation mark if there exists a safe version of that dangerous method. [ссылка]```Ruby # плохо - there is no matching ‘safe’ method class Person def update! end end
хорошо
class Person def update end end
хорошо
class Person def update! end
def update end end ```
-
Define the non-bang (safe) method in terms of the bang (dangerous) one if possible. [ссылка]
```Ruby class Array def flatten_once! res = []
each do |e| [*e].each { |f| res << f } end replace(res) end
def flatten_once dup.flatten_once! end end ```
-
When using
reduce
with short blocks, name the arguments|a, e|
(accumulator, element). [ссылка] -
When defining binary operators, name the argument
other
(<<
and[]
are exceptions to the rule, since their semantics are different). [ссылка]Ruby def +(other) # body omitted end
-
Prefer
map
overcollect
,find
overdetect
,select
overfind_all
,reduce
overinject
andsize
overlength
. This is not a hard requirement; if the use of the alias enhances readability, it’s ok to use it. The rhyming methods are inherited from Smalltalk and are not common in other programming languages. The reason the use ofselect
is encouraged overfind_all
is that it goes together nicely withreject
and its name is pretty self-explanatory. [ссылка] -
Don’t use
count
as a substitute forsize
. ForEnumerable
objects other thanArray
it will iterate the entire collection in order to determine its size. [ссылка]```Ruby # плохо some_hash.count
хорошо
some_hash.size ```
-
Use
flat_map
instead ofmap
+flatten
. This does not apply for arrays with a depth greater than 2, i.e. ifusers.first.songs == ['a', ['b','c']]
, then usemap + flatten
rather thanflat_map
.flat_map
flattens the array by 1, whereasflatten
flattens it all the way. [ссылка]```Ruby # плохо all_songs = users.map(&:songs).flatten.uniq
хорошо
all_songs = users.flat_map(&:songs).uniq ```
-
Use
reverse_each
instead ofreverse.each
.reverse_each
doesn’t do a new array allocation and that’s a good thing. [ссылка]```Ruby # плохо array.reverse.each { … }
хорошо
array.reverse_each { … } ```
Комментарии
Good code is its own best documentation. As you’re about to add a comment, ask yourself, “How can I improve the code so that this comment isn’t needed?” Improve the code and then document it to make it even clearer.
– Steve McConnell
-
Пишите говорящий за себя код и смело пропускайте все остальное в этом разделе. Серьезно! [ссылка]
-
Пишите комментарии по-английски. [ссылка]
-
Используйте один пробел между символом
#
в начале и текстом самого комментария.[ссылка] -
Комментарии длиной больше одного слова должны оформляться в виде законченных предложений (с большой буквы и со знаками препинания). Разделяйте предложения одним пробелом. [ссылка]
-
Избегайте избыточного комментирования. [ссылка]
Ruby # плохо counter += 1 # Увеличивает счетчик на единицу.
-
Актуализируйте существующие комментарии. Устаревший комментарий гораздо хуже отсутствующего комментария. [ссылка]
Good code is like a good joke - it needs no explanation.
– Russ Olsen
- Не пишите комментарии для объяснения плохого кода. Перепишите код, чтобы он говорил сам за себя. [ссылка]
Do or do not - there is no try.
– Yoda
Пометки в комментариях
-
Обычно пометки следует записывать на предшествующей описываемому коду строке.[ссылка]
-
Пометка отделяется двоеточием и пробелом, потом следует примечание, описывающее проблему.[ссылка]
-
Если для описания проблемы потребуются несколько строк, то на каждой последующей строке следует сделать отступ в три пробела после символа
#
.[ссылка]Ruby def bar # FIXME: This has crashed occasionally since v3.2.1. It may # be related to the BarBazUtil upgrade. baz(:quux) end
-
В тех случаях, когда проблема настолько очевидна, что любые описания покажутся избыточными, пометки можно поставить в конце вызывающей проблему строки. Однако такое применение должно быть исключением, а не правилом.[ссылка]
Ruby def bar sleep 100 # OPTIMIZE end
-
Используйте
TODO
, чтобы пометить отсутствующие возможности или функционал, которые должны быть добавлены позже.[ссылка] -
Используйте
FIXME
, чтобы пометить код с ошибками, который должен быть исправлен.[ссылка] -
Используйте
OPTIMIZE
, чтобы пометить медленный или неэффективный код, который может вызвать проблемы с производительностью. [ссылка] -
Используйте
HACK
, чтобы пометить кода “с душком”, который должен быть переработан и использует сомнительные практики разработки. [ссылка] -
Используйте
REVIEW
, чтобы пометить все, что должно быть проверено на работоспособность. Например,REVIEW: Are we sure this is how the client does X currently?
.[ссылка] -
Используйте персональные пометки, если это подходит по месту, но обязательно опишите их смысл в файле
README
(или похожем) для вашего проекта.[ссылка]
Классы и модули
-
Структурируйте ваши классы единообразно.[ссылка]
```Ruby class Person # extend и include в начале extend SomeModule include AnotherModule
# вложенные классы CustomErrorKlass = Class.new(StandardError)
# после этого константы SOME_CONSTANT = 20
# после этого макросы методов доступа к атрибутам attr_reader :name
# и все прочие макросы (если имеются) validates :name
# public class methods are next in line def self.some_method end
# followed by public instance methods def some_method end
# protected and private methods are grouped near the end protected
def some_protected_method end
private
def some_private_method end end ```
-
Don’t nest multi line classes within classes. Try to have such nested classes each in their own file in a folder named like the containing class. [ссылка]
```Ruby # плохо
foo.rb
class Foo class Bar # 30 methods inside end
class Car # 20 methods inside end
# 30 methods inside end
хорошо
foo.rb
class Foo # 30 methods inside end
foo/bar.rb
class Foo class Bar # 30 methods inside end end
foo/car.rb
class Foo class Car # 20 methods inside end end ```
-
Prefer modules to classes with only class methods. Classes should be used only when it makes sense to create instances out of them. [ссылка]
```Ruby # плохо class SomeClass def self.some_method # body omitted end
def self.some_other_method end end
хорошо
module SomeModule module_function
def some_method # body omitted end
def some_other_method end end ```
-
Favor the use of
module_function
overextend self
when you want to turn a module’s instance methods into class methods. [ссылка]```Ruby # плохо module Utilities extend self
def parse_something(string) # do stuff here end
def other_utility_method(number, string) # do some more stuff end end
хорошо
module Utilities module_function
def parse_something(string) # do stuff here end
def other_utility_method(number, string) # do some more stuff end end ```
-
When designing class hierarchies make sure that they conform to the Liskov Substitution Principle. [ссылка]
-
Always supply a proper
to_s
method for classes that represent domain objects. [ссылка]```Ruby class Person attr_reader :first_name, :last_name
def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end
def to_s “#{@first_name} #{@last_name}” end end ```
-
Use the
attr
family of functions to define trivial accessors or mutators.[ссылка]```Ruby # плохо class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end
def first_name @first_name end
def last_name @last_name end end
хорошо
class Person attr_reader :first_name, :last_name
def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end ```
-
Avoid the use of
attr
. Useattr_reader
andattr_accessor
instead.[ссылка]```Ruby # плохо - creates a single attribute accessor (deprecated in 1.9) attr :something, true attr :one, :two, :three # behaves as attr_reader
хорошо
attr_accessor :something attr_reader :one, :two, :three ```
-
Consider using
Struct.new
, which defines the trivial accessors, constructor and comparison operators for you. [ссылка]```Ruby # хорошо class Person attr_accessor :first_name, :last_name
def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end
better
Person = Struct.new(:first_name, :last_name) do end ````
-
Don’t extend a
Struct.new
- it already is a new class. Extending it introduces a superfluous class level and may also introduce weird errors if the file is required multiple times. [ссылка] -
Consider adding factory methods to provide additional sensible ways to create instances of a particular class. [ссылка]
Ruby class Person def self.create(options_hash) # body omitted end end
-
Prefer duck-typing over inheritance.[ссылка]
```Ruby # плохо class Animal # abstract method def speak end end
extend superclass
class Duck < Animal def speak puts ‘Quack! Quack’ end end
extend superclass
class Dog < Animal def speak puts ‘Bau! Bau!’ end end
хорошо
class Duck def speak puts ‘Quack! Quack’ end end
class Dog def speak puts ‘Bau! Bau!’ end end ```
-
Avoid the usage of class (
@@
) variables due to their “nasty” behavior in inheritance.[ссылка]```Ruby class Parent @@class_var = ‘parent’
def self.print_class_var puts @@class_var end end
class Child < Parent @@class_var = ‘child’ end
Parent.print_class_var # => will print “child” ```
As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.
-
Assign proper visibility levels to methods (
private
,protected
) in accordance with their intended usage. Don’t go off leaving everythingpublic
(which is the default). After all we’re coding in Ruby now, not in Python. [ссылка] -
Indent the
public
,protected
, andprivate
methods as much the method definitions they apply to. Leave one blank line above the visibility modifier and one blank line below in order to emphasize that it applies to all methods below it. [ссылка]```Ruby class SomeClass def public_method # … end
private
def private_method # … end
def another_private_method # … end end ```
-
Use
def self.method
to define singleton methods. This makes the code easier to refactor since the class name is not repeated. [ссылка]```Ruby class TestClass # плохо def TestClass.some_method # body omitted end
# хорошо def self.some_other_method # body omitted end
# Also possible and convenient when you # have to define many singleton methods. class « self def first_method # body omitted end
def second_method_etc # body omitted end end end ```
Исключения
-
Signal exceptions using the
fail
method. Useraise
only when catching an exception and re-raising it (because here you’re not failing, but explicitly and purposefully raising an exception). [ссылка]Ruby begin fail 'Oops' rescue => error raise if error.message != 'Oops' end
-
Don’t specify
RuntimeError
explicitly in the two argument version offail/raise
.[ссылка]```Ruby # плохо fail RuntimeError, ‘message’
хорошо - signals a RuntimeError by default
fail ‘message’ ```
-
Prefer supplying an exception class and a message as two separate arguments to
fail/raise
, instead of an exception instance. [ссылка]```Ruby # плохо fail SomeException.new(‘message’) # Note that there is no way to do
fail SomeException.new('message'), backtrace
.хорошо
fail SomeException, ‘message’ # Consistent with
fail SomeException, 'message', backtrace
. ``` -
Never return from an
ensure
block. If you explicitly return from a method inside anensure
block, the return will take precedence over any exception being raised, and the method will return as if no exception had been raised at all. In effect, the exception will be silently thrown away. [ссылка]Ruby def foo begin fail ensure return 'very bad idea' end end
-
Используйте неявный begin где возможно.[ссылка]
```Ruby # плохо def foo begin # Основной функционал rescue # Обработка ошибок end end
хорошо
def foo # Основной функционал rescue # Обработка ошибок end ```
-
Mitigate the proliferation of
begin
blocks by using[ссылка]```Ruby # плохо begin something_that_might_fail rescue IOError # handle IOError end
begin something_else_that_might_fail rescue IOError # handle IOError end
хорошо
def with_io_error_handling yield rescue IOError # handle IOError end
with_io_error_handling { something_that_might_fail }
with_io_error_handling { something_else_that_might_fail } ```
-
Don’t suppress exceptions.[ссылка]
```Ruby # плохо begin # an exception occurs here rescue SomeError # the rescue clause does absolutely nothing end
плохо
do_something rescue nil ```
-
Avoid using
rescue
in its modifier form.[ссылка]```Ruby # плохо - this catches exceptions of StandardError class and its descendant classes read_file rescue handle_error($!)
хорошо - this catches only the exceptions of Errno::ENOENT class and its descendant classes
def foo read_file rescue Errno::ENOENT => ex handle_error(ex) end ```
-
Don’t use exceptions for flow of control.[ссылка]
```Ruby # плохо begin n / d rescue ZeroDivisionError puts ‘Cannot divide by 0!’ end
хорошо
if d.zero? puts ‘Cannot divide by 0!’ else n / d end ```
-
Avoid rescuing the
Exception
class. This will trap signals and calls toexit
, requiring you tokill -9
the process. [ссылка]```Ruby # плохо begin # calls to exit and kill signals will be caught (except kill -9) exit rescue Exception puts “you didn’t really want to exit, right?” # exception handling end
хорошо
begin # a blind rescue rescues from StandardError, not Exception as many # programmers assume. rescue => e # exception handling end
also хорошо
begin # an exception occurs here
rescue StandardError => e # exception handling end ```
-
Put more specific exceptions higher up the rescue chain, otherwise they’ll never be rescued from. [ссылка]
```Ruby # плохо begin # some code rescue Exception => e # some handling rescue StandardError => e # some handling end
хорошо
begin # some code rescue StandardError => e # some handling rescue Exception => e # some handling end ```
-
Release external resources obtained by your program in an ensure block.[ссылка]
Ruby f = File.open('testfile') begin # .. process rescue # .. handle error ensure f.close unless f.nil? end
-
Favor the use of exceptions for the standard library over introducing new exception classes. [ссылка]
Коллекции
-
Prefer literal array and hash creation notation (unless you need to pass parameters to their constructors, that is). [ссылка]
```Ruby # плохо arr = Array.new hash = Hash.new
хорошо
arr = [] hash = {} ```
-
Prefer
%w
to the literal array syntax when you need an array of words (non-empty strings without spaces and special characters in them). Apply this rule only to arrays with two or more elements. [ссылка]```Ruby # плохо STATES = [‘draft’, ‘open’, ‘closed’]
хорошо
STATES = %w(draft open closed) ```
-
Prefer
%i
to the literal array syntax when you need an array of symbols (and you don’t need to maintain Ruby 1.9 compatibility). Apply this rule only to arrays with two or more elements. [ссылка]```Ruby # плохо STATES = [:draft, :open, :closed]
хорошо
STATES = %i(draft open closed) ```
-
Avoid comma after the last item of an
Array
orHash
literal, especially when the items are not on separate lines. [ссылка]```Ruby # плохо - easier to move/add/remove items, but still not preferred VALUES = [ 1001, 2020, 3333, ]
плохо
VALUES = [1001, 2020, 3333, ]
хорошо
VALUES = [1001, 2020, 3333] ```
-
Не создавайте массивы с большими пробелами.[ссылка]
Ruby arr = [] arr[100] = 1 # получаем массив с кучей нулей
-
При доступе к первому или последнему эллементу массива используйте,
first
илиlast
нежели[0]
или[-1]
.[ссылка] -
Use
Set
instead ofArray
when dealing with unique elements.Set
implements a collection of unordered values with no duplicates. This is a hybrid ofArray
’s intuitive inter-operation facilities andHash
’s fast lookup. [ссылка] -
Prefer symbols instead of strings as hash keys.[ссылка]
```Ruby # плохо hash = { ‘one’ => 1, ‘two’ => 2, ‘three’ => 3 }
хорошо
hash = { one: 1, two: 2, three: 3 } ```
-
Avoid the use of mutable objects as hash keys.[ссылка]
-
Use the Ruby 1.9 hash literal syntax when your hash keys are symbols.[ссылка]
```Ruby # плохо hash = { :one => 1, :two => 2, :three => 3 }
хорошо
hash = { one: 1, two: 2, three: 3 } ```
-
Don’t mix the Ruby 1.9 hash syntax with hash rockets in the same hash literal. When you’ve got keys that are not symbols stick to the hash rockets syntax. [ссылка]
```Ruby # плохо { a: 1, ‘b’ => 2 }
хорошо
{ :a => 1, ‘b’ => 2 } ```
-
Use
Hash#key?
instead ofHash#has_key?
andHash#value?
instead ofHash#has_value?
. As noted here by Matz, the longer forms are considered deprecated. [ссылка]```Ruby # плохо hash.has_key?(:test) hash.has_value?(value)
хорошо
hash.key?(:test) hash.value?(value) ```
-
Use
Hash#fetch
when dealing with hash keys that should be present.[ссылка]```Ruby heroes = { batman: ‘Bruce Wayne’, superman: ‘Clark Kent’ } # плохо - if we make a mistake we might not spot it right away heroes[:batman] # => “Bruce Wayne” heroes[:supermann] # => nil
хорошо - fetch raises a KeyError making the problem obvious
heroes.fetch(:supermann) ```
-
Introduce default values for hash keys via
Hash#fetch
as opposed to using custom logic.[ссылка]```Ruby batman = { name: ‘Bruce Wayne’, is_evil: false }
плохо - if we just use || operator with falsy value we won’t get the expected result
batman[:is_evil] || true # => true
хорошо - fetch work correctly with falsy values
batman.fetch(:is_evil, true) # => false ```
-
Prefer the use of the block instead of the default value in
Hash#fetch
.[ссылка]```Ruby batman = { name: ‘Bruce Wayne’ }
плохо - if we use the default value, we eager evaluate it
# so it can slow the program down if done multiple times batman.fetch(:powers, get_batman_powers) # get_batman_powers is an expensive call
хорошо - blocks are lazy evaluated, so only triggered in case of KeyError exception
batman.fetch(:powers) { get_batman_powers } ```
-
Use
Hash#values_at
when you need to retrieve several values consecutively from a hash.[ссылка]```Ruby # плохо email = data[‘email’] nickname = data[‘nickname’]
хорошо
email, username = data.values_at(‘email’, ‘nickname’) ```
-
Rely on the fact that as of Ruby 1.9 hashes are ordered.[ссылка]
-
Never modify a collection while traversing it.[ссылка]
Строки
-
Используйте интерполяцию строк и форматные шаблоны, а не конкатенацию строк:[ссылка]
```Ruby # плохо email_with_name = user.name + ‘ <’ + user.email + ‘>’
хорошо
email_with_name = “#{user.name} <#{user.email}>”
хорошо
email_with_name = format(‘%s <%s>’, user.name, user.email) ```
-
Постарайтесь отделять код интерполяции пробелами. Это отделит код интерполяции более четко от окружающего кода. [ссылка]
Ruby "#{ user.last_name }, #{ user.first_name }"
-
Постарайтесь внедрить единообразных стиль кавычек для строчных литералов. В среде программистов на Руби есть два популярных стиля, оба из них считаются приемлемыми. Стиль А подразумевает одинарные кавычки по умолчанию, а стиль B двойные кавычки. [ссылка]
-
(A) Используйте одинарные кавычки, если вам не нужна интерполяция строк или специальные символы вроде
\t
,\n
,'
и т.д.```Ruby # плохо name = “Bozhidar”
хорошо
name = ‘Bozhidar’ ```
-
(B) Используйте двойные кавычки в ваших строчных литералах, если они не содержат
"
или экранируйте символы, которые не должны интерполироваться.```Ruby # плохо name = ‘Bozhidar’
хорошо
name = “Bozhidar” ```
Второй стиль, по некоторым мнениям, более распространен среди разработчиков на Руби. Однако в этом руководстве оформление строк следует первому правилу.
-
-
Не используйте запись для литералов алфавитных символов
?x
. Начиная с версии Руби 1.9, этот вариант записи избыточен:?x
будет интерпретироваться в виде'x'
(строка с единственным символом в ней).[ссылка]```Ruby # плохо char = ?c
хорошо
char = ‘c’ ```
-
Всегда применяйте фигурные скобки
{}
вокруг глобальных переменных и переменных экземпляров класса при интерполяции строк.[ссылка]```Ruby class Person attr_reader :first_name, :last_name
def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end
# плохо (допустимо, но вычурно) def to_s “#@first_name #@last_name” end
# хорошо def to_s “#{@first_name} #{@last_name}” end end
$global = 0 # плохо puts “$global = #$global”
хорошо
puts “$global = #{$global}” ```
-
Не используйте метод
Object#to_s
для интерполируемых объектов, он вызывается автоматически при интерполяции. [ссылка]```Ruby # плохо message = “This is the #{result.to_s}.”
хорошо
message = “This is the #{result}.” ```
-
Не применяйте метод
String#+
, когда вам нужно собрать вместе большие отрезки строк. Вместо этого используйтеString#<<
. Конкатенация изменяет экземпляр строки и всегда работает быстрее, чемString#+
, который создает целую кучу новых строковых объектов. [ссылка]```Ruby # хорошо и быстро html = ‘’ html « ‘<h1>Page title</h1>’
paragraphs.each do |paragraph| html « “<p>#{paragraph}</p>” end ```
-
При использовании многострочных HEREDOC не забывайте, что пробелы в начале строк тоже являются частью создаваемой строки. Примером хорошего стиля является применение техник, основывающихся на ограничителях, для удаления ненужных пробелов.[ссылка]
Ruby code = <<-END.gsub(/^\s+\|/, '') |def test | some_method | other_method |end END #=> "def test\n some_method\n other_method\nend\n"
Регулярные выражения
Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems.
– Jamie Zawinski
-
Не используйте регулярные выражения, когда вам нужно просто найди в строке подстроку:
string['text']
[ссылка] -
For simple constructions you can use regexp directly through string index.[ссылка]
Ruby match = string[/regexp/] # get content of matched regexp first_group = string[/text(grp)/, 1] # get content of captured group string[/text (grp)/, 1] = 'replace' # string => 'text replace'
-
Use non-capturing groups when you don’t use captured result of parentheses.[ссылка]
Ruby /(first|second)/ # плохо /(?:first|second)/ # хорошо
-
Don’t use the cryptic Perl-legacy variables denoting last regexp group matches (
$1
,$2
, etc). UseRegexp.last_match[n]
instead. [ссылка]```Ruby /(regexp)/ =~ string …
плохо
process $1
хорошо
process Regexp.last_match[1] ```
-
Avoid using numbered groups as it can be hard to track what they contain. Named groups can be used instead. [ссылка]
```Ruby # плохо /(regexp)/ =~ string … process Regexp.last_match[1]
хорошо
/(?
regexp)/ =~ string ... process meaningful_var ``` -
Character classes have only a few special characters you should care about:
^
,-
,\
,]
, so don’t escape.
or brackets in[]
. [ссылка] -
Be careful with
^
and$
as they match start/end of line, not string endings. If you want to match the whole string use:\A
and\z
(not to be confused with\Z
which is the equivalent of/\n?\z/
). [ссылка]Ruby string = "some injection\nusername" string[/^username$/] # matches string[/\Ausername\z/] # doesn't match
-
Use
x
modifier for complex regexps. This makes them more readable and you can add some useful comments. Just be careful as spaces are ignored. [ссылка]Ruby regexp = / start # some text \s # white space char (group) # first group (?:alt1|alt2) # some alternation end /x
-
For complex replacements
sub
/gsub
can be used with block or hash.[ссылка]
Процентные литералы
-
Используйте
%()
(это сокращение от%Q
) для строк без переносов, в которых реализуется интерполяция и присутствуют двойные кавычки. Для строк с переносами лучше используйте формат HERE Doc. [ссылка]```Ruby # плохо (интерполяция не нужна) %(<div class="text">Some text</div>) # должно быть ‘<div class="text">Some text</div>’
плохо (нет двойных кавычек)
%(This is #{quality} style) # должно быть “This is #{quality} style”
плохо (строка с переносами)
%(<div>\n#{exclamation}\n</div>) # лучше применить HERE Doc
хорошо (необходима интерполяция, присутствуют кавычки, нет переносов)
%(<tr><td class="name">#{name}</td>) ```
-
Избегайте
%q
, если это не случай строки с'
и"
одновременно Обычные строки читаются проще, и их следует использовать, если нет излишне большого количества символов, которые нужно будет экранировать. [ссылка]```Ruby # плохо name = %q(Bruce Wayne) time = %q(8 o’clock) question = %q(“What did you say?”)
хорошо
name = ‘Bruce Wayne’ time = “8 o’clock” question = ‘“What did you say?”’ ```
-
Используйте
%r
только для регулярных выражений, которые обрабатывают больше одного знака ‘/’.[ссылка]```Ruby # плохо %r(\s+)
все еще плохо
%r(^/(.)$) # должно быть /^\/(.)$/
хорошо
%r(^/blog/2011/(.*)$) ```
-
Откажитесь от использования
%x
, если вы не хотите вызывать внешнюю команду с обратными кавычками в теле (что само по себе маловероятно). [ссылка]```Ruby # плохо date = %x(date)
хорошо
date =
date
echo = %x(echodate
) ``` -
Старайтесь избегать
%s
. По общепринятому мнению, предпочтительным способом определения символа с пробелами в имени является:"some string"
[ссылка] -
Используйте
()
в качестве ограничителей для всех литералов со знаком%
кроме%r
. Так как круглые скобки очень часто используются в самих регулярных выражениях, во многих случаях менее частый символ{
может быть лучшим выбором для ограничителя (разумеется, с учетом смысла регулярного выражения). [ссылка]```Ruby # плохо %w[one two three] %q{“Test’s king!”, John said.}
хорошо
%w(one two three) %q(“Test’s king!”, John said.) ```
Метапрограммирование
-
Откажитесь от метапрограммирования ради метапрограммирования как такового. [ссылка]
-
Не разводите беспорядок в базовых классах при написании библиотек (не используйте “monkey patching”). [ссылка]
-
Используйте
#class_eval
с блоком вместно интерполяции значений в строке. Если вы используете интерполяцию, то всегда указывайте дополнительно__FILE__
и__LINE__
, чтобы информация о стеке вызова была осмысленной:[ссылка]Ruby class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
#define_method
предпочтительнее, чем#class_eval { def ... }
-
При использовании
#class_eval
(или других#eval
) с интерполяцией строк обязательно добавляйте комментарий, который будет наглядно показывать, как интерполированные значения будут выглядеть (примеры, используемые в исходном коде Rails):[ссылка]```Ruby # из activesupport/lib/active_support/core_ext/string/output_safety.rb UNSAFE_STRING_METHODS.each do |unsafe_method| if ‘String’.respond_to?(unsafe_method) class_eval «-EOT, FILE, LINE + 1 def #{unsafe_method}(args, &block) # def capitalize(args, &block) to_str.#{unsafe_method}(args, &block) # to_str.capitalize(args, &block) end # end
def #{unsafe_method}!(*args) # def capitalize!(*args) @dirty = true # @dirty = true super # super end # end EOT end end ```
-
Избегайте
#method_missing
для целей метапрограммирования, так как стек вызова становится нечитаемым, метод не виден в#methods
, опечатки в вызовах методов пройдут незамеченными, например,nukes.launch_state = false
. Используйте делегирование, проксирование или же#define_method
. Если вы используете#method_missing
: [ссылка]- обязательно задайте
#respond_to_missing?
; - перехватывайте вызовы только с четко определенными префиксами, например,
#find_by_*
– задайте в своем коде наиболее узкие рамки для неопределенностей; - вызывайте
#super
в конце ваших выражений; -
делегируйте вызовы понятным, “немагическим” методам:
```Ruby # плохо def method_missing?(meth, *args, &block) if /^find_by_(?
.*)/ =~ meth # ... lots of code to do a find_by else super end end хорошо
def method_missing?(meth, *args, &block) if /^find_by_(?
.*)/ =~ meth find_by(prop, *args, &block) else super end end Самым лучшим будет все же использование
#define_method
,# так как каждый видимый аргумент будет определен. ```
- обязательно задайте
Разное
-
Пишите код, не дающий предупреждений при вызове
ruby -w
.[ссылка] -
Не используйте хеши в качестве необязательных параметров. Возможно, ваш метод просто делает слишком много. Это не касается, однако, методов инициализации объектов. [ссылка]
-
Старайтесь не писать методы длиннее 10 строк. В идеальном случае большинство методов должны быть короче 5 строк. Пустные строки не подсчитываются.[ссылка]
-
Не создаваете методы с более чем тремя-четырьмя параметрами.[ссылка]
-
Если вам действительно нужны глобальные функции, включайте их в модуль Kernel и сделайте их приватными. [ссылка]
-
Используйте переменные модулей вместо глобальных переменных.[ссылка]
```Ruby # плохо $foo_bar = 1
хорошо
module Foo class « self attr_accessor :bar end end
Foo.bar = 1 ```
-
Избегайте использования
alias
, если достаточно использоватьalias_method
.[ссылка] -
Используйте
OptionParser
для анализа сложных аргуметов командрой строки иruby -s
для элеметарных случаев. [ссылка] -
Используйте вариант
Time.now
, а неTime.new
, когда хотите получить текущее значение системного времени. [ссылка] -
Пишите код в функциональном стиле без изменения значений, когда это подходит по смыслу.[ссылка]
-
Не изменяйте значения аргументов, если только это не есть цель метода.[ссылка]
-
Старайтесь не создавать вложенные структуры с уровнем вложения больше третьего. [ссылка]
-
Будьте последовательны. В идеальном мире последовательно придерживайтесь данного руководства. [ссылка]
-
Руководствуйтесь здравым смыслом. [ссылка]
Инструментарий
В этом разделе собраны инструменты, которые могут помочь вам автоматически сверить ваш код на Руби с предписаниями этого руководства.
РубоКоп
RuboCop — это утилита проверки стиля программного кода на Руби, который основывается на этом руководстве. РубоКоп уже реализует большую часть этого руководства, поддерживает MRI 1.9 и MRI 2.0 и хорошо интегрируется с редактором Емакс.
RubyMine
Модуль проверки кода RubyMine частично основывается на этом руководстве.
Сотрудничество
Ничто, описанное в этом руководстве, не высечено в камне. И я очень хотел бы сотрудничать со всеми, кто интересуется стилистикой оформления кода на Руби, чтобы мы смогли вместе создать ресурс, который был бы полезен для всего сообщества программистов на Руби.
Не стесняйтесь создавать отчеты об ошибках и присылать мне запросы на интеграцию вашего кода. И заранее большое спасибо за вашу помощь!
Вы можете поддержать проект (и РубоКоп) денежным взносом при помощи gittip.
Как сотрудничать в проекте?
Это просто! Следуйте руководству по сотрудничеству.
Лицензирование
Данная работа опубликована на условиях лицензии Creative Commons Attribution
3.0 Unported License
Расскажи другому
Создаваемое сообществом руководство по стилю оформления будет малопригодным для сообщества, которое об этом руководстве ничего не знает. Делитесь ссылками на это руководство с вашими друзьями и коллегами доступными вам средствами. Каждый получаемый нами комментарий, предложение или мнение сделает это руководство еще чуточку лучше. А ведь мы хотим самое лучшее руководство из возможных, не так ли?
Всего,
Божидар