Начну с ситуации, знакомой каждому программисту: вы приступаете к выполнению кода и вдруг к вашему полному недоумению получаете сообщение об ошибке, или еще хуже — код успешно работает, но на выводе не дает никаких результатов. Это как раз случай из моей практики, и до знакомства с Pry я бывало кропотливо, строка за строкой, проверял свой код в попытке обнаружить ошибку. Доходило даже до того, что приходилось вставлять инструкции puts, например “The error is here!” (“Ошибка здесь!”), в надежде определить источник ее происхождения. Но стоило лишь познакомиться с Pry, как всё изменилось к лучшему. Нет никаких сомнений, что это один из важнейших инструментов для начинающих Ruby-программистов.

Что же такое Pry?

Pry — это REPL (цикл “чтение — вычисление — вывод”), простая интерактивная среда программирования, которая приостанавливает код в месте своего включения, позволяя проводить тестирование/отладку. Когда код подвергается “досмотру”, его выполнение временно прекращается, и ваш терминал превращается в REPL прямо в середине программы.

Как пользоваться Pry?  

Прежде всего, следует убедиться, что вы установили gem (иначе говоря, библиотеку) Pry. Для этого введите в терминале следующее:  

gem install pry

После установки вам придется запрашивать эту библиотеку поверх всех файлов, в которых планируется ее использование.

# test.rb
require 'pry'

А теперь самое время установить точку останова Pry в коде, используя следующий фрагмент: binding.pry.

Предлагаю создать файл с именем ‘practice.rb’ и вставить в него следующий код: 

require 'pry'

def my_fancy_method
    inside_method = "We are now inside the method."
    puts inside_method
    pry_coming = "We are about to see how pry works!"
    
    binding.pry  
  
    frozen = "Pry froze the program before it got to this point!" 
    puts frozen
end

my_fancy_method

Теперь, при вызове “my_fancy_method” до завершения выполнения кода, он дойдет до “binding.pry” и перейдет в REPL, не достигая конечной точки. В REPL в вашем распоряжении будет только то, что было определено до точки binding.pry. Терминал же будет выглядеть следующим образом: 

3: def my_fancy_method
4:     inside_method = "We are now inside the method."
5:     puts inside_method
6:     pry_coming = "We are about to see how pry works!"
=>  7:     binding.pry
8:     frozen = "Pry froze the program before it got to this point!"
9:     puts frozen
10: end

[1] pry(main)>

Внизу вы увидите строкуpry(main)>, указывающую на то, что вы находитесь в интерактивной среде PRY. Давайте это проверим. Поскольку переменная inside_method была определена до binding.pry, то, скорее всего, Pry о ней знает. 

[1] pry(main)> inside_method

=> "We are now inside the method."

[2] pry(main)>

Сработало! А теперь попробуем вызвать переменную frozen, определенную в строке 8 после binding.pry. 

