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

Рекомендации по разработке на 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Загрузка примера кода…