8 советов по улучшению кода на Руби

Это перевод оригинальной статьи от Amanda Fawcett.

Источник: https://www.educative.io/blog/ruby-best-practices
Источник: https://www.educative.io/blog/ruby-best-practices

Разработчики всяческих программ зачастую думают, что продуктивность означает способность писать код как можно быстрее. Но истинная продуктивность в разработке программ сводится к качеству вашего кода.

Руби — это уникальный универсальный язык программирования, на котором можно создать всё что угодно. Руби, хоть он и прост для изучения, требует глубоких знаний и опыта для полного раскрытия его потенциала. Так что сегодня мы собрали наши лучшие советы, хитрости и рекомендации, которые помогут вам максимально эффективно создавать код на Руби.

Совет №1: Избегайте скрытых структур

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

class ObscuringReferences def initialize(data) @data = data end def calculate data.collect do |cell| cell[0] + (cell[1] * 2) end end private attr_reader :data end ObscuringReferences.new([[100, 25], [250, 22], [984, 30], [610, 42]])

Что за проблема здесь? Мы используем индексы массива, чтобы получить информацию в методе calculate. Однако достаточно сложно сказать, какую именно информацию мы получаем, потому что 0 и 1 совсем не определены и поэтому практически бесполезны.

Мы могли бы использовать константы, чтобы придать больше смысла нашим переменным, как здесь:

class RevealingReferences HEIGTH = 0 WIDTH = 1 def initialize(data) @data = data end def calculate data.collect do |value| value[HEIGTH] + (value[WIDTH] * 2) end end private attr_reader :data end

Мы также можем избежать скрытых структур Ruby, используя Struct для осмысленного представления данных.

Совет №2: Избегайте while !(not)

Цикл while !(not) используется для проверки кода до тех пор, пока условие не будет выполнено. В следующем примере условие буквально звучит так: «пока не завершилась загрузка (выполнять блок кода)».

while !download.is_finished? spinner.keep_spinning end

Это может привести к путанице, потому что не очень естественно думать одновременно позитивно и негативно. Вместо этого, мы могли бы использовать цикл until, который по сути является негативной формой цикла while.

until download.is_finished? spinner.keep_spinning end

В этой пересмотренной версии, условие until выглядит более натурально: «пока завершается загрузка (выполнять блок кода)». Это может помочь сделать ваш код более читабельным и чистым.

Примечание переводчика:

На русском не совсем понятно, что хотела сказать автор. Я не хотел далеко отходить от оригинала, чтобы сильно не переврать смысл. Но от себя хочу добавить, что в целом стоит избегать логического «не» в условиях, так как оно, зачастую, не воспринимается при попытке прочитать код, что может привести к трудностям с отладкой логических выражений.

Совет №3: Используйте loop do вместо while(true)

Цикл loop do предлагает, во многих случаях, более чистый синтаксис, чем условие while(true). Давайте сравним две версии кода:

# while def play get_guess while true ... ... end end
# loop do def play get_guess loop do ... ... end end

В общем, цикл loop do предлагает более чистый и лучший синтаксис при работе с внешними итераторами. Ещё одно преимущество заключается в том, что цикл loop do позволяет вам итерировать две коллекции одновременно, что приводит к чистому и лёгкому коду на Руби.

iterator = (1..9).each iterator_two = (1..5).each loop do puts iterator.next puts iterator_two.next end #=> 1,1,2,2,3,3,4,4,5,5,6.

Примечание переводчика:

Нужно также отметить, что цикл loop гораздо медленнее цикла while:

require 'benchmark' iterations = 10_000 Benchmark.bm(27) do |bm| bm.report('цикл loop') do iterations.times do iter = 0 loop do iter += 1 break if iter == 1_000 end end end bm.report('цикл for') do iterations.times do for iter in 0..1_000 end end end bm.report('цикл while') do iterations.times do iter = 0 while true iter += 1 break if iter == 1_000 end end end end # Результаты: user system total real цикл loop 0.348371 0.000000 0.348371 ( 0.348539) цикл for 0.283495 0.000000 0.283495 ( 0.283638) цикл while 0.106019 0.000000 0.106019 ( 0.106072)

Приблизительно в 3 раза. Как и у цикла for, между прочим. Но это не означает, что нужно полностью отказываться от цикла loop. Просто нужно понимать его недостатки и пользоваться преимуществами, наподобие того, что было приведено.

Совет №4: Используйте «или-присваивание» ||= в ваших методах

«Или-присваивание» — хороший способ писать лаконичный код на Руби. Оно эквивалентно следующему коду: a || a = b

a ||= b работает как оператор условного присваивания, поэтому если a не определено или ложно, то в него запишется результат из b.

Этот оператор отлично подходит для создания методов в ваших классах, особенно для вычислений.

def total @total ||= (1..100000000).to_a.inject(:+) end

Совет №5: Исправляйте сборщик мусора (Garbage Collector)

Сборщик мусора Ruby (Garbage Collector — GC) известен своей медленной работой, особенно в версиях до 2.1. Алгоритм сборщика мусора Ruby — «маркируй и очищай» (самый медленный для сборщиков мусора), и он должен останавливать приложение во время процессов сборки мусора.

Чтобы исправить это, мы можем включить настраиваемые расширения GC. Это очень поможет увеличить скорость при масштабировании вашего приложения Ruby.

