Бывает так, что запускаешь тесты, а они падают там, где вроде бы всё должно работать. В логах — только сухая ошибка, без контекста. Открываешь код, смотришь на участок кода, где произошёл сбой, и начинаешь гадать:
не те входные данные из-за плохого контракта
возникла неучтённая крайняя ситуация
логика в принципе неверная, хотя для большинства случаев всё «вроде бы ок».
Ситуация осложняется тем, что по какой-то причине нет отладчика (pry, byebug и т.п.). Начинаешь на скорую руку выводить переменные puts-ами, но понимание проблемы все равно не приходит.
Вот тут‑то и выручает binding.irb — встроенный в Ruby способ запустить интерактивную консоль IRB прямо в точке вызова.
Что за Binding?
Binding — это встроенный класс Ruby, позволяющий захватить и сохранить контекст выполнения кода в определённый момент времени. Метод binding (также встроен в Ruby) возвращает экземпляр этого класса.
3.2.0 :002 > obj = binding => #<Binding:0x000000010e35b718> 3.2.0 :003 > obj.class => Binding
Становится доступен метод irb, который запускает IRB (интерактивная среда выполнения кода Ruby).
Для работы с Ruby можно написатьirb, и вы попадете в ту же интерактивную среду.
~ irb 3.2.0 :001 > name = "John" => "John" 3.2.0 :002 > age = 35 => 35 3.2.0 :003 > start_info = "#{name} is #{age} years old" => "John is 35 years old" 3.2.0 :004 > name.class => String 3.2.0 :006 > start_info => "John is 35 years old" 3.2.0 :007 > exit ~
Кто знаком с JavaScript, узнает Node.js REPL (Read‑Eval‑Print Loop).
Как дебажим?
Устанавливаем binding.irb в той части кода, где хотим остановить выполнение. Запускаем и попадаем в консоль IRB.
Смотрим на входные данные:
1: def call_user(name) => 2: binding.irb 3: puts "Hello #{name}" 4: end 5: 6: call_user("Larisa") 3.2.0 :001 > name => "Larisa" 3.2.0 :002 > exit Hello Larisa
Проверяем попадание в условие:
def call_user(name) if name.length < 2 binding.irb puts "Name is too short" end puts "Hello #{name}" end call_user("Larisa")
Вызываем необходимые методы:
1: def call_user(name) 2: if name.length < 2 => 3: binding.irb 4: puts "Name is too short" 5: end 6: 7: puts "Hello #{name}" 8: end 3.2.0 :001 > name.length => 1
1: def call_user(name) 2: if name.length < 2 => 3: binding.irb 4: puts "Name is too short" 5: end 6: 7: puts "Hello #{name}" 8: end 3.2.0 :001 > name.class => String
Доступны любые манипуляции с контекстом для нахождения ошибки.
Недавняя история из жизни: валидация входных данных не охватывала один пограничный случай. Первичные тесты проходили, но падали совершенно в другом месте. Надо было разобраться "быстро и еще вчера".
Поставила
binding.irbна входные данные в самом начале цепочки. Необходимая ошибка не рейзилась, данные уходили дальше и валили тесты позднее.
Просто? Да.
Быстро? Не всегда, т.к. зачастую распутывать приходится большие клубки зависимостей.
Итого
binding.irb — элементарный, встроенный и эффективный способ дебажить на лету, без лишних настроек и гемов. Он не просто показывает переменные — он открывает нам контекст выполнения, позволяя буквально заглянуть внутрь кода.
Такое видение контекста помогает понять, что действительно происходит, а не должно происходить. Возможность видеть и анализировать - это основа, остальные инструменты (pry, byebug и т. п.) лишь расширяют эту возможность.
