Глава 17. Отладка сценариев Perl. Руководство по стилю программирования

Коротко

Вероятно, вы способны написать замечательный, прозрачный код, который сразу будет работать безотказно. Но для многих программистов, ошибки — неотъемлемая часть творчества. Вам, возможно, захочется взглянуть на эту главу, чтобы помочь им, поскольку речь пойдет об отладчике Perl.

Интерпретатор Perl, запущенный с ключом -d, переходит в режим отладчика. Отладчик — это интерактивная оболочка, позволяющая вводить команды отладки, исследовать код, устанавливать точки прерывания, изменять значения переменных, и т. д.. Если отладчик Perl на ваш вкус недостаточно мощен, существует много других отладчиков, как коммерческих, так и некоммерческих. Проверьте архив CPAN и выберите наиболее подходящий. Также учтите, что если вы работаете с редактором GNU Emacs или XEmacs, то можете использовать его для взаимодействия с отладчиком Perl, обеспечивая полностью интегрированную оболочку для разработки программного обеспечения на Perl.

Подсказка: Отладчик Perl ориентирован на построчный вывод. Это значит, что в определенных случаях он выводит больше строк текста, чем может поместиться на экране компьютера. В результате могут возникать проблемы. В качестве меры, исправляющей положение, можно предложить вводить команды отладчика с префиксом |, то есть через канал. В этом случае вывод проходит через промежуточный фильтр (pager), позволяющий просматривать страницу за страницей.

Пример сеанса отладки

Рассмотрим пример сеанса работы с отладчиком. Предположим, что в файле debug.pl хранится сценарий:

$variable1 = 5;

$variable2 = 10;

$variable1 += 5;

print "\$variable1 = $variable1\n";

print "\$variable2 = $variable2\n";

Чтобы загрузить сценарий в отладчик, введем команду:

%perl -d debug.pl

Отладчик загружается и выводит приглашение для ввода команды DB<1> — число в угловых скобках показывает номер отладочной команды:

Loading DB routines from perl5db.pl version  1.0401

Emacs support available.

 

