Перейти к основному содержимому

Рекомендации по разработке на Ruby

Разработчику Архитектору

Рекомендации по разработке на Ruby

Введение в культуру кода Ruby

Ruby обладает уникальной философией, выраженной в принципе "программист счастлив важнее, чем компьютер счастлив". Эта философия проявляется в синтаксисе языка, стандартной библиотеке и принятых в сообществе практиках. Код на Ruby ценит выразительность, читаемость и элегантность. Хороший Ruby-код читается как естественный язык, минимизирует шаблонные конструкции и предпочитает явные намерения неявным соглашениям.

Сообщество Ruby разработало устоявшиеся соглашения, закреплённые в стилевых гидах (например, от команды GitHub и от сообщества Ruby on Rails). Эти соглашения обеспечивают единообразие кодовой базы в разных проектах и упрощают совместную разработку.


Соглашения об именовании

Основные правила именования

Имена в коде передают смысл и намерения разработчика. Следование единым правилам именования упрощает чтение и понимание кода.

Элемент языкаСтильПримеры
Переменные, методыsnake_caseuser_count, calculate_total
КонстантыSCREAMING_SNAKE_CASEMAX_RETRIES, DEFAULT_TIMEOUT
Классы, модулиPascalCaseUserService, PaymentGateway
Символы:snake_case:user_id, :created_at

Специальные суффиксы методов

Ruby использует соглашения о суффиксах для передачи семантики методов:

  • Методы с вопросительным знаком (?) возвращают логическое значение:
def active?
status == :active
end

def empty?
items.size.zero?
end
  • Методы с восклицательным знаком (!) изменяют объект на месте или могут выбрасывать исключения:
# Изменение на месте
names.map!(&:upcase)

# Выбрасывание исключения при ошибке
def save!
raise RecordInvalid unless valid?
save
end
  • Методы с суффиксом = устанавливают значение атрибута:
def name=(value)
@name = value.strip
end

Именование параметров блоков

Параметры блоков получают короткие, осмысленные имена, отражающие сущность элемента:

# Хорошо
users.each { |user| process(user) }
orders.map { |order| order.total }
files.select { |file| file.readable? }

# Избегать однобуквенных имён без контекста
items.each { |i| process(i) } # Неясно, что представляет i

Форматирование и оформление кода

Отступы и пробелы

  • Используйте два пробела для отступов. Табуляция не применяется.
  • Добавляйте один пробел вокруг операторов:
x = 1 + 2
result = items.map { |item| item.value * 2 }
  • Не добавляйте пробелы внутри скобок:
method(arg1, arg2) # Хорошо
method( arg1, arg2 ) # Избегать
  • Добавляйте пробел после запятых:
[1, 2, 3]
{ name: "Alice", age: 30 }

Длина строк и переносы

Ограничивайте длину строк 80–100 символами. При необходимости переноса:

  • Выравнивайте аргументы относительно открывающей скобки:
def create_user(
email:,
password:,
first_name:,
last_name:,
role: :user
)
# реализация
end
  • Для цепочек методов переносите каждый вызов на новую строку с отступом:
users
.active
.where(region: "EU")
.order(created_at: :desc)
.limit(100)
.to_a
  • Для хэшей с несколькими ключами используйте многострочный формат:
config = {
host: "api.example.com",
port: 443,
timeout: 30,
retries: 3
}

Расположение фигурных скобок

Для однострочных блоков допустимы фигурные скобки:

names = users.map { |user| user.name }

Для многострочных блоков используйте ключевые слова do/end:

users.each do |user|
logger.info "Processing user: #{user.id}"
process(user)
update_status(user)
end

Структура проекта

Стандартная структура Ruby-приложения

my_app/
├── bin/ # Исполняемые файлы
├── lib/ # Основной код приложения
│ ├── my_app/
│ │ ├── version.rb
│ │ ├── user.rb
│ │ └── services/
│ └── my_app.rb # Точка входа для загрузки
├── spec/ # Тесты (при использовании RSpec)
│ ├── my_app/
│ │ └── user_spec.rb
│ └── spec_helper.rb
├── Gemfile # Зависимости
├── Gemfile.lock
├── Rakefile # Задачи Rake
└── README.md

Структура Rails-приложения

app/
├── controllers/ # Контроллеры
├── models/ # Модели
├── views/ # Шаблоны представлений
├── helpers/ # Хелперы
├── services/ # Сервисные объекты
├── forms/ # Формы
├── queries/ # Объекты запросов
└── policies/ # Политики авторизации

lib/
├── Задачи/ # Rake задачи
└── extensions/ # Расширения стандартной библиотеки

config/
├── initializers/ # Инициализаторы
└── environments/ # Конфигурации окружений

Организация модулей и пространств имён

Группируйте связанные классы в модули, отражающие предметную область:

Код ITЗагрузка примера кода…

Избегайте чрезмерной вложенности модулей. Два–три уровня вложенности обычно достаточны для поддержания чистоты пространства имён.


Проектирование классов и модулей

Принцип единственной ответственности

Каждый класс решает одну задачу. Класс с именем, содержащим союз "и" (UserAndOrderManager), часто нарушает этот принцип.

Код ITЗагрузка примера кода…


Использование модулей для примесей

Модули предоставляют механизм повторного использования поведения без наследования:

Код ITЗагрузка примера кода…


Композиция вместо наследования

Предпочитайте композицию наследованию для построения сложного поведения:

Код ITЗагрузка примера кода…


Инкапсуляция состояния

