Вы когда-нибудь видели, как разработчик пытается отладить код, добавляя puts на каждом шаге? Это как искать чёрную кошку в тёмной комнате, особенно если кошка — это утечка памяти в продакшене. К счастью, Ruby даёт нам целый арсенал инструментов для самодиагностики. Сегодня разберём три кита: Debug, Benchmark и StackProf — и научимся не тыкать в код наугад.
🐞 Debug: когда puts уже не смешно
Теория: зачем нужен дебаггер?
puts — это как кричать “Эй, я здесь!” в космосе. А дебаггер — это GPS с голосовыми подсказками. В Ruby 3.1+ появился встроенный debug гем, который делает отладку осознанной.
Проблема:
# app/services/payment_processor.rb
def process
validate_amount! # падает где-то здесь... но где именно?
charge_card
create_invoice
end
Решение:
require "debug"
def process
debugger # точка остановки
validate_amount!
binding.break # альтернативный синтаксис
charge_card
end
Запускаем с rdbg и получаем:
- интерактивную консоль
- просмотр стека вызовов
- изменение переменных на лету
Антипаттерны дебага
- “puts-driven development”
puts "Дошёл сюда, user_id = #{user.id}" # А потом забыли удалить - Дебаг в продакшене
Никогда не оставляйтеbinding.pryв коде. Серьёзно, никогда.
⏱ Benchmark: измеряем правильно
Теория: почему Time.now — плохая идея
Замер времени через Time.now — это как измерять температуру подмышкой: примерные цифры, но не точные. В Ruby есть Benchmark из стандартной библиотеки.
Проблемный код:
start = Time.now
1000.times { User.where(active: true).to_a }
puts "Затрачено: #{Time.now - start} секунд"
Правильный замер:
require "benchmark"
result = Benchmark.measure do
1000.times { User.where(active: true).load }
end
puts result
#=> 0.020000 0.000000 0.020000 ( 0.025678)
Вывод:
- user CPU time
- system CPU time
- общее время
- реальное время
Практика: Benchmark IPS
Для сравнения двух подходов используем benchmark-ips:
require "benchmark/ips"
Benchmark.ips do |x|
x.report("with to_a") { User.where(active: true).to_a }
x.report("with load") { User.where(active: true).load }
x.compare!
end
Вывод:
Comparison:
with load: 462.5 i/s
with to_a: 428.6 i/s - 1.08x slower
🔥 StackProf: находим узкие места
Теория: профайлинг — это не роскошь
Когда приложение тормозит, но непонятно где — нужен профайлер. stackprof — sampling-профайлер для Ruby, который показывает:
- Где тратится CPU
- Какие методы вызываются чаще всего
- Где память утекает как вода
Установка:
gem install stackprof
Запуск:
require "stackprof"
StackProf.run(mode: :cpu, out: "tmp/stackprof.dump") do
# ваш медленный код
end
Анализ результатов
- Текстовый отчёт:
stackprof tmp/stackprof.dump --textПокажет что-то вроде:
Samples: 1000 (0.00% miss rate) GC: 50 (5.00%) TOTAL (pct) SAMPLES (pct) FRAME 300 (30.0%) 200 (20.0%) ActiveRecord::Relation#to_a - Flamegraph:
stackprof --flamegraph tmp/stackprof.dump > tmp/flamegraph.jsonИ открываем в https://speedscope.app
Боль: когда профайлинг врет
-
Слишком короткие тесты
Если код выполняется меньше 100мс, sampling может дать некорректные результаты. -
Эффект наблюдателя
Сам факт профайлинга замедляет выполнение кода.
🧪 Комбо: Debug + Benchmark + StackProf
Реальный кейс:
API endpoint тормозит с 200мс до 2с под нагрузкой.
-
Debugger
Ставим точку остановки перед медленным участком, смотрим состояние объектов. - Benchmark
Замеряем отдельные компоненты:Benchmark.measure { Order.preload(:items).where(user: user) } - StackProf
Запускаем профайлер на полном запросе, находим N+1 запросы или рекурсию.
🎁 Выводы
- Debug — для точечной отладки сложных сценариев
- Benchmark — когда нужно сравнить два подхода
- StackProf — для поиска узких мест в продакшене
Как сказал один мудрый разработчик:
“Если бы Ruby умел говорить, он бы сам сказал, где у него болит. Но пока что у нас есть эти инструменты.”
P.S. И да, удалите наконец эти puts из кода.