Enter g or `h h’ for help.

 

Main::(debug.pl:1):         $variable1 = 5;

 DB<1>

После приглашения введите символ минуса (–). Эта команда выведет исходный текст программы:

 DB<1> –

1==>         $variable1 = 5;

2:              $variable2 = 10;

3:              $variable1 += 5;

4:              print "\$variable1 = $variable1\n";

5:              print "\$variable2 = $variable2\n";

6

Обратите внимание на символ ==> в строке кода с номером один. Он отмечает положение указателя отладчика — текущую выполняемую строчку. Чтобы выполнить несколько строк кода и остановиться, мы устанавливаем точку прерывания (breakpoint) на строке 4 (команда «b 4»). Таким образом, выполнение программы остановится, когда отладчик доходит до точки прерывания. Мы также используем команду «c» для выполнения кода программы от положения указателя до точки прерывания:

 DB<1> b 4

 DB<2> c

main::(debug.pl:4):         print "\$variable1 = $variable1\n";

Теперь посмотрим на код. Указатель отладчика находится в строке 4, — обратите внимание на символ «b», который означает точку прерывания:

 DB<2> –

1:              $variable1 = 5;

2:              $variable2 = 10;

3:              $variable1 += 5;

4==>b            print "\$variable1 = $variable1\n";

5:              print "\$variable2 = $variable2\n";

6

Кроме выполнения вплоть до точки прерывания, можно выполнять код пошагово, используя команду «s». В нашем примере мы увидим, что указатель отладчика переместился на следующую строчку:

 DB<2> s

main::(debug.pl:5):         print "\$variable2 = $variable2\n";

 DB<2>

Другая полезная техника — проверка значений переменных и выражений. В режиме наблюдения за переменной или выражением, отладчик сообщает, когда происходит что-то, изменяющее значение переменной или выражения. В следующем примере команда «W» устанавливает режим наблюдения для переменной $variable1 — заметьте, как отладчик останавливает работу программы и дает нам знать, что он встретил строчку кода, изменяющую $variable1:

 DB<1> W $variable1

 DB<2> c

Watchpoint 0:   $variable1 changed:

    old value:  undef

    new value:  '5'

Работая с отладчиком, вы можете делать гораздо более интересные вещи, — например, изменять значения переменных, вычислять выражения и даже выполнять команды Perl перед очередным шагом в программе.

Кроме режима использования отладчика, Perl предлагает ряд конструктивных предложений по поводу стиля программирования. Мы познакомимся в этой главе с некоторыми из них, а пока перейдем к разделу «Непосредственные решения».

Непосредственные решения

Перехват ошибок времени выполнения

Перед тем, как приступить к отладке кода, обратите внимание, что до некоторой степени можно перехватывать ошибки во время выполнения программы. Для этого служат специальные переменные: $? (ошибка, возникшая в дочернем процессе), $! (ошибка последнего вызова системной функции), $^E (расширенное сообщение об ошибке), $@ (ошибка при последнем выполнении функции eval).

Что касается перехвата ошибок во время выполнения программы, то у Perl нет специальных команд типа try-catch, существующих, например, в языках С++, Delphi, Python. (Потенциально опасный код помещается внутрь блока try. При появлении ошибки управление передается блоку catch.) Однако подобие такого блока модно сконструировать с помощью команды eval.

Рассмотрим пример. Подпрограмма try будет выполнять код, переданный ей в качестве параметра. Чтобы передать ец блок кода, заключенный в фигурные скобки, мы задаем прототип подпрограммы так, чтобы она понимала в качестве параметра ссылку на анонимную подпрограмму. Чтобы перехватить прерывание по ошибке, мы выполняем вызов подпрограммы внутри команды eval (в этом случае ошибка не приводит к завершению программы). Если внутри eval происходит ошибка, сообщение о ней заносится в переменную $@. Это позволяет определить факт ошибки и вывести сообщение о ее причине (в нашем случае попытка деления на ноль):

sub try (&) {

   my $code = shift;

   eval {&$code};

   if ($@) {print $@;}

};

try {

   $operand1 = 1;

   $operand2 = 0;

   result = $operand1 / $operand2;

};

Illegal division by zero at try.pl line 9.

Модно также задать собственный обработчик ошибок. Для этого надо перехватить сигнал __WARN__ подпрограммой обработчиком. Как и все обработчики сигналов, она заносится в хеш-таблицу %SIG:

local $SIG{__WARN__} = sub {print "Error!\n"};

В случае возникновения ошибки вызывается эта подпрограмма.

На этом обсуждение обработки программных ошибок закончено. Логическая ошибка программы — это совсем другая тема: вам надо воспользоваться отладчиком. По этому поводу — см. следующий раздел.

Запуск отладчика

Как запустить сценарий Perl в отладчикеё? Для этого надо при старте интерпретатора использовать ключ -d:

%perl -d debug.pl

Когда задан этот ключ, Perl загружает ваш сценарий в режиме отладки.

Подсказка: Если вы используете режим отладки, но не указываете файл, в котором расположен отлаживаемый сценарий, придется ввести его вручную с клавиатуры. Лишь затем, после завершающего __END__, интерпретатор перейдет в режим отладки.

Вместе с ключом -d вы можете использовать ключ -D, устанавливающий параметры режима отладки (они перечислены в таблице 17.1). Параметры отладчика указываются в виде букв (например, -Df) или числа (например, -D256). Вы можете объединять несколько параметров отладки — для этого либо перечисляют сразу несколько букв (например, -Dts), либо задают число, являющееся суммой отдельных числовых величин (например, -D10 равносильно -Dts).

Чтобы интерпретатор смог использовать параметры отладки, он должен быть скомпилирован с ключом -DDEBUGGING (устанавливается автоматически, если при компилияции использован ключ -g).

Таблица 17.1. Параметры режима отладки

Число

Буква

Значение

1

p

Обрабатывать ошибки синтаксического разбора

2

s

Выводить состояние стека вызовов подпрограмм

4

l

Разрешить контекстно-зависимую обработку стека

8

t

Разрешить трассировку выполнения сценария

16

o

Разрешить проверку перегрузки методов

32

c

Поддерживать преобразование строк в числа и наоборот

64

P

Поддерживать препроцессор печати

128

m

Разрешить выделение блоков памяти

256

f

Разрешить обработку форматов

512

r

Разрешить синтаксический разбор и выполнение регулярных выражений

1024

x

Разрешить вывод дампов деревьев синтаксического разбора

2048

u

Поддерживать проверку меченых данных

4096

L

Проверка «утечек» памяти, то есть появление блоков памяти, которые не используются ни сценарием, ни отладчиком, и не входят в область свободных блоков

8192

H

Разрешить дампы хешей

16384

X

Разрешить оперативное реагирование отладчика

32768

D

Разрешить режим очистки

Доступные команды отладчика

Чтобы узнать, какие есть команды у отладчика, используйте команду «h» (сокращение от help). В ответ на приглашение отладчика ввести команду, вы можете указать одну из команд вывода подсказки:

h

|h

h h

h команда

Первая форма команды «h» выводит на экран краткую справку по всем командам отладчика. Как правило, эта «краткая справка» слишком длинна, чтобы уместиться на экране. Если в начале команды будет стоять символ «|», выдача на экран будет проходить через внутренний фильтр отладчика, позволяющий просматривать справочный текст страница за страницей.

Подсказка: Символ вертикальной черты (|), как уже было сказано в начале этой главы, воздействует на любой текст, выводимый отладчиком на экран, а не только на результат работы команды «h». То есть, если при выполнении очередной команды отладчика вы видите, что информация скрылась за верхним краем экрана, и перед глазами расположен только конец нужного вам текста, просто повторите эту команду, поставив в ее начале символ «|».

Команда «h h» позволит увидеть выжимку из краткой справки по командам отладчика, выводимой по команде «h». Это еще более краткий текст, отформатированный в две колонки. Она слишком лаконична, чтобы быть информативной для человека, не имеющего никакой информации о командах отладчика. Зато она позволяет увидеть полный список имен команд, не прибегая к постраничному просмотру информации. Теперь, используя команду h в формате «h команда», вы получите развернутую справку по каждой команде в отдельности. Наиболее существенные и часто используемые команды отладчика разъясняются в последующих разделах этой главы.

Просмотр исходного кода

После того, как исходный текст вашего сценария будет загружен в память отладчика, вам может понадобиться увидеть этот код, чтобы освежить его в памяти. Для этого используется одна из команд, управляющих выводом листинга («l» является сокращением от list, «w» — от window):

  • l — Вывести следующий фрагмент исходного кода. Если вы находитесь в самом конце сценария, выводится последняя строчка сценария.

  • l min+incr — Начиная со строки с номером min вывести incr+1 строк исходного кода.

  • l min-max — Вывести строки с номерами от min до max.

  • l строка — Вывести строку с указанным номером.

  • l подпрограмма — Вывести первый фрагмент из строк исходного кода, относящихся к указанной подпрограмме.

  • – — Вывести предыдущий фрагмент исходного кода сценария.

  • w — Вывести на экран строки исходного кода, расположенные вокруг строки, на которой установлен указатель отладчика (предыдущая строка, текущая строка и следующая строка).

  • w строка— Вывести на экран строки исходного кода, расположенные вокруг указанной строки.

  • . — Переместить указатель отладчика к последней выполненной им строчке сценария и вывести на экран эту строчку.

    В следующем примере мы выводим на экран первые три строки сценария:

     DB<1> l 1-3

    1==>         $variable1 = 5;

    2:              $variable2 = 10;

    3:              $variable1 += 5;

    Пошаговое выполнение

    Чтобы продвигаться по коду с помощью отладчика, вы можете сделать один шаг вашей программы, выполнив одну строчку кода. Для этого используется команда «s» (сокращение от step):

  • s — Выполнить следующую строку сценария.

  • s выражение — Выполнить указанное выражение, как если бы указанные команды Perl шли вслед за текущей строкой, но перед следующей. (Следующая строка была бы выполнена, если использовать команду «s» без параметров.)

    Если строка сценария или указанное вами выражение включает вызовы функций, строки, составляющие тело функции, также будут выполняться по шагам. (Чтобы выполнить вызов функции как единое целое, используйте команду «n».) Обратите внимание, что пустая строка, введенная после команды «s» или «n», приводит к повтору команды, — то есть, если вы выполнили очередную строку сценария с помощью команды «s» без параметров, то нажатие клавиши Enter приведет к выполнению следующей строки сценария.

    В этом примере мы выполняем по шагам три команды печати:

     DB<1> –

    1==>   print "Hello\n";

    2:        print "from\n";

    3:        print "Perl!\n";

    4

     DB<1> s

    Hello

    main::(d.pl:2): print "from\n";

     DB<1> s

    From

    main::(d.pl:3): print "Perl!\n";

     DB<1> s

    Perl!

    Пошаговое выполнение без захода в подпрограммы

    Команда «n» (сокращение от next) запрещает пошаговое выполнение подпрограмм, вызываемых в процессе выполнения очередной строки сценария или в процессе вычисления выражения. Формат команды «n» и ее действия аналогичны рассмотренной в предыдущем разделе команде «s» за тем исключением, что вызовы подпрограмм выполняются как единое целое, не приводя к пошаговой трассировке тела подпрограммы:

    n

    n выражение

    Вы можете использовать нажатие на клавишу Enter, чтобы повторить последнюю команду «s» или «n».

    Установка точек прерывания

    Когда отладчик доходит до установленной точки прерывания, он приостанавливает работу сценария, и вы можете проверить, что происходит в программе. Отладчик Perl обеспечивает довольно большую гибкость по части установки точек прерывания. Команда «b» (сокращение от break), устанавливающая точки прерывания, выглядит следующим образом:

  • b — Устанавливает точку прерывания в текущей строке.

  • b строка — Устанавливает точку прерывания в указанной строке.

  • b подпрограмма — Устанавливает точку прерывания в первой строке указанной подпрограммы.

  • b postpone подпрограмма — Устанавливает точку прерывания в первой строке указанной подпрограммы, но только после ее компиляции.

  • b load имя-файла — Задает прерывание выполнения сценария, как только указанный файл будет загружен командой use или require.

  • b compile подпрограмма — Задает прерывание выполнения сценария, как только будет откомпилирована указанная подпрограмма.

    Команды «b», «b строка», «b подпрограмма» и «b postpone подпрограмма» позволяют указывать в качестве необязательного параметра условие (например, $variable == 0). Условие представляет собой выражение Perl. Если при достижении указанной строки, но до ее выполнения, вычисление этого выражения соответствует условию истина, то выполнение сценария прерывается, если же проверка возвращает значение ложь — то оно продолжается дальше. Поскольку в качестве условия бессмысленно задавать фиксированное число (оно либо всегда истинно, либо — в случае, если это ноль, — всегда ложно), то отладчик Perl гарантированно способен отличить команду «b строка» от команды «b условие».

    В следующем примере мы устанавливаем точку прерывания на четвертой строке исходного кода и выполняем сценарий целиком до того момента, пока не достигнем этой строки (см. также описание команды «c» в разделе «Запуск программы до следующей точки остановки» далее в этой главе):

     DB<1> –

    1==>   print "Hello\n";

    2:        print "from\n";

    3:        print "Perl!\n";

    4:        print "Hello again.\n";

    5

     DB<1> b 4

     DB<2> c

    Hello

    from

    Perl!

    main::(d.pl:4): print "Hello again.\n";

     DB<2>

    Удаление точек прерывания

    Чтобы удалить точки прерывания, вы можете использовать следующие команды отладчика (сокращение от delete):

  • d — Удаляет точку прерывания, установленную в текущей строке (если она там есть).

  • d строка — Удаляет точку прерывания в строке с указанным номером (если она там есть).

  • D — Удаляет все установленные точки прерывания.

    Запуск программы до следующей точки остановки

    Команда «c» (сокращение от continue) выполняет очередной фрагмент сценария:

  • c — Выполнять сценарий до тех пор, пока не встретится следующая точка прерывания.

  • c строка — Выполнять сценарий до тех пор, пока не встретится следующая точка прерывания или не будет достигнута указанная строка.

  • c подпрограмма — Выполнять сценарий до тех пор, пока не встретится следующая точка прерывания или не будет вызвана указанная подпрограмма.

    Пример использования команды «c» приведен ранее в разделе «Установка точек прерывания».

    Если в процессе выполнения команды «c», был достигнут конец сценария, вы увидите сообщение:

    Debugged program terminated. Use q to quit or R to restart,

       use O inhibit_exit to avoid stopping after program termination,

       h q, h R or h O to get additional info.

    Если теперь ввести команду «R» (сокращение от Restart), отладчик вернется к началу сценария, очистив все переменные, стек вызовов подпрограмм, и т. д., но сохранив установленные точки прерывания. Тем самым получите еще один шанс разобраться, что же происходит в вашей программе. Если же ввести команду «q», отладчик закончит свою работу.

    Печать выражения

    Чтобы вывести на экран компьютера значение какого-либо выражения (например, значение переменной), используется команда «p» (сокращение от print):

    p выражение

    В следующем примере мы выводим с помощью команды «p» значение переменной $variable1:

     DB> p $variable1

    5

    Вычисление выражения

    Чтобы вычислить выражение Perl, достаточно ввести его в ответ на очередное приглашение отладчика. В качестве выражений выступают любые команды Perl. Если команда очевидным образом не вмещается в одну строчку, вы можете продолжить ее на следующей, введя последним символом обратную косую черту (\). Отладчик выведет в качестве приглашения текст «cont:» и будет ждать продолжения. Пример (печать текста три раза с помощью оператора цикла):

     DB<1> for (1..3) { \

     cont: print "Hello from Perl!\n"; \

     cont: }

    Hello from Perl!

    Hello from Perl!

    Hello from Perl!

    Изменение значений переменных

    Вы можете изменить значение переменной, присвоив ей новое значение (см. предыдущий раздел). Пример:

     DB<1> p $variable1

    5

     DB<2> $variable1=10;

     DB<3> p $variable1

    10

    Установка глобальных условий

    С помощью команды «W» (сокращение от Watch) можно указать глобальные условия, проверяющие изменения значений переменных:

  • W выражение — Прерывает выполнение сценария, если в процессе работы изменяется заданное выражение.

  • W — Удаляет все установленные ранее условия по глобальной проверке значений выражений.

    Самый простой случай — это когда в качестве выражения указана переменная. Тогда работа сценария прерывается, как только изменяется значение переменной. (Заметьте, что оператор присвоения, присваиваивающий переменной то же самое значение, которое в ней уже находится, не прерывает работы сценария.) Пример:

    main::(debug.pl):     $variable1 = 5;

     DB<1> W $variable1

     DB<2> c

    Watchpoint 0:   $variable1 changed:

       old value:     undef

       new value:   '5'

    Однако команда «W» позволяет задавать в качестве аргумента не только переменные, но и целые выражения. В этом случае прерывание нормальной работы сценария возникает, если изменится соответствующее выражение как единое целое. Команда «W» достаточно сообразительна, чтобы вычислять значение выражения и выполнять проверку, только когда меняются входящие в выражение компоненты. Необходимо отметить, что если вы указываете команде «W» в качестве аргумента выражение, а не просто имя переменной, все переменные, входящие в выражение, уже должны иметь определенное значение. Если одна или несколько переменных имеют значение undef, вы получите довольно длинный список ошибок, возникающих внутри отладчика (он ведь тоже представляет собой сценарий Perl!) Причем такая диагностика будет выводиться при каждом изменении переменных, входящих в выражение, пока все они не получат определенное значение.

    Установка отладочных действий

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

  • < действие — Перед каждым выводом приглашения отладчика выполнить указанные команды Perl.

  • << действие — Добавить новые команды Perl к командам, уже установленным командой «< действие».

  • < — Удалить все команды Perl, установленные командами «< действие» и «<< действие».

  • > действие — Каждый раз после ввода команды отладчика и нажатия на клавишу Enter, выполнить указанные команды Perl.

  • >> действие — Добавить новые команды Perl к командам, уже установленным командой «> действие».

  • > — Удалить все команды Perl, установленные командами «> действие» и «>> действие».

  • { действие — Каждый раз перед тем, как вывести приглашение отладчика на ввод команды, выполнить указанные команды отладчика.

  • {{ действие — Добавить новые команды отладчика к командам, установленным командой «{ действие».

  • { — Удалить все имеющиеся команды отладчика, установленные командами «{ действие» и «{{ действие».

  • a действие — Для текущей строки задать выполнение действий (команд Perl). А именно, каждый раз, когда отладчик будет доходить до текущей строки, он:

  • 1. проверит наличие точки прерывания (раздел «Установка точек прерывания»),

  • 2. проверит наличие глобального условия (раздел «Установка глобальных условий»),

  • 3. выведет данную строку (если это необходимо),

  • 4. выполнит указанное действие,

  • 5. выведет приглашение, если это необходимо (например, в режиме пошаговой работы),

  • 6. выполнит команды Perl, расположенные в этой строке.

  • a строка действие — То же, что и в предыдущем случае, но для строки с указанным номером.

  • A — Удалить все установленные на текущий момент действия.

    Пример — перед выводом очередного приглашения отладчик печатает текущее значение переменной $variable1:

     DB<1> –

    1==> $variable1 = 5;

    2: $variable1 += 5;

    3: $variable1 += 5;

     DB<1> < print "\$variable1 = $variable1\n";

     DB<2> s

    main::(debug.pl:2)  $variable1 += 5;

    $variable1 = 5;

     DB<2> s

    main::(debug.pl:3)  $variable1 += 5;

    $variable1 = 10;

    Выход из отладчика

    Чтобы выйти из отладчика, используйте команду «q» (сокращение от quit).

    Руководство по стилю программирования на Perl

    У разработчиков Perl имеется много предложений по стилю программирования для этого языка. Многие — всего лишь вопрос вкуса. Учитывайте этот факт, соглашаясь или нет с приведенным ниже списком сразу же по мере его прочтения. Итак:

  • Блок, состоящий из команды, умещающейся в одну строку, должен занимать одну строку (включая фигурные скобки).

  • Выравнивайте соответствующие друг другу элементы по вертикали.

  • Всегда проверяйте код, возвращаемый при вызове системной функции или выполнении команды, заключенной в обратные апострофы.

  • Выбирайте идентификаторы, несущие смысловую нагрузку — когда вы вернетесь к ним через некоторое время, будет проще.

  • Рассмотрите возможность всегда использовать прагму use strict.

  • Не применяйте чрезмерно вычурные конструкции, типичные для программистов на С, чтобы выйти из блока операторов строго в начале или конце цикла. Perl предоставляет вам законный способ покинуть цикл в середине его тела.

  • Не используйте точку с запятой для блоков, состоящих из одной команды и занимающих одну строчку.

  • Не используйте пробелов перед точкой с запятой или между именем функции и круглыми скобками.

  • Если это помогает структурированию кода, используйте пустые строки, чтобы разделять отдельные фрагменты программы.

  • Если требуется разбить длинную строчку, делайте это после оператора (за исключением операторов and и or).

  • То, что вы имеете право опустить круглые скобки, еще не означает, что вы должны это делать. Если вы сомневаетесь, можно или нельзя опустить круглые скобки, используйте их.

  • Делайте программу настолько пригодной к многократному использованию кода, насколько это возможно. Рассмотрите возможность сделать из нее модуль или класс.

  • Помещайте открывающую фигурную скобку на той же строке, что и ключевое слово, если это можно сделать. Если нет, выравнивайте их по вертикали.

  • Закрывающая фигурная скобка блока для составной команды должна быть выровнена по отношению к ключевому слову команды. Используйте начальный символ подчеркивания, чтобы показать, что какая-то переменная или подпрограмма являются приватными для пакета.

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

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

  • Используйте строчные буквы для имен функций и методов.

  • Всегда используйте флаг -w.

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

  • Если вы используете конструкции, которые могут оказаться не реализованными на каком-то компьютере, выполняйте такие конструкции внутри команды eval и обязательно проверяйте работают ли они.