Магия Pry для отладки кода Ruby


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

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

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

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

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

gem install pry

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

# test.rbrequire '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 в ваших руках, испытайте его силу в своей практике программирования!


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


Поделиться статьей:


Вернуться к статьям

Комментарии

    Ничего не найдено.