[[1] pry(main)> inside_method

=> "We are now inside the method."

[2] pry(main)> frozen

NameError: undefined local variable or method `frozen' for main:Object

Pry не распознает переменную frozen, так как код еще не дошел до ее определения, и по факту она не существует. Разумно? 

[3] pry(main)> exit

Pry froze the program before it got to this point!

Для выхода из Pry введите ‘exit’, после чего продолжится выполнение оставшейся части кода, как показано выше. 

Pry для отладки кода 

Итак, мы разобрались, как установить и использовать Pry. Теперь посмотрим, как эта интерактивная среда помогает в отладке кода. Предлагаю вашему вниманию самые типичные случаи применения Pry на основе моего первоначального опыта работы в сфере программирования. 

Вариант #1: Проверка переменных 

Одна из распространенных ошибок начинающих программистов — неправильное присвоение значений переменным. Рассмотрим, к примеру, следующий код.

require 'pry'

def simple_cubing_tool(number)
    number * number * number
    puts "The answer is #{number}!"
end

simple_cubing_tool(4)

Он принимает целое число в качестве аргумента и возводит его в куб, после чего возвращает выражение с числом в третьей степени. И уже в этом простом примере кроется проблема, но не будем спешить и посмотрим, что же происходит при выполнении метода с входным значением 4. 

// ♥ > ruby practice.rb

The answer is 4!

4??? Но при возведении 4 в третью степень получается 64, а не 4. Проанализируем ситуацию, используя Pry.  

require 'pry'

def simple_cubing_tool(number)
    number * number * number
    
    binding.pry    puts "The answer is #{number}!"
end

simple_cubing_tool(4)

3: def simple_cubing_tool(number)
4:     number * number * number
=> 5:     binding.pry
6:     puts "The answer is #{number}!"
7: end

[1] pry(main)> number
=> 4

При вводе числа в Pry даже после выполнения строки 4 (number * number * number) видно, что значение числа по-прежнему составляет 4. Вот незадача! А все потому, что мы забыли присвоить значение number*number*number переменной. Что же следует предпринять? 

require 'pry'

def simple_cubing_tool(number)
    num_cubed = number * number * number
    puts "The answer is #{num_cubed}!"

end

Готово! Выполним код еще раз. 

// ♥ > ruby practice.rb

The answer is 64!

Превосходно! Вот вы и узнали, как использовать Pry для проверки значений переменных на разных стадиях программы.  

Perfect! Now you see how to use Pry to check the value of variables at different stages of your program.

Вариант #2: Определение текущего местоположения во вложенном хэше/массиве 

Допустим, у нас есть массив хэшей (AOH), определенный следующим образом:

nested = [
  {:fruit => {
    :apple => 1,
    :banana => 2,
    :grape => 6
  },
  :pets => {
    :fido => "dog",
    :whiskers => "cat",
    :charles => "mouse",
    :bitey => "snake"
  },
  :teams => {
    :new_york => {
      :baseball => ["mets", "yankees"],
      :basketball =>["knicks", "nets"],
      :football => ["giants", "jets"],
      :hockey => ["rangers", "islanders"]
    },
    :los_angeles => {
      :baseball => ["dodgers", "angels"],
      :basketball =>["lakers", "clippers"],
      :football => ["rams", "chargers"],
      :hockey => ["kings"]
    },
    :chicago => {
      :baseball => ["cubs"],
      :basketball => ["bulls"],
      :football => ["bears"],
      :hockey => ["blackhawks"]
      }
    }
  }
]

Наша цель — провести итерацию этого хэша и вернуть все баскетбольные команды каждого города в таком формате: 

“The basketball teams for (city name) are (basketball team names)”, т.е. “Баскетбольными командами (название города) являются (названия команд)”. 

На данном этапе задача кажется непростой, но с помощью дружественной нам Pry мы сможем поэтапно ее решить. 

def list_basketball_teams_by_city
  nested.each do |element|
    element.each do |outer_key, outer_value|
      binding.pry
    end
  end
end

list_basketball_teams_by_city

// ♥ > ruby practice.rb
Traceback (most recent call last):
5: from practice.rb:45:in `<main>'
4: from practice.rb:37:in `list_basketball_teams_by_city'
3: from practice.rb:37:in `each'
2: from practice.rb:38:in `block in list_basketball_teams_by_city'
1: from practice.rb:38:in `each'

practice.rb:39:in `block (2 levels) in list_basketball_teams_by_city': undefined method `pry' for
#<Binding:0x00007faadd86ecc0> (NoMethodError)

При выполнении кода получаем сообщение об ошибке NoMethodError — неопределенный метод ‘pry’… В чем же дело? 

Причина в том, что мы забыли запросить ‘pry’ в начале кода!!! (Честно говоря, я на самом деле допустил эту ошибку во время написания кода и решил ее здесь оставить)

require 'pry'

def list_basketball_teams_by_city
  nested.each do |element|
    element.each do |outer_key, outer_value|
      binding.pry
    end
  end
end

list_basketball_teams_by_city// ♥ > ruby practice.rb
From: /Users/sean.laflam/Development/code/mod1/practice.rb:41 Object#list_basketball_teams_by_city:
38: def list_basketball_teams_by_city(array)
39:   array.each do |element|
40:     element.each do |outer_key, outer_value|
=> 41:       binding.pry
42:     end
43:   end
44: end[1] 

pry(main)>

Гораздо лучше! Как видно из кода мы на два уровня углублены во вложенный массив хэшей и в первом цикле each. Мы можем подтвердить этот факт, добавив в терминал Pry outer_key и outer_value. 

[1] pry(main)> outer_key

=> :fruit

[2] pry(main)> outer_value

=> {:apple=>1, :banana=>2, :grape=>6}

Отлично, работаем дальше. Мы не достигнем хэша :teams, пока не произойдет 3-й виток внутреннего цикла each, после этого нам по-прежнему будет нужно углубиться на 2 уровня ниже, чтобы вернуть массив названий баскетбольных команд. Продолжим, добавив логику. (Не забывайте использовать “exit!”, чтобы выйти из Pry и вернуться обратно в код).  

def list_basketball_teams_by_city(array)
  array.each do |element|
    element.each do |outer_key, outer_value|
      if outer_key == :teams
        outer_value.each do |city, sports_hash|
          binding.pry
        end
      end
    end
  end
end38: def list_basketball_teams_by_city(array)
39:   array.each do |element|
40:     element.each do |outer_key, outer_value|
41:       if outer_key == :teams
42:         outer_value.each do |city, sports_hash|
=> 43:           binding.pry
44:         end
45:       end
46:     end
47:   end
48: end

На данном этапе мы должны находиться в первой итерации хэша городов внутри хэша :teams, представленной городом :new_york. 

Это можно проверить в PRY через строку pry(main)> путем ввода “city” и “sports_hash”.

[1] pry(main)> sports_hash

=> {:baseball=>["mets", "yankees"],

:basketball=>["knicks", "nets"],

:football=>["giants", "jets"],

:hockey=>["rangers", "islanders"]}

[2] pry(main)> city

=> :new_york

Выглядит отлично! Мы приближаемся к желаемому выводу! Закончим же начатое.  

require 'pry'def list_basketball_teams_by_city(array)
  array.each do |element|
    element.each do |outer_key, outer_value|
      if outer_key == :teams
        outer_value.each do |city, sports_hash|
          sports_hash.each do |sport, team_name_array|
            if sport == :baseball
              puts "The basketball teams for #{city} are #{sport}."
            end
          end
        end
      end
    end
  end
end

list_basketball_teams_by_city(nested)

// ♥ > ruby practice.rb

The basketball teams for New York are baseball.

The basketball teams for Los Angeles are baseball.

The basketball teams for Chicago are baseball.

Как же так!! Счастье было так близко, но что-то пошло не так. Самое время обратиться за помощью к Pry. 

if sport == :baseball
  binding.pry
  puts "The basketball teams for #{city} are #{sport}."
end

38: def list_basketball_teams_by_city(array)
39:   array.each do |element|
40:     element.each do |outer_key, outer_value|
41:       if outer_key == :teams
42:         outer_value.each do |city, sports_hash|
43:           sports_hash.each do |sport, team_name_array|
44:             if sport == :baseball
=> 45:               binding.pry
46:               puts "The basketball teams for #{city} are #{sport  
47:             end
48:           end
49:         end
50:       end
51:     end
52:   end
53: end

[1] pry(main)> sport

=> :baseball

[2] pry(main)> team_name_array

=> ["mets", "yankees"]

[3] pry(main)>

Здесь у нас пара ошибок. При проверке sport он возвращает :baseball, но нам то нужны баскетбольные команды каждого города. Ошибка кроется в строке 44, в if-инструкции которой сказано if sport ==:baseball вместо :basketball. 

К тому же, мы вернули неправильную переменную в инструкцию puts. Наша цель—вывести массив названий баскетбольных команд этого города, который определен как “team_name_array”, но мы ввелиputs The basketball teams for #{city} are #{sport}.

Перед вами итоговый вариант кода со всеми коррективами после отладки:

require 'pry'def list_basketball_teams_by_city(array)
  array.each do |element|
    element.each do |outer_key, outer_value|
      if outer_key == :teams
        outer_value.each do |city, sports_hash|
          sports_hash.each do |sport, team_name_array|
            if sport == :basketball
              puts "The basketball teams for #{city} are #{team_name_array}."
            end
          end
        end
      end
    end
  end
end

list_basketball_teams_by_city(nested)

// ♥ > ruby practice.rb

The basketball teams for New York are ["knicks", "nets"].

The basketball teams for Los Angeles are ["lakers", "clippers"].

The basketball teams for Chicago are ["bulls"].

~/.../code/mod1 // ♥ >

Можно продолжить дорабатывать код, стремясь к более “красивому” выводу, но, как вы уже успели убедиться, благодаря Pry он уже и так хорош.  

Вариант #3: Pry для отладки кода в приложениях!!! 

Использовать Pry можно в любых подходящих приложениях, требующих отладки кода. И теперь, когда волшебство Pry в ваших руках, испытайте его силу в своей практике программирования!

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Sean LaFlam: Debugging Ruby Code with Pry

Предыдущая статьяСоветы по Docker: очистка локального компьютера
Следующая статьяТеория графов в кратком и практичном изложении