- Организация исходного кода
- Синтаксис
- Наименование
- Комментарии
- Классы и модули
- Исключения
- Коллекции
- Строки
- Регулярные выражения
- Процентные литералы
- Метапрограммирование
- Разное
- Инструментарий
Почти все убеждены, что любой стиль кроме их собственного ужасен и нечитаем. Уберите отсюда "кроме их собственного" — и они будут, наверное, правы...
-- Джерри Коффин (Jerry Coffin) об отступах
-
Используйте
UTF-8в качестве кодировки для исходного кода.[ссылка] -
Используйте два пробела на уровень отступа (т.е. мягкую табуляцию). Никаких знаков табуляции. [ссылка]
# плохо (четыре пробела) def some_method do_something end # хорошо def some_method do_something end
-
Используйте стиль Unix для строк (пользователи *BSD/Solaris/Linux/OS X используют их по умолчанию, пользователям Windows нужно обратить особое внимание).[ссылка]
-
Если вы используете Git, вы можете добавить следующие настройки в вашу конфигурацию, чтобы предотвратить ненамеренное проникновение в ваш код строк, оканчивающихся в стиле Windows:
$ git config --global core.autocrlf true
-
-
Не используйте
;для разделения директив и выражений. Отсюда непосредственно следует, что каждая директива должна занимать свою отдельную строку.[ссылка]# плохо (точка с запятой избыточна) puts 'foobar'; puts 'foo'; puts 'bar' # две директивы на одной строке # хорошо puts 'foobar' puts 'foo' puts 'bar' puts 'foo', 'bar' # это частное правило для `puts`
-
Используйте преимущественно однострочный формат для определений классов с пустым телом. [ссылка]
# плохо class FooError < StandardError end # сносно class FooError < StandardError; end # хорошо FooError = Class.new(StandardError)
-
Избегайте однострочных методов. И хотя они достаточно популярны в среде программистов, существует множество неприятных мелочей, связанных с синтаксисом их определения, которые делают применение таких методов нежелательным. В любом случае однострочные методы не должны содержать больше одного выражения. [ссылка]
# плохо 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
Одним исключением в этом правиле являются методы с пустым телом.
# хорошо def no_op; end
-
Вставляйте пробелы вокруг операторов, после запятых, двоеточий и точек с запятыми, вокруг
{и перед}. Пробелы (по большей части) игнорируются интерпретатором Руби, но их правильное использование является ключом к написанию легко читаемого кода. [ссылка]sum = 1 + 2 a, b = 1, 2 [1, 2, 3].each { |e| puts e } class FooError < StandardError; end
Единственным исключением для операторов является оператор степени:
# плохо e = M * c ** 2 # хорошо e = M * c**2
{и}заслуживают некоторого пояснения, так как они используются для блоков и для литералов хешей, а также для интерполяции строк.Для литералов хешей два стиля являются общепринятыми:
# хорошо (пробел после { и до }) { one: 1, two: 2 } # хорошо (пробелы отсутствуют после { и перед }) {one: 1, two: 2}
Первый вариант несколько проще для чтения и, по всей вероятности, более распространен среди членов сообщества программистов на Руби. Второй вариант имеет преимущество в том, что создается видимое различие между блоками и литералами хешей. Какой бы стиль вы ни выбрали, применяйте его единообразно.
-
Не используйте пробел после
(,[или перед],).[ссылка]some(arg).other [1, 2, 3].size
-
Не используйте пробел после
!. [ссылка]# плохо ! something # хорошо !something
-
Записывайте литералы диапазонов без пробелов.[link]
# плохо 1 .. 3 'a' ... 'z' # хорошо 1..3 'a'...'z'
-
Делайте отступ для
whenтаким же, как и дляcase. Я знаю, что многие не согласятся с этим, но этот стиль предписывается как "Языком программирования Ruby", так и "Programming 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
-
Присваивая результат условного выражения переменной, сохраняйте соответствие уровней отступа. [ссылка]
# плохо (слишком запутано) 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
-
Используйте пустые строки для разделения определений методов и выделения логических частей определений внутри них.[ссылка]
def some_method data = initialize(options) data.manipulate! data.result end def some_method result end
-
Избегайте запятых после последнего параметра в вызове метода, особенно когда параметры расположены в отдельных строках.[ссылка]
# плохо, хотя проще перемещать/добавлять/удалять строки some_method( size, count, color, ) # плохо some_method(size, count, color, ) # хорошо some_method(size, count, color)
-
Вставляйте пробелы вокруг оператора присваивания
=, когда назначаете параметрам метода значения по умолчанию: [ссылка]# плохо def some_method(arg1=:default, arg2=nil, arg3=[]) # do something... end # хорошо def some_method(arg1 = :default, arg2 = nil, arg3 = []) # do something... end
Хотя в некоторых книгах по 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 Когда продолжаете цепочку вызовов методов на следующую строку, начинайте её с точки.
# плохо - нужно посмотреть на предыдущую строку, чтобы понять # смысл последующей one.two.three. four # хорошо - сразу ясно, что происходит во второй строке one.two.three .four
-
B Соответственно, наоборот, когда продолжаете цепочку вызовов на следующей строке, завершайте строку точкой
., давая понять, что продолжение выражения следует# плохо - чтобы понять, что выражение не окончено, необходимо # посмотреть на следующую строку. one.two.three .four # хорошо - сразу видно, что выражение будет продолжено на # следующей строке one.two.three. four
-
C аргументами за и против обоих стилей можно ознакомиться в дискуссии здесь.
-
Выравнивайте параметры вызова метода, если вызов занимает более одной строки. Если выравнивание невозможно из-за ограничений на длину строки, то используйте одинарный отступ. [ссылка]
# первоначальный вариант (строка слишком длинная) 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
-
Выравнивайте элементы литералов массива, если они занимают несколько строк.[ссылка]
# плохо 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']
-
Добавляйте символ подчеркивания в большие числовые константы для улучшения их восприятия. [ссылка]
# плохо (Сколько тут нолей?) num = 1000000 # хорошо (число воспринимается гораздо легче) num = 1_000_000
-
Используйте устоявшиеся правила RDoc для описания интерфейсов. Не отделяйте блок комментария от начала определения метода
defпустой строкой.[ссылка] -
Ограничивайте длину строк 80-ю символами.[ссылка]
-
Не оставляйте пробелы в конце строки. [ссылка]
-
Завершайте каждый файл переводом строки. [ссылка]
-
Не пользуйтесь блочными комментариями. Их нельзя разместить на необходимом уровне отступа. К тому же их сложнее воспринимать, чем обычные комментарии.[ссылка]
# плохо =begin строка комментария еще одна строка комментария =end # хорошо # строка комментария # другая строка комментария
-
Используйте
::только для обращения к константам (в том числе к классам и модулям) и конструкторам класса (например,Array()илиNokogiri::HTML()). Никогда не используйте::для обычного вызова методов. [ссылка]# плохо SomeClass::some_method some_object::some_method # хорошо SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONST SomeModule::SomeClass()
-
Используйте
defсо скобками, когда у метода есть параметры. Опускайте скобки, когда метод не принимает параметров. [ссылка]# плохо def some_method() # некоторый код end # хорошо def some_method # некоторый код end # плохо def some_method_with_parameters param1, param2 # некоторый код end # хорошо def some_method_with_parameters(param1, param2) # некоторый код end
-
Избегайте параллельного присвоения значений переменным. Параллельное присвоение разрешается тогда, когда присваивается возвращаемое методом значение совместно с оператором разобщения или значения переменных взаимно переопределяются. Параллельное присвоение сложнее воспринимается, чем обычная его форма записи, кроме этого оно еще и несколько медленнее. [link]
# плохо a, b, c, d = 'foo', 'bar', 'baz', 'foobar' # хорошо a = 'foo' b = 'bar' c = 'baz' d = 'foobar' # хорошо (взаимное переопределение) # Взаимное переопределение явлается особым случаем, так как помогает заместить # оба задействованных значения. a = 'foo' b = 'bar' a, b = b, a puts a # => 'bar' puts b # => 'foo' # хорошо (возвращаемое значение) def multi_return [1, 2] end first, second = multi_return # хорошо (применение оператора разобщения) first, *list = [1,2,3,4] hello_array = *"Hello" a = *(1..3)
-
Используйте оператор
forтолько в случаях, когда вы точно знаете, зачем вы это делаете. В подавляющем большинстве остальных случаев стоит применять итераторы. Операторforреализуется при помощиeach(таким образом вы добавляете еще один уровень абстракции), но с некоторыми отличиями: не создается отдельная область видимости (в отличии отeach) и переменные, объявленные в телеfor, будут видны за пределами блока. [ссылка]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, объявленных на нескольких строках.[ссылка]# плохо if some_condition then # некоторое действие end # хорошо if some_condition # некоторое действие end
-
Всегда записывайте условие для
if/unlessна той же строке, что содержитif/thenв многострочном условии. [ссылка]# плохо if some_condition do_something do_something_else end # хорошо if some_condition do_something do_something_else end
-
Предпочитайте тернарный оператор (
?:) конструкциям сif/then/else/end. Он используется чаще и по определению более краток.[ссылка]# плохо result = if some_condition then something else something_else end # хорошо result = some_condition ? something : something_else
-
Используйте только одно выражение в каждой ветви тернарного оператора. Отсюда следует, что лучше избегать вложенных тернарных операторов. При возникновении такой необходимости применяйте конструкции с
if/else.[ссылка]# плохо 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 эту синтаксическую конструкцию удалили, используйте вместо нее тернарные операторы. [ссылка]# плохо result = if some_condition: something else something_else end # хорошо result = some_condition ? something : something_else
-
Не используйте точку с запятой в
if x; .... Применяйте тернарные операторы. [ссылка] -
Извлекайте пользу из такого факта, что
ifиcaseявляются выражениями, возвращающими результирующие значения. [ссылка]# плохо 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. [ссылка]# плохо (необходимы скобки из-за неоднозначности приоритетов операторов) x = (not something) # хорошо x = !something
-
Не используйте
!!. [ссылка]# плохо x = 'test' # неявная проверка на nil if !!x # некоторое выражение end x = false # двойное отрицание бессмысленно для булевых значений !!x # => false # хорошо x = 'test' unless x.nil? # некоторое выражение end
-
Ключевые слова
andиorследует забыть. Они не несут дополнительной пользы. Всегда используйте&&и||вместо них. [ссылка]# плохо # булево выражение 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. Другим хорошим вариантом являются операторы управления потоком исполнения&&/||. [ссылка]# плохо if some_condition do_something end # хорошо do_something if some_condition # еще хороший вариант some_condition && do_something
-
Избегайте
if/unlessв конце нетривиального многострочного блока. [ссылка]# плохо 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для отрицательных условий (или||для управления потоком исполнения). [ссылка]# плохо do_something if !some_condition # плохо do_something if not some_condition # хорошо do_something unless some_condition # тоже хорошо some_condition || do_something
-
Не используйте
unlessвместе сelse. Перепишите такие выражение с положительной проверкой. [ссылка]# плохо unless success? puts 'failure' else puts 'success' end # хорошо if success? puts 'success' else puts 'failure' end
-
Не используйте скобки для ограничения условных выражений в
if/unless/while/until.[ссылка]# плохо if (x > 10) # код опущен для краткости end # хорошо if x > 10 # код опущен для краткости end
Однако в этом правиле есть некоторые исключения, например, надежные присвоения в условных выражениях.
-
Не используйте
while/until УСЛОВИЕ doдля многострочных циклов сwhile/until. [ссылка]# плохо while x > 5 do # код опущен для краткости end until x > 5 do # код опущен для краткости end # хорошо while x > 5 # код опущен для краткости end until x > 5 # код опущен для краткости end
-
Используйте
while/untilдля однострочный выражений.[ссылка]# плохо while some_condition do_something end # хорошо do_something while some_condition
-
Используйте
untilвместоwhileдля условий на отрицания.[ссылка]# плохо do_something while !some_condition # хорошо do_something until some_condition
-
Используйте
Kernel#loopвместоwhile/untilдля бесконечного цикла.[ссылка]# плохо while true do_something end until false do_something end # хорошо loop do do_something end
-
Используйте
Kernel#loopсbreakвместоbegin/end/untilилиbegin/end/whileдля циклов с постусловием. [ссылка]# плохо 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и при вызове аксессоров. Используйте скобки при вызове прочих методов. [ссылка]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
-
Не используйте фигурные скобки для ограничения хешей, передаваемых методу. [ссылка]
# плохо user.set({ name: 'John', age: 45, permissions: { read: true } }) # хорошо user.set(name: 'John', age: 45, permissions: { read: true })
-
Не используйте фигурные скобки для ограничения хешей, передаваемых методу, и скобки вокруг параметров для методов, являющихся частью DSL. [ссылка]
class Person < ActiveRecord::Base # плохо validates(:name, { presence: true, length: { within: 1..10 } }) # хорошо validates :name, presence: true, length: { within: 1..10 } end
-
Опускайте скобки при вызове метода без параметров. [ссылка]
# плохо Kernel.exit!() 2.even?() fork() 'test'.upcase() # хорошо Kernel.exit! 2.even? fork 'test'.upcase
-
Используйте краткую форму для вызова
proc, если вызываемый метод является единственным в блоке. [link]# плохо names.map { |name| name.upcase } # хорошо names.map(&:upcase)
-
Используйте преимущественно
{...}в случае однострочных блоков, аdo...endв случае многострочных блоков (многострочные последовательности вызовов методов всегда выглядят ужасно). Старайтесь применятьdo...endдля логических операций и определений методов (например, для Rakefile и некоторых DSL). Не используйтеdo...endв цепочках вызовов. [ссылка]names = %w(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(&:upcase)
Некоторые из нас поспорят, что многострочные последовательные вызовы с блоками при использовании {...} выглядят неплохо, но тогда стоит себя спросить, а читается ли такой код и не стоит ли выделить эти блоки в отдельные специальные методы.
-
Попробуйте использовать блоки напрямую в виде аргумента в случае, когда блок просто передает свои аргументы в другой блок. В этом случае обратите внимание на падение производительности, так как аргументы будут преобразованы в объект класс
Proc. [ссылка]require 'tempfile' # плохо def with_tmp_dir Dir.mktmpdir do |tmp_dir| # блок просто передает аргументы дальше Dir.chdir(tmp_dir) { |dir| yield dir } 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 доступен в виде параметра, и pwd имеет значение: #{dir}" end
-
Избегайте ключевого слова
returnвезде, где это не нужно для управления ветвлением. [ссылка]# плохо def some_method(some_arr) return some_arr.size end # хорошо def some_method(some_arr) some_arr.size end
-
Избегайте ключевого слова
selfвезде, где оно не требуется. Оно необходимо только при вызове методов доступа (attr_reader,attr_writer,attr_accessor). [ссылка]# плохо 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
-
В качестве бездоказательного утверждения: избегайте маскирования методов локальными переменными, если они не эквивалентны. [ссылка]
class Foo attr_accessor :options # cносно # как options, так и self.options здесь эквивалентны def initialize(options) self.options = options 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
-
Используйте возвращаемое оператором присваивания (
=) значение только в случаях, когда все выражение стоит в скобках. Эта идиома достаточно распространена среди программистов на Руби и часто называется надежное присваивание в логических выражениях. [ссылка]# плохо (к тому же вызывает предупреждение) if v = array.grep(/foo/) do_something(v) ... end # хорошо (MRI будет вызывает предупреждение, но не Рубокоп) if (v = array.grep(/foo/)) do_something(v) ... end # хорошо v = array.grep(/foo/) if v do_something(v) ... end
-
По возможности используйте сокращенные операторы присваивания. [ссылка]
# плохо 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
-
Используйте оператор
||=для инициализации переменных, только если переменная еще не инициализирована. [ссылка]# плохо name = name ? name : 'Bozhidar' # плохо name = 'Bozhidar' unless name # хорошо (присвоить переменной name значение Bozhidar, только если ее значение # nil или false name ||= 'Bozhidar'
-
Не используйте оператор
||=для инициализации логических переменных. Это вызовет проблемы, если текущим значением переменной будетfalse. [ссылка]# плохо (назначит переменной enabled значение true, даже если оно было false) enabled ||= true # хорошо enabled = true if enabled.nil?
-
Используйте оператор
&&=для предварительной работы с переменными, которые уже или еще не инициализированы. Использование оператора&&=изменит значение переменной, только если она инициализирована. При этом отпадает необходимость в проверке сif. [ссылка]# плохо if something something = something.downcase end # плохо something = something ? something.downcase : nil # сносно something = something.downcase if something # хорошо something = something && something.downcase # еще лучше something &&= something.downcase
-
Избегайте явного использования оператора равенства в case
===. Как подсказывает его имя, этот оператор предназначен для имплицитного применения в выраженияхcase, в отрыве от них он приводит только к разночтениям в коде. [ссылка]# плохо Array === something (1..100) === 7 /something/ === some_string # хорошо something.is_a?(Array) (1..100).include?(7) some_string =~ /something/
-
Не используйте
eql?, если будет достаточно==. Более строгая семантика сравнения, реализованная вeql?, достаточно редко нужна на практике. [link]# плохо (`eql?` работает для строк, как и `==`) "ruby".eql? some_str # хорошо "ruby" == some_str 1.0.eql? x # здесь `eql?` имеет смысл, если вы хотите различать классы числа: `Fixnum` vs. `Float`
-
Избегайте специальных переменных, заимствованных из языка Перл, например,
$:,$;и т.д. Они сложно воспринимаются, и их использование приветствуется только в однострочных скриптах. В остальных случаях применяйте легкие для восприятия варианты этих переменных из библиотекиEnglish. [ссылка]# плохо $:.unshift File.dirname(__FILE__) # хорошо require 'English' $LOAD_PATH.unshift File.dirname(__FILE__)
-
Не оставляйте пробел между именем метода и открывающей скобкой. [ссылка]
# плохо f (3 + 2) + 1 # хорошо f(3 + 2) + 1
-
Если первый аргумент при вызове метода начинается скобкой, то всегда используйте скобки при вызове метода. Например, пишем так:
f((3 + 2) + 1). [ссылка] -
Всегда вызывайте интерпретатор Руби с ключом
-w, чтобы получать напоминания о правилах, описанных выше, даже если вы о них забываете. [ссылка] -
Используйте новый синтаксис лямбда-выражений для однострочных блоков. Используйте метод
lambdaдля многострочных блоков. [ссылка]# плохо l = lambda { |a, b| a + b } l.call(1, 2) # верно, но выглядит очень странно 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
-
Используйте скобки при определении
lambdaс аргументами. [link]# плохо l = ->x, y { something(x, y) } # хорошо l = ->(x, y) { something(x, y) }
-
Не используйте скобки при определении
lambdaбез аргументов. [link]# плохо l = ->() { something } # хорошо l = -> { something }
-
Используйте
procвместоProc.new. [ссылка]# плохо p = Proc.new { |n| puts n } # хорошо p = proc { |n| puts n }
-
Используйте
proc.call()вместоproc[]илиproc.()для лямбда-выражений и блоков. [ссылка]# плохо (выглядит как доступ к энумератору) l = ->(v) { puts v } l[1] # тоже плохо (редкая формулировка) l = ->(v) { puts v } l.(1) # хорошо l = ->(v) { puts v } l.call(1)
-
Начинайте неиспользуемые параметры блока с подчеркивания
_. Также допустимо использовать только подчеркивание_, хотя это и менее информативно. Эта договоренность распознается интерпретатором Руби и Рубокопом и уберет предупреждения о неиспользуемых переменных. [ссылка]# плохо 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#%. [ссылка]# плохо '%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'
-
Используйте
Array#joinвместо достаточно неочевидногоArray#*со строковым аргументом. [ссылка]# плохо %w(one two three) * ', ' # => 'one, two, three' # хорошо %w(one two three).join(', ') # => 'one, two, three'
-
Используйте
[*var]илиArray()вместо явной проверки с помощьюArray, когда вам приходится работать с переменной, которая по вашим ожиданиям должна быть массивом, но вы в этом не полностью уверены. [ссылка]# плохо paths = [paths] unless paths.is_a? Array paths.each { |path| do_something(path) } # хорошо [*paths].each { |path| do_something(path) } # хорошо (и более читаемо) Array(paths).each { |path| do_something(path) }
-
Используйте интервалы или метод
Comparable#between?вместо сложной логики для сравнения, когда это возможно. [ссылка]# плохо do_something if x >= 1000 && x <= 2000 # хорошо do_something if (1000..2000).include?(x) # хорошо do_something if x.between?(1000, 2000)
-
Используйте предикативные методы вместо явного сравнения с использованием
==. Сравнение чисел можно проводить явно. [ссылка]# плохо 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
-
Проводите явную проверку на значение
nil, только если вы работаете с логическими значениями. [ссылка]# плохо do_something if !something.nil? do_something if something != nil # хорошо do_something if something # хорошо (логическое значение) def value_set? !@some_boolean.nil? end
-
Старайтесь не использовать блоки
BEGIN. [ссылка] -
Никогда не используйте блоки
END. Используйте методKernel#at_exit. [ссылка]# плохо END { puts 'Goodbye!' } # хорошо at_exit { puts 'Goodbye!' }
-
Избегайте переменных-перевертышей (flip-flops). [ссылка]
-
Избегайте вложенных условий для управления ветвлением. Используйте проверочные выражения (guard clauses). Проверочные выражения - это условные выражения в самом начале функции, которые срабатывают при первой же возможности. [ссылка]
# плохо 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
Используйте в циклах
nextв место блоков с условием.# плохо [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
Единственными настоящими сложностями в программировании являются очистка кэша и выбор наименований.
-- Фил Карлтон (Phil Karlton)
-
Используйте английский язык, называя идентификаторы.[ссылка]
# плохо (идентификатор использует символы вне ASCII) зарплата = 1_000 # плохо (идентификатор - это русское слово, набранное латиницей вместо # кириллицы) zarplata = 1_000 # хорошо salary = 1_000
-
Используйте
snake_caseдля имен символов, методов и переменных. [ссылка]# плохо :'some symbol' :SomeSymbol :someSymbol someVar = 5 def someMethod ... end def SomeMethod ... end # хорошо :some_symbol def some_method ... end
-
Используйте
CamelCaseдля имен классов и модулей. Сокращения вродеHTTP,RFC,XMLнабирайте заглавными буквами. [ссылка]# плохо class Someclass ... end class Some_Class ... end class SomeXml ... end class XmlSomething ... end # хорошо class SomeClass ... end class SomeXML ... end class XMLSomething ... end
-
Используйте
snake_case, называя файлы, например,hello_world.rb.[ссылка] -
Используйте
snake_case, называя каталоги, например,lib/hello_world/hello_world.rb. [ссылка] -
Старайтесь создавать только один класс или модуль в каждом файле исходного кода. Называйте эти файлы по имени класса или модуля, изменив запись в форме
CamelCaseнаsnake_case. [ссылка] -
Используйте
SCREAMING_SNAKE_CASEдля всех других констант кроме имен классов и модулей. [ссылка]# плохо SomeConst = 5 # хорошо SOME_CONST = 5
-
Идентификаторы предикативных методов, т.е. методов, возвращающих логическое значение, должны оканчиваться вопросительным знаком. Например,
Array#empty?. Методы, не возвращающие логическое значение, не должны оканчиваться вопросительным знаком. [ссылка] -
Идентификаторы потенциально опасных методов, т.е. таких методов, которые могут изменить
selfили его аргументы, должны оканчиваться восклицательным знаком, если есть соответствующий безопасный вариант такого метода. Например,exit!, который не вызывает завершающий скрипт в отличии отexit, выполняющего финализацию. [ссылка]# плохо (нет соответствующего безопасного аналога) class Person def update! end end # хорошо class Person def update end end # хорошо class Person def update! end def update end end
-
Определяйте безопасный метод (вариант без восклицательного знака) при помощи вызова опасного метода (с восклицательным знаком), если это возможно. [ссылка]
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
-
При использовании
#reduceс коротким блоком, называйте аргументы|a, e|(accumulator, element). [ссылка] -
При определении бинарных операторов называйте параметр
other. Исключение составляют методы#<<и#[], так как их семантика сильно отличается. [ссылка]def +(other) # некоторый код end
-
Используйте
#mapвместо#collect,#findвместо#detect,#selectвместо#find_all,#reduceвместо#injectи#sizeвместо#length. Это требование не сложно реализовать. Если использование альтернатив улучшит восприятие кода, то можно использовать и их. Все описанные варианты были взяты из языка Smalltalk и не распространены в других языках программирования. Причиной, почему не следует использовать#find_allвместо#select, является хорошая сочетаемость с методом#reject, и эти наименования очевидны. [ссылка] -
Не используйте
#countв качестве заметы для#size. Для объектов классов с включеннымEnumerable(кроме классаArray) это приведет к затратному полному обходу всех элементов для определения размера.[ссылка]# плохо some_hash.count # хорошо some_hash.size
-
Используйте
#flat_mapвместо#map+#flatten. Это правило не относится к массивам с глубиной больше 2, например, еслиusers.first.songs == ['a', ['b', 'c']], то используйте#map+#flatten, а не#flat_map. Метод#flat_mapуменьшает глубину на один уровень. Метод#flattenсглаживает вложенность любого уровня. [ссылка]# плохо all_songs = users.map(&:songs).flatten.uniq # хорошо all_songs = users.flat_map(&:songs).uniq
-
Используйте метод
#reverse_eachвместо#reverse.each, так как некоторые классы, включающие в себя модульEnumerable, дадут вам очень эффективную реализацию. Даже в худшем случае, когда класс не реализует этот метод отдельно, наследуемая реализация из модуляEnumerableбудет по меньшей мере такой же эффективной, как и для#reverse.each. [ссылка]# плохо array.reverse.each { ... } # хорошо array.reverse_each { ... }
Хороший код является лучшей документацией для себя. Каждый раз, когда вы готовитесь добавить комментарий, спросите себя: "Как я могу улучшить код, чтобы это комментарий стал ненужным?" Улучшите код и добавьте комментарий, чтобы сделать его еще понятнее.
-- Стив Макконнел (Steve McConnell)
-
Пишите говорящий за себя код и смело пропускайте все остальное в этом разделе. Серьезно! [ссылка]
-
Пишите комментарии по-английски. [ссылка]
-
Используйте один пробел между символом
#в начале и текстом самого комментария. [ссылка] -
Комментарии длиной больше одного слова должны оформляться в виде законченных предложений (с большой буквы и со знаками препинания). Разделяйте предложения одним пробелом. [ссылка]
-
Избегайте избыточного комментирования. [ссылка]
# плохо counter += 1 # Увеличивает счетчик на единицу.
-
Актуализируйте существующие комментарии. Устаревший комментарий гораздо хуже отсутствующего комментария. [ссылка]
Хороший код подобен хорошей шутке: он не нуждается в пояснениях.
-- Рус Ольсен (Russ Olsen)
- Не пишите комментарии для объяснения плохого кода. Перепишите код, чтобы он говорил сам за себя. [ссылка]
Делай или не делай, тут нет места попыткам.
-- Мастер Йода
-
Обычно пометки следует записывать на предшествующей описываемому коду строке.[ссылка]
-
Пометка отделяется двоеточием и пробелом, потом следует примечание, описывающее проблему.[ссылка]
-
Если для описания проблемы потребуются несколько строк, то на каждой последующей строке следует сделать отступ в три пробела после символа
#. [ссылка]def bar # FIXME: This has crashed occasionally since v3.2.1. It may # be related to the BarBazUtil upgrade. baz(:quux) end
-
В тех случаях, когда проблема настолько очевидна, что любые описания покажутся избыточными, пометки можно поставить в конце вызывающей проблему строки. Однако такое применение должно быть исключением, а не правилом.[ссылка]
def bar sleep 100 # OPTIMIZE end
-
Используйте
TODO, чтобы пометить отсутствующие возможности или функционал, которые должны быть добавлены позже. [ссылка] -
Используйте
FIXME, чтобы пометить код с ошибками, который должен быть исправлен. [ссылка] -
Используйте
OPTIMIZE, чтобы пометить медленный или неэффективный код, который может вызвать проблемы с производительностью. [ссылка] -
Используйте
HACK, чтобы пометить код "с душком", который должен быть переработан и использует сомнительные практики разработки. [ссылка] -
Используйте
REVIEW, чтобы пометить все, что должно быть проверено на работоспособность. Например,REVIEW: Are we sure this is how the client does X currently?. [ссылка] -
Используйте персональные пометки, если это подходит по месту, но обязательно опишите их смысл в файле
README(или похожем) для вашего проекта. [ссылка]
-
Придерживайтесь единообразной структуры классов.[ссылка]
class Person # extend и include в начале extend SomeModule include AnotherModule # вложенные классы CustomErrorKlass = Class.new(StandardError) # после этого константы SOME_CONSTANT = 20 # после этого макросы методов доступа к атрибутам attr_reader :name # и все прочие макросы (если имеются) validates :name # следующими по списку будут публичные методы класса def self.some_method end # инициализация объекта стоит между методами класса и экземпляров def initialize end # и следующие за ними публичные методы экземпляров этого класса def some_method end # защищенные и частные методы нужно собрать ближе к концу protected def some_protected_method end private def some_private_method end end
-
Если определение класса занимает несколько строк, постарайтесь вынести такой класс в отдельный файл. Файл с определением стоит поместить в директорию, названную по имени родительского класса, внутри которого определяется вложенный класс. [ссылка]
# плохо # foo.rb class Foo class Bar # 30 методов внутри end class Car # 20 методов внутри end # 30 методов внутри end # хорошо # foo.rb class Foo # 30 методов внутри end # foo/bar.rb class Foo class Bar # 30 методов внутри end end # foo/car.rb class Foo class Car # 20 методов внутри end end
-
Если класс определяет только методы класса, то трансформируйте такой класс в модуль. Использовать классы логично в тех ситуациях, когда нужно создавать экземпляры класса. [ссылка]
# плохо class SomeClass def self.some_method # некоторый код end def self.some_other_method end end # хорошо module SomeModule module_function def some_method # некоторый код end def some_other_method end end
-
Используйте
module_functionвместоextend self, когда вам нужно преобразовать включаемые методы модуля в методы модуля. [ссылка]# плохо module Utilities extend self def parse_something(string) # здесь реализуется логика end def other_utility_method(number, string) # здесь реализуется дополнительная логика end end # хорошо module Utilities module_function def parse_something(string) # здесь реализуется логика end def other_utility_method(number, string) # здесь реализуется дополнительная логика end end
-
Создавая иерархии классов, проверяйте их на соответствие [принципу подстановки Барбары Лисков][Liskov]. [ссылка]
-
Проверяйте дизайн ваших классов на соответствие принципу SOLID, если такая возможность есть. [ссылка]
-
Для описывающих предметные области объектов всегда определяйте метод
#to_s. [ссылка]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
-
Применяйте макросы из семества
attr_для тривиальных методов доступа к объекту. [ссылка]# плохо 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
-
Не используйте обобщенную форму
attr. Используйтеattr_readerиattr_accessorвместо нее. [ссылка]# плохо (создает единый метод доступа атрибуту, объявлено нежелательным 1.9) attr :something, true attr :one, :two, :three # ведет себя как attr_reader # хорошо attr_accessor :something attr_reader :one, :two, :three
-
Подумайте об использовании
Struct.new, эта конструкция даст вам сразу простейшие методы доступа к состоянию, метод инициализации и методы сравнения. [ссылка]# хорошо class Person attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end # лучше Person = Struct.new(:first_name, :last_name) do end
-
Не дополняйте
Struct.newпри помощи#extend. В этом случае уже создается новый класс. При дополнении вы создадите избыточный уровень абстракции, это может привезти к странным ошибкам при многократной загрузке кода из файла. [ссылка]# плохо class Person < Struct.new(:first_name, :last_name) end # хорошо Person = Struct.new(:first_name, :last_name)
-
Продумывайте варианты добавления фабричных методов как дополнительной возможности создавать экземпляры конкретного класса. [ссылка]
class Person def self.create(options_hash) # некоторый код end end
-
Используйте технику [утиной типизации][duck-typing] (duck typing) вместо наследования. [ссылка]
# плохо 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
-
Избегайте переменных класса (
@@) из-за их "непристойного" поведения при наследовании. [ссылка]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 # => вернет "child"
Как вы видите, все классы в иерархии фактически делят одну и ту же переменную класса. Как правило, вам следует использовать переменные экземпляра класса вместо переменной класса.
-
Ограничивайте область видимости методов (
private,protected) в зависимости от их планируемого применения. Не оставляйте все в областиpublic(это стандартное значение). В конце концов мы пишем на Руби, а не на Питоне. [ссылка] -
Делайте отступы для указателей
public,protectedиprivateтакими же, как и у самих определений методов, к которым они относятся. Оставляйте пустую строку выше, а также после указателя, чтобы подчеркнуть, что он относится ко всем определяемым ниже методам. [ссылка]class SomeClass def public_method # некоторый код end private def private_method # некоторый код end def another_private_method # некоторый код end end
-
Для определения методов класса используйте
def self.method. Это упростит рефакторинг, так как имя класса будет использоваться только один раз. [ссылка]class TestClass # плохо def TestClass.some_method # некоторый код end # хорошо def self.some_other_method # body omitted end # Также допускается и будет удобным, когда # нужно определить много методов класса. class << self def first_method # некоторый код end def second_method_etc # некоторый код end end end
-
Используйте
aliasпри определении алиасов методов в лексической области видимости класса.selfв данном случае также имеет лексическую область видимости, и это подчеркивает тот факт, что алиас будет указывать на метод того класса, в котором определен. Вызов не будет перенаправлен неявно. [link]class Westerner def first_name @names.first end alias given_name first_name end
Так как
alias, как иdef, является ключевым словом, используйте простые имена методов, а не символы или строки в качестве аргументов. Другими словами, пишитеalias foo bar, а неalias :foo :bar.Также обратите внимание, как Ruby обрабатывает алиасы при наследовании: алиас будет привязан к тому методу, который находится в области видимости в момент объявления. Динамическое перенаправление вызова не производится.
class Fugitive < Westerner def first_name 'Nobody' end end
В этом примере
Fugitive#given_nameбудет вызывать метод базового классаWesterner#first_name, а неFugitive#first_name. Чтобы переопределить поведениеFugitive#given_name, нужно объявить алиас в классе-наследнике.class Fugitive < Westerner def first_name 'Nobody' end alias given_name first_name end
-
Всегда применяйте
alias_methodдля определения алиасов методов модулей, классов или синглетных классов во время выполнения, так какaliasиспользует лексическую область видимости, что приводит к неопределенному поведению в данном случае. [link]module Mononymous def self.included(other) other.class_eval { alias_method :full_name, :given_name } end end class Sting < Westerner include Mononymous end
-
Вызывайте исключения при помощи ключевого слова
fail. Используйтеraiseтолько при перехвате исключения и вызове его же заново. В этом случае вы не вызываете исключение, а лишь намеренно передаете его дальше по стеку.[ссылка]begin fail 'Oops' rescue => error raise if error.message != 'Oops' end
-
Нет нужды задавать
RuntimeErrorявно в качестве аргумента при вызовеfail/raiseс двумя аргументами. [ссылка]# плохо fail RuntimeError, 'message' # хорошо - вызывает `RuntimeError` по умолчанию fail 'message'
-
Передавайте класс исключения и сообщение в форме двух аргументов для
fail/raiseвместо экземпляра класса исключения. [ссылка]# плохо fail SomeException.new('message') # Обратите внимание, что нет возможности вызвать # `fail SomeException.new('message'), backtrace`. # хорошо fail SomeException, 'message' # Работает с `fail SomeException, 'message', backtrace`.
-
Не возвращайте значений в блоке
ensure. Если вы явным образом возвращаете значение из блокаensure, то возвращение будет обрабатываться сначала и метод вернет значение, как если бы исключения не было вовсе. По итогу исключение будет просто тихо проигнорировано. [ссылка]def foo fail ensure return 'very bad idea' end
-
Используйте имплицитную форму блока
beginпо возможности.[ссылка]# плохо def foo begin # основной код находится здесь rescue # здесь происходит обработка ошибок end end # хорошо def foo # здесь реализуется основная логика rescue # здесь происходит обработка ошибок end
-
Смягчайте неудобства, связанные с использование блоков
beginпри помощи contingency methods (термин введен Авди Гриммом).[ссылка]# плохо 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 }
-
Не подавляйте исключения без обработки. [ссылка]
# плохо begin # здесь образовалось исключение rescue SomeError # rescue не содержит никакой обработки end # плохо do_something rescue nil
-
Откажитесь от использования
rescueв виде постмодификатора.[ссылка]# плохо - это перехватывает исключения класса `StandardError` и его наследников read_file rescue handle_error($!) # хорошо - это перехватывает только исключения класса `Errno::ENOENT` и его наследников def foo read_file rescue Errno::ENOENT => ex handle_error(ex) end
-
Управляйте ветвлением в программе без помощи исключений.[ссылка]
# плохо begin n / d rescue ZeroDivisionError puts 'Cannot divide by 0!' end # хорошо if d.zero? puts 'Cannot divide by 0!' else n / d end
-
Не перехватывайте напрямую класс исключений
Exception. Это будет перехватывать сигналы и вызовыexit, что потребует в крайнем случае завершения процесса при помощиkill -9. [ссылка]# плохо begin # сигналы выхода будет перехвачены (кроме kill -9) exit rescue Exception puts "you didn't really want to exit, right?" # обработка исключений end # хорошо begin # `rescue` без параметров перехватывает `StandardError`, а не `Exception`, # как предполагают многие разработчики. rescue => e # обработка исключений end # тоже хорошо begin # здесь вызывается исключение rescue StandardError => e # обработка ошибок end
-
Размещайте более специфичные исключения в иерархии проверки, иначе они никогда не будут отфильтрованы. [ссылка]
# плохо begin # код с ошибкой rescue Exception => e # некоторое действие rescue StandardError => e # некоторое действие end # хорошо begin # код с ошибкой rescue StandardError => e # некоторое действие rescue Exception => e # некоторое действие end
-
Освобождайте используемые вашей программой ресурсы в блоке
ensure. [ссылка]f = File.open('testfile') begin # .. process rescue # .. handle error ensure f.close unless f.nil? end
-
Применяйте варианты доступа к ресурсам, которые гарантируют автоматический возврат выделенных ресурсов, если есть такая возможность. [link]
# плохо (нужно специально закрывать ранее открытый файл) f = File.open('testfile') # ... f.close # хорошо (открытый файл закрывается автоматически) File.open('testfile') do |f| # ... end
-
Преимущественно используйте исключения, определенные в стандартной библиотеке. Не создавайте без нужды новые классы исключений. [ссылка]
-
При создании массивов и хешей применяйте нотацию с литералами. Используйте конструкторы класса, только если вам нужно передать дополнительные параметры при создании коллекций. [ссылка]
# плохо arr = Array.new hash = Hash.new # хорошо arr = [] hash = {}
-
Используйте нотацию
%wдля литералов массивов, когда вам необходимо создать массив слов (непустых строк без пробелов и метасимволов). Это правило касается лишь массивов с двумя и более элементами.[ссылка]# плохо STATES = ['draft', 'open', 'closed'] # хорошо STATES = %w(draft open closed)
-
Используйте нотацию
%iдля литералов массивов, когда вам необходимо создать массив символов. Помните, что эта нотация несовместима с синтаксисом Ruby 1.9 и старше. Это правило касается лишь массивов с двумя и более элементами.[ссылка]# плохо STATES = [:draft, :open, :closed] # хорошо STATES = %i(draft open closed)
-
Не ставьте запятую после последнего элемента в литералах массивов и хешей, особенно если элементы находятся не на разных строках.[ссылка]
# плохо (проще перемещать, добавлять и удалять элементы, но не идеально) VALUES = [ 1001, 2020, 3333, ] # плохо VALUES = [1001, 2020, 3333, ] # хорошо VALUES = [1001, 2020, 3333]
-
Не создавайте массивы с большими незанятыми промежутками адресов.[ссылка]
arr = [] arr[100] = 1 # Теперь у вас есть массив с кучей значений `nil`.
-
При доступе к первому и последнему элементам массива используйте методы
#firstили#last, а не индексы[0]и[-1]. [ссылка] -
Используйте класс
SetвместоArray, если вы работаете с уникальными элементами. КлассSetреализует несортированную коллекцию элементов без повторений и является гибридом интуитивных операций классаArrayи легкого и быстрого доступа классаHash. [ссылка] -
Используйте символы вместо строк в качестве ключей хешей.[ссылка]
# плохо hash = { 'one' => 1, 'two' => 2, 'three' => 3 } # хорошо hash = { one: 1, two: 2, three: 3 }
-
Не используйте мутируемые объекты в качестве ключей для хешей.[ссылка]
-
Применяйте введенный в Ruby 1.9 синтаксис для литералов хешей, когда ключами являются символы. [ссылка]
# плохо hash = { :one => 1, :two => 2, :three => 3 } # хорошо hash = { one: 1, two: 2, three: 3 }
-
Не используйте разные способы записи хешей одновременно (нотации до и после Ruby 1.9). Если вы используете не только символы в качестве ключей, то применяйте только старую нотацию со стрелками. [ссылка]
# плохо { a: 1, 'b' => 2 } # хорошо { :a => 1, 'b' => 2 }
-
Применяйте
Hash#key?вместоHash#has_key?иHash#value?вместоHash#has_value?. Матц описывает здесь свои планы исключить эти методы в будущем. [ссылка]# плохо hash.has_key?(:test) hash.has_value?(value) # хорошо hash.key?(:test) hash.value?(value)
-
Для надежной работы с заданными ключами, о существовании которых доподлинно известно, используйте
Hash#fetch. [ссылка]heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' } # плохо (закравшуюся ошибку можно и не заметить сразу) heroes[:batman] # => "Bruce Wayne" heroes[:supermann] # => nil # хорошо (`Hash#fetch` вызывает `KeyError` и явно указывает на проблему) heroes.fetch(:supermann)
-
Задавайте стандартные значения для хешей при помощи
Hash#fetch, не реализуйте эту логику самостоятельно. [ссылка]batman = { name: 'Bruce Wayne', is_evil: false } # плохо (например, при использование оператора `||` мы получим неожиданный # результат при ложном значении первого операнда) batman[:is_evil] || true # => true # хорошо (`Hash#fetch` отрабатывает корректно) batman.fetch(:is_evil, true) # => false
-
Используйте блоки вместо значений
Hash#fetchпо умолчанию, если вызываемый код имеет сторонние эффекты или сложен для выполнения. [ссылка]batman = { name: 'Bruce Wayne' } # плохо (при использовании значения по умолчанию метод его расчета будет # вызываться каждый раз, сильно замедляя выполнение программы при # многократных вызовах) # obtain_batman_powers - нагруженный метод batman.fetch(:powers, obtain_batman_powers) # хорошо (блоки исчисляются лишь по необходимости, когда вызывается KeyError) batman.fetch(:powers) { obtain_batman_powers }
-
Используйте
Hash#values_at, когда вам нужно получить несколько значений хеша за один раз. [ссылка]# плохо email = data['email'] username = data['nickname'] # хорошо email, username = data.values_at('email', 'nickname')
-
Вы можете положиться на то, что хеши в Ruby 1.9 и младше отсортированы.[ссылка]
-
Никогда не модифицируйте коллекцию в процессе ее обхода.[ссылка]
-
Получая доступ к элементам коллекций, старайтесь избегать доступа при помощи
[n], а используйте альтернативные методы доступа, если таковые определены. Это обезопасит вас от вызова[]наnil. [link]# плохо Regexp.last_match[1] # хорошо Regexp.last_match(1)
-
При определении методов доступа к коллекции, добавьте альтернативную форму, чтобы оградить пользователей от необходимости проверки на
nilперед доступом к элементу коллекции. [link]# плохо def awesome_things @awesome_things end # хорошо def awesome_things(index = nil) if index && @awesome_things @awesome_things[index] else @awesome_things end end
-
Используйте интерполяцию строк и форматные шаблоны, а не конкатенацию строк. [ссылка]
# плохо email_with_name = user.name + ' <' + user.email + '>' # хорошо email_with_name = "#{user.name} <#{user.email}>" # хорошо email_with_name = format('%s <%s>', user.name, user.email)
-
Избегайте пробелов внутри скобок вокруг интерполируемых выражений в строках. [ссылка]
# плохо "From: #{ user.first_name }, #{ user.last_name }" # хорошо "From: #{user.first_name}, #{user.last_name}" "#{ user.last_name }, #{ user.first_name }"
-
Постарайтесь внедрить единообразный стиль кавычек для строчных литералов. В среде программистов на Руби есть два популярных стиля, оба из них считаются приемлемыми. Стиль А подразумевает одинарные кавычки по умолчанию, а стиль B — двойные кавычки. [ссылка]
-
Стиль A: Используйте одинарные кавычки, если вам не нужна интерполяция строк или специальные символы вроде
\t,\n,'и т.д.# плохо name = "Bozhidar" # хорошо name = 'Bozhidar'
-
Стиль B: Используйте двойные кавычки в ваших строчных литералах, если они не содержат
"или экранируйте символы, которые не должны интерполироваться.# плохо name = 'Bozhidar' # хорошо name = "Bozhidar"
Второй стиль, по некоторым мнениям, более распространен среди разработчиков на Руби. Однако в этом руководстве оформление строк следует первому правилу.
-
-
Не используйте запись для литералов алфавитных символов
?x. Начиная с версии Руби 1.9, этот вариант записи избыточен:?xбудет интерпретироваться в виде'x'(строка с единственным символом в ней).[ссылка]# плохо char = ?c # хорошо char = 'c'
-
Всегда применяйте фигурные скобки
{}вокруг глобальных переменных и переменных экземпляров класса при интерполяции строк.[ссылка]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для интерполируемых объектов, он вызывается автоматически при интерполяции. [ссылка]# плохо message = "This is the #{result.to_s}." # хорошо message = "This is the #{result}."
-
Не применяйте метод
String#+, когда вам нужно собрать вместе большие отрезки строк. Вместо этого используйтеString#<<. Конкатенация изменяет экземпляр строки и всегда работает быстрее, чемString#+, который создает целую кучу новых строковых объектов. [ссылка]# хорошо и быстро html = '' html << '<h1>Page title</h1>' paragraphs.each do |paragraph| html << "<p>#{paragraph}</p>" end
-
Избегайте метода
String#gsubв случаях, когда можно использовать более быстрый и специализированный альтернативный метод. [link]url = 'http://example.com' str = 'lisp-case-rules' # плохо url.gsub("http://", "https://") str.gsub("-", "_") # хорошо url.sub("http://", "https://") str.tr("-", "_")
-
При использовании многострочных HEREDOC не забывайте, что пробелы в начале строк тоже являются частью создаваемой строки. Примером хорошего стиля является применение техник, основывающихся на ограничителях, для удаления ненужных пробелов. [ссылка]
code = <<-END.gsub(/^\s+\|/, '') |def test | some_method | other_method |end END #=> "def test\n some_method\n other_method\nend\n"
Многие люди, встречаясь с проблемой, думают: "Я знаю решение, я применю регулярные выражения!" Теперь у них две проблемы.
-- Джейми Цавински / Jamie Zawinski
-
Не используйте регулярные выражения, когда вам нужно просто найти в строке подстроку:
string['text']. [ссылка] -
В простейших случаях вы просто можете использовать индексирование строк. [ссылка]
match = string[/regexp/] # Возвращает найденные совпадения. first_group = string[/text(grp)/, 1] # Возвращает совпадения выделенной группы. string[/text (grp)/, 1] = 'replace' # string => 'text replace'
-
Используйте группировку без сохранения, если вы не планируете использовать содержание выделенной скобками группы. [ссылка]
/(first|second)/ # плохо /(?:first|second)/ # хорошо
-
Откажитесь от использования наследия Перла вроде мистических переменных, обозначающих группы совпадений (
$1,$2и т.д.). Вместо этого используйтеRegexp.last_match(n). [ссылка]/(regexp)/ =~ string ... # плохо process $1 # хорошо process Regexp.last_match(1)
-
Применение пронумерованных групп совпадений может быть сложной задачей. Вместо этого используйте поименованные группы с говорящими именами.[ссылка]
# плохо /(regexp)/ =~ string ... process Regexp.last_match[1] # хорошо /(?<meaningful_var>regexp)/ =~ string ... process meaningful_var
-
Классы символов используют лишь небольшой набор метасимволов, которые вам придется обрабатывать:
^,-,\,], поэтому нет нужды экранировать.или скобки внутри[]. [ссылка] -
Будьте осторожны с символами
^и$, так как они обозначают начало/конец строки в тексте, а не строчного литерала. Если вам надо обозначить начало и конец литерала, то используйте\Aи\z. Не путайте\Zи\z:\Zявляется эквивалентом/\n?\z/. [ссылка]string = "some injection\nusername" string[/^username$/] # есть совпадение string[/\Ausername\z/] # нет совпадения
-
Используйте модификатор
xдля сложных регулярных выражений. Он поможет вам сделать выражения удобочитаемыми и позволит добавлять комментарии. Не забывайте при этом, что пробелы в данном случае игнорируются. [ссылка]regexp = / start # какой-то текст \s # знак пробела (group) # первая группа (?:alt1|alt2) # некоторая дизъюнкция end /x
-
В случае сложных замен либо подстановок
sub/gsubможно использовать с блоком или хешем параметров. [ссылка]
-
Используйте
%()(это сокращение от%Q) для строк без переносов, в которых реализуется интерполяция и присутствуют двойные кавычки. Для строк с переносами лучше используйте формат HERE Doc. [ссылка]# плохо (интерполяция не нужна) %(<div class="text">Some text</div>) # должно быть '<div class="text">Some text</div>' # плохо (нет двойных кавычек) %(This is #{quality} style) # должно быть "This is #{quality} style" # плохо (строка с переносами) %(<div>\n<span class="big">#{exclamation}</span>\n</div>) # лучше применить HERE Doc # хорошо (необходима интерполяция, присутствуют кавычки, нет переносов) %(<tr><td class="name">#{name}</td>)
-
Избегайте
%q, если это не случай строки с символами кавычек'и"одновременно Обычные строки читаются проще, и их следует использовать, если нет излишне большого количества символов, которые нужно будет экранировать. [ссылка]# плохо 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для регулярных выражений, которые обрабатывают хотя бы один символ/, в остальных случаях используйте стандартный синтаксис. [ссылка]
# плохо
%r{\s+}
# хорошо
%r{^/(.*)$}
%r{^/blog/2011/(.*)$}-
Откажитесь от использования
%xкроме случаев, когда вы хотите вызвать внешнюю команду с обратными кавычками в теле (что само по себе маловероятно). [ссылка]# плохо date = %x(date) # хорошо date = `date` echo = %x(echo `date`)
-
Старайтесь избегать
%s. По общепринятому мнению, предпочтительным способом определения символа с пробелами в имени является:"some string". [ссылка] -
Используйте
()в качестве ограничителей для всех литералов со знаком%кроме%r. Так как круглые скобки очень часто используются в самих регулярных выражениях, во многих случаях менее частый символ{может быть лучшим выбором для ограничителя (разумеется, с учетом смысла регулярного выражения). [ссылка]# плохо %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__, чтобы информация о стеке вызова была осмысленной:[ссылка]class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
#define_methodпредпочтительнее, чем#class_eval { def ... }
-
При использовании
#class_eval(или других#eval) с интерполяцией строк обязательно добавляйте комментарий, который будет наглядно показывать, как интерполированные значения будут выглядеть (примеры, используемые в исходном коде Rails):[ссылка]# из 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}(*params, &block) # def capitalize(*params, &block) to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block) end # end def #{unsafe_method}!(*params) # def capitalize!(*params) @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в конце ваших выражений; -
делегируйте вызовы понятным, "немагическим" методам:
# плохо def method_missing?(meth, *params, &block) if /^find_by_(?<prop>.*)/ =~ meth # ... lots of code to do a find_by else super end end # хорошо def method_missing?(meth, *params, &block) if /^find_by_(?<prop>.*)/ =~ meth find_by(prop, *params, &block) else super end end # Самым лучшим будет все же использование `#define_method`, # так как каждый видимый аргумент будет определен.
-
-
Пишите код, не дающий предупреждений при вызове
ruby -w.[ссылка] -
Не используйте хеши в качестве необязательных параметров. Возможно, ваш метод просто делает слишком много. Это не касается, однако, методов инициализации объектов. [ссылка]
-
Старайтесь не писать методы длиннее 10 строк. В идеальном случае большинство методов должны быть короче 5 строк. Пустые строки не подсчитываются.[ссылка]
-
Не создавайте методы с более чем тремя-четырьмя параметрами.[ссылка]
-
Если вам действительно нужны глобальные функции, включайте их в модуль Kernel и сделайте их приватными. [ссылка]
-
Используйте переменные модулей вместо глобальных переменных.[ссылка]
# плохо $foo_bar = 1 # хорошо module Foo class << self attr_accessor :bar end end Foo.bar = 1
-
Используйте
OptionParserдля анализа сложных аргументов командной строки иruby -sдля элементарных случаев. [ссылка] -
Используйте вариант
Time.now, а неTime.new, когда хотите получить текущее значение системного времени. [ссылка] -
Пишите код в функциональном стиле без изменения значений, когда это подходит по смыслу.[ссылка]
-
Не изменяйте значения параметров, если только это не есть цель метода. [ссылка]
-
Старайтесь не создавать вложенные структуры с уровнем вложения больше третьего. [ссылка]