Вы можете настроить переменные основываясь на вашей версии Руби. Также, на GitHub можно отыскать много чего полезного, если вы не хотите писать этот код сами. Вот три примера:

  • RUBY_GC_HEAP_INIT_SLOTS: начальные слоты распределения
  • RUBY_GC_HEAP_FREE_SLOTS: подготовить хотя бы указанное количество слотов после сборки мусора и выделить слоты, если их недостаточно
  • RUBY_GC_HEAP_GROWTH_MAX_SLOTS: ограничить скорость выделения до этого количества слотов

Примечание переводчика:

Здесь она как-то совсем по верхам прошлась. Лично мне ничего не понятно, поэтому будет ещё статья или серия статей на тему оптимизации Garbage Collector.

Совет №6: Используйте Hash[…] для создания хэша из списка значений

Вы можете легко создавать хэши из списков на Руби, используя Hash[…]. Например:

Hash['key1', 'value1', 'key2', 'value2'] # => {"key1"=>"value1", "key2"=>"value2"}

Представьте, мы собрали данные в табличном формате. Они организованы в виде массива с названиями столбцов, и массива массивов представляющих значения в строках:

columns = [ "Name", "city", "employed"] rows = [ [ "Matt", "Seattle", true ], [ "Amy", "Chicago", true], [ "Nice", "Lahore", false ] ]

Ключами для наших окончательных хэшей будет массив столбцов, который содержит имена наших столбцов, а табличные данные — это наш массив массивов, содержащий данные строк. Мы хотим создать массив хэшей, чтобы в них каждый ключ был именем столбца, а строка — значением. Можно сделать это двумя способами:

# Сложный способ results = [] for i in (0...rows.length) h = {} for j in (0...columns.length) h[columns[j]] = rows[i][j] end results << h end results # Результаты: [{"Name"=>"Matt", "city"=>"Seattle", "employed"=>true}, {"Name"=>"Amy", "city"=>"Chicago", "employed"=>true}, {"Name"=>"Nice", "city"=>"Lahore", "employed"=>false}] # Простой способ: correlated = rows.map{|r| Hash[ columns.zip(r) ] } # Результаты: [{"Name"=>"Matt", "city"=>"Seattle", "employed"=>true}, {"Name"=>"Amy", "city"=>"Chicago", "employed"=>true}, {"Name"=>"Nice", "city"=>"Lahore", "employed"=>false}]

Совет №7: Избегайте применять в циклах медленно работающие методы

При работе с циклами в Ruby важна скорость. Поэтому нам нужно избегать более медленных методов и находить альтернативы, которые помогут сделать работу нашей программы более производительной.

Например, известно, что метод Date#parse известен своей низкой производительностью, так как он ожидает получить дату сразу в нескольких форматах и проверяет каждый из них. Вместо него, лучше будет прямо указать ожидаемый формат даты используя метод Date#strptime — он отработает в несколько раз быстрее. Предположим, что мы хотим работать с датой в формате «дд/мм/гггг»:

Date.strptime(date, '%d/%m/%Y')

С точки зрения проверки типов, использование методов Object#class, Object#kind_of? и Object#is_a? также может привести к снижению производительности, если применять их в цикле. Вместо этого лучше сохранять в переменную результат вызова такого метода вне цикла, а в цикле использовать переменную, если такое возможно.

Совет №8: Следуйте лучшим примерам кода на Руби

Такие примеры работы с Ruby помогают нам писать лаконичный код, с которым легко работать в дальнейшем. Давайте посмотрим на некоторые из них, которые вы можете начинать использовать уже сегодня:

  • Когда это возможно, используйте гемы, вместо того, чтобы писать код с нуля для получения более оптимизированного кода
  • Избегайте использования цикла for — вместо этого, старайтесь применять метод each с блоком
  • Используйте тернарный оператор ( ? : ) для написания более короткого кода
  • Вместо длинных цепочек if—elsif—end, используйте case—when и unless/until/while
  • Используйте оператор «звёздочка» * для группировки аргументов метода в массив, когда ваши методы имеют нефиксированное количество аргументов (то же самое можно сказать и про ** для группировки аргументов метода в хэш — прим.пер.)
  • Для именования ключей в хэше используйте символы (Symbol) вместо строк (String) (использование символов повышает производительность вашего кода — прим.пер.)
  • Избегайте использования комментариев, если в них нет необходимости. В идеале ваш код должен быть достаточно понятным сам по себе, чтобы не нуждаться в комментариях
  • Используйте двойной восклицательный знак !!, чтобы создавать методы, которые определяют, существует ли полученное значение
  • Используйте APM (??? — прим.пер.) во время разработки. Это поможет вам сразу узнать, когда что-то новое в вашем коде вызвало падение производительности
  • При создании методов помните два простых правила: метод должен делать только одну вещь, и ему нужно четкое имя, отражающее эту единственную цель

Что ещё стоит изучить

Эти советы и рекомендации помогут вам писать более читаемый и понятный код Ruby для повышения производительности. Ruby — отличный язык с множеством интересных синтаксических приемов. Чтобы максимально использовать этот язык, вы должны изучить всё, что можете о нем.

Вот на что ещё стоит обратить внимание работая с Руби:

  • Методы параллелизма и многопоточности
  • Отслеживание запущенных процессов
  • Когда использовать nil
  • Преобразование базы чисел

Спасибо, что прочитали до конца!

Начать дискуссию