RubyMiddleExperience

Какие ошибки делают разработчики, переходящие на Ruby с другого языка или стека?

Переходящие на Ruby чаще всего переносят паттерны Java/PHP (процедурный стиль, ранние return-guards, статическую типизацию), недооценивают GVL и пишут Rails-код без понимания, когда выполняются callbacks.

1. Перенос стиля из Java или PHP

Разработчик с Java-бэкграундом пишет:

# Java-style Ruby (плохо):
class UserService
  def self.create_user(name, email)
    user = User.new
    user.name = name
    user.email = email
    if user.valid?
      user.save
      return user
    else
      return nil
    end
  end
end
# Идиоматичный Ruby:
class UserService
  def self.create_user(attrs)
    User.create(attrs)  # ActiveRecord уже делает valid? + save
  end
end

# Или с явной обработкой ошибок:
User.create!(name: name, email: email)  # бросает если invalid

2. Непонимание, когда выполняются Rails-callbacks

Часто переходящие думают, что before_save выполняется при любом обращении к объекту:

class Order < ApplicationRecord
  before_save :calculate_total

  private

  def calculate_total
    self.total = line_items.sum(&:price)
  end
end

# Проблема: update_column обходит callbacks!
order.update_column(:status, 'shipped')  # calculate_total НЕ вызовется
order.update(status: 'shipped')          # calculate_total вызовется

3. Игнорирование GVL при работе с потоками

# Ошибка: ожидание параллельного CPU-bound выполнения
results = data_chunks.map do |chunk|
  Thread.new { expensive_cpu_computation(chunk) }
end.map(&:value)
# Потоки не ускоряют CPU-bound задачи в MRI!

# Правильно для CPU-bound:
results = data_chunks.map do |chunk|
  fork { expensive_cpu_computation(chunk) }
end
# или используйте Ractor (Ruby 3+)

4. Monkey-patching стандартных классов в неправильном месте

# Опасно: добавление метода к String глобально
class String
  def to_slug
    downcase.gsub(/\s+/, '-')
  end
end

# Лучше: refinements — ограниченный scope
module SlugRefinement
  refine String do
    def to_slug
      downcase.gsub(/\s+/, '-')
    end
  end
end

class Article
  using SlugRefinement  # активно только в этом классе
end

5. Использование символов и строк непоследовательно

# Часто от PHP/Python — перемешивание:
params = { 'name' => 'Alice', 'role' => 'admin' }

# В Rails params — HashWithIndifferentAccess, в чистом Ruby — нет:
params[:name]    # => nil (если обычный Hash со строками-ключами)
params['name']   # => 'Alice'

# Договоритесь о стандарте:
config = { host: 'localhost', port: 5432 }  # символы для внутренних Hash

6. Игнорирование идиом Enumerable

# Императивно (из PHP/Java):
result = []
users.each do |u|
  result << u.name if u.active?
end

# Идиоматично:
result = users.select(&:active?).map(&:name)

Подводные камни

  • rescue Exception вместо rescue StandardError — перехватывает SystemExit и Interrupt. Типичная ошибка из PHP/Python-привычки писать catch(Exception).
  • Неиспользование Bundler — разработчики из Python иногда ставят gems глобально. Всегда работайте через Gemfile + bundle exec.
  • attr_accessor для всего — Java-разработчики открывают все поля через accessor. В Ruby инкапсуляция через attr_reader + приватные setters.
  • Забытый frozen_string_literal — в production Rails-приложениях добавьте magic comment в Gemfile или через initializer для снижения аллокаций строк.
  • Отсутствие тестов на поведение callbacks — callbacks тестируйте интеграционно, а не только unit; иначе пропускаете edge cases с update_column, touch, import.

What hurts your answer

  • Перечислять ошибки без объяснения причин
  • Не отличать beginner mistakes от production failure modes
  • Не предлагать процесс, который предотвращает повторение ошибок

What they're listening for

  • Знает типичные ошибки при работе с Ruby
  • Понимает причины ошибок
  • Предлагает практики prevention и early detection

Related topics