Бывает так, что запускаешь тесты, а они падают там, где вроде бы всё должно работать. В логах — только сухая ошибка, без контекста. Открываешь код, смотришь на участок кода, где произошёл сбой, и начинаешь гадать:

  • не те входные данные из-за плохого контракта

  • возникла неучтённая крайняя ситуация

  • логика в принципе неверная, хотя для большинства случаев всё «вроде бы ок».

Ситуация осложняется тем, что по какой-то причине нет отладчика (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 и т. п.) лишь расширяют эту возможность.

Комментарии (0)