Скрывайте внутреннее состояние объекта за интерфейсом методов:

Код ITЗагрузка примера кода…

Метод items объявлен приватным, предотвращая прямое изменение коллекции извне.


Проектирование методов

Длина методов

Методы должны умещаться на один экран (15–20 строк). Длинные методы декомпозируются на более мелкие с осмысленными именами:

# Слишком длинный метод
def process_order(order)
# 50 строк кода обработки заказа
end

# Декомпозиция
def process_order(order)
validate_order(order)
reserve_inventory(order)
calculate_pricing(order)
create_payment(order)
send_confirmation(order)
end

Количество параметров

Ограничивайте количество параметров метода тремя–четырьмя. Для большего числа параметров используйте хэш или объект параметров:

Код ITЗагрузка примера кода…


Чистые функции

Стремитесь к созданию чистых функций — методов без побочных эффектов, которые всегда возвращают одинаковый результат для одинаковых входных данных:

# Чистая функция
def calculate_discount(price, percentage)
price * (1 - percentage / 100.0)
end

# Функция с побочным эффектом
def calculate_and_log_discount(price, percentage)
result = price * (1 - percentage / 100.0)
logger.info "Discount calculated: #{result}" # Побочный эффект
result
end

Чистые функции проще тестировать, отлаживать и повторно использовать.


Обработка ошибок

Используйте исключения для обработки исключительных ситуаций, а не для управления нормальным потоком выполнения:

Код ITЗагрузка примера кода…

Создавайте специфичные классы исключений для предметной области:

class PaymentError < StandardError; end
class InsufficientFundsError < PaymentError; end
class InvalidCardError < PaymentError; end

begin
payment.process
rescue InsufficientFundsError => e
notify_user_about_insufficient_funds(e.amount)
rescue InvalidCardError => e
request_new_card_details(e.card_type)
end

Работа с коллекциями и блоками

Предпочтение функциональных итераторов

Используйте методы map, select, reduce вместо циклов for или while:

# Императивный подход
result = []
users.each do |user|
result << user.name.upcase if user.active?
end

# Функциональный подход
result = users
.select(&:active?)
.map { |user| user.name.upcase }

Цепочки методов

Выстраивайте цепочки методов вертикально для улучшения читаемости:

# Горизонтальная цепочка (трудно читать)
orders = Order.where(status: :completed).where("created_at > ?", 1.month.ago).order(created_at: :desc).limit(100)

# Вертикальная цепочка
orders = Order
.where(status: :completed)
.where("created_at > ?", 1.month.ago)
.order(created_at: :desc)
.limit(100)

Ленивые вычисления

Используйте lazy для обработки больших коллекций без загрузки всех элементов в память:

File.foreach("large.log")
.lazy
.select { |line| line.include?("ERROR") }
.first(10)
.each { |line| puts line }

Комментарии и документация

Самодокументируемый код

Стремитесь к созданию кода, который не требует комментариев благодаря осмысленным именам и структуре:

# Требует комментария
def m(d)
d + 7
end

# Самодокументируемый
def next_week(date)
date + 7.days
end

Документация публичного интерфейса

Документируйте публичные методы и классы с использованием формата YARD:

# Обрабатывает платеж через шлюз оплаты
#
# @param order [Order] заказ для обработки
# @param payment_method [String] метод оплаты (например, "credit_card")
# @return [PaymentResult] результат обработки платежа
# @raise [InsufficientFundsError] если средств недостаточно
# @raise [InvalidCardError] если данные карты недействительны
def process_payment(order, payment_method:)
# реализация
end

Комментарии для объяснения "почему"

Комментарии должны объяснять причины принятых решений, а не описывать "что" делает код:

# Плохой комментарий — описывает очевидное
# Увеличиваем счётчик на единицу
counter += 1

# Хороший комментарий — объясняет причину
# Используем 3 попытки вместо стандартных 5 из-за ограничений внешнего API
# (см. документацию провайдера, раздел 4.2)
3.times do
break if send_request
sleep 1
end

Тестирование

Структура тестов

Организуйте тесты по структуре, отражающей тестируемый код:

spec/
├── models/
│ └── user_spec.rb
├── services/
│ └── payment_service_spec.rb
├── controllers/
│ └── orders_controller_spec.rb
└── support/
└── shared_examples/
└── authenticatable.rb

Стиль написания тестов

Пишите тесты, которые читаются как спецификация поведения:

Код ITЗагрузка примера кода…


Тестирование граничных случаев

Включайте тесты для граничных значений и нестандартных ситуаций:

Код ITЗагрузка примера кода…


Инструменты обеспечения качества

RuboCop

RuboCop обеспечивает соблюдение стилевых соглашений и выявляет потенциальные ошибки. Настройте файл .rubocop.yml под требования проекта:

Код ITЗагрузка примера кода…


EditorConfig

Файл .editorconfig обеспечивает единообразие отступов и кодировки между разными редакторами:

Код ITЗагрузка примера кода…


CI/CD для Ruby

Настройте непрерывную интеграцию для автоматического запуска тестов и проверки стиля:

Код ITЗагрузка примера кода…


Практические примеры

Рефакторинг длинного метода

Исходный код:

Код ITЗагрузка примера кода…

Рефакторинг с декомпозицией:

Код ITЗагрузка примера кода…


Использование сервисных объектов

Сервисные объекты инкапсулируют сложную бизнес-логику, извлекая её из контроллеров и моделей:

Код ITЗагрузка примера кода…