Анализ ошибок программы позволяет лишний раз не засорять форумы глупыми вопросами, на которые следуют такие же ответы. Система сообщений об ошибках придумана очень давно, и главное ее назначение - показать программисту (или пользователю), где именно эта ошибка произошла (к сообщениям об ошибках Windows это не имеет никакого отношения - см. www.exler.ru ;-)). Кроме того, любой язык программирования позволяет перехватывать и обрабатывать всевозможные неприятности. В процессе написания программы расставлять обработчики ошибок нужно обязательно. В дальнейшем, часть из них можно и удалить. Но этого делать я бы не рекомендовал, так как человеку свойственно ошибаться, и если вам кажется, что в данном куске кода ошибки быть не может, это не является аксиомой. Приступим к охоте за ошибками.
"Жы Шы пиши с буквой И". Синтаксические ошибки - нормальный атрибут процесса программирования. Забытая скобка, точка-с-запятой, очепятка... Различные редакторы с подсветкой кода помогают в обнаружении таких ошибок, но не все пользуются такими программами (или отключают подсветку). Поэтому неприятности появляются при запуске скрипта. Для локальной программы или cgi-скрипта на локальном сервере поиск ошибок не составляет особого труда. Сообщения выдаются либо в стандартный вывод, либо пишутся в лог-файл ошибок сервера. Здесь стоит пояснить пользователям Win32, что по причине "закрывательства" окна MS-DOS при завершении программы, следует запускать скрипты не двойным кликом, а открывать сеанс MS-DOS и запускать скрипт командной строкой как perl script.pl. В этом случае окно не закроется, и вы увидите сообщение об ошибке. С локальными программами разобрались, но как быть с cgi-скриптами, запускаемыми на сервере? На платных серверах часто предоставляется доступ к логам сервера, и это облегчает проблему, но не у всех же денег много! Здесь есть два выхода. Первый - попросить доступ к логам (вариант с самовольным получением доступа мы рассматривать не будем=)). Второй - проверять синтаксис на локальной машине. В этом случае можно использовать редакторы "Перл", а также отладчик "Перл". Запускается он в командной строке как perl -d script.pl. (подробнее perldoc perlrun, perldoc perldebtut , perldoc perldubug). ActiveState предлагает для отладки свой инструмент с графическим интерфейсом, который, после установки, запускается вместо стандартного консольного. В поставке "Перл" его нет, поэтому его следует скачать.
"А чей-та оно так делает?". Каждая функция возвращает какое-либо значение (этим она от процедуры и отличается). Одни функции по своему назначению должны возвращать какой-либо результат (например hex, time, etc.), другие, выполняя поставленную задачу, на всякий случай шепотом говорят нам о том, как прошла ее работа. Вот этим и надо пользоваться!
Примеры.
Программе обязательно требуется некоторый файл, например файл конфигурации.
...
...
Теперь о том, что все это означает. Если слева от || - false, значит, выполнится то, что справа.
Если функция open() не сработала, то она возвращает неопределенное значение (а это самое настоящее false) (perldoc -f open).
|| - это оператор "ИЛИ". Смысл его работы таков: слева и справа от него два выражения. Одно из них должно быть положительным, а другое отрицательным (здесь имеется в виду результат работы, а не числовое +1,-1).
Он начинает проверять слева, и если находит положительное значение (true), значит, правую часть надо сделать отрицательной, то есть не запускать. А если находит false (0 или "" или неопределенность), значит, правую часть надо запустить. Таким же образом работает оператор && - "И", с той лишь разницей, что ему нужны слева и справа одинаковые значения. Подробнее эти операторы мы рассмотрим ниже (perldoc perlop).
Функция die производит аварийное завершение программы с выводом ошибки в STDERR - стандартный вывод для ошибок (perldoc -f die).
$! - это встроенная переменная "Перл", которая содержит информацию о последней произошедшей ошибке (perldoc perlvar).
В связи с тем, что "Перл" имеет очень широкие "синтаксические" возможности, выше описанное выражение можно записать так:
...
...
...
...
а вот "расшифрованный" вариант написания.
...
...
Таким образом, результаты работы функций можно хранить в различных переменных, массивах, etc., можно обрабатывать на лету и логировать ход выполнения программы. Несколько примеров:
# который считает по дням отдельно.
# день месяца, їмесяца минус 1, год минус 1900.
# функцией localtime массива. (perldoc -f localtime)
# Приводим в порядок даты. Прибавляем к месяцу 1,
# а к году 1900. Также приводим день и месяц к двухзначному
# на чтение и запись. Если файла нет, функция open вернёт false,
# и тогда мы откроем файл на запись с созданием. (perldoc -f open)
;
$count++;
# Читаем число, прибавляем 1.
# Если мы только что создали файл, чтения не произойдёт
# и $count будет ="", но $count++ сделает переменную равной 1.
seek(F,0,0);
# Устанавливаем курсор в файле на самое начало.(perldoc -f seek)
truncate(F,0);
# Очищаем файл полностью (perldoc -f truncate)
print F $count;
close(F);
# Пишем новое число и закрываем.
Это пример, как можно использовать возвраты функций. Теперь примеры именно отлова ошибок разными способами на примере различных функций.
open(F,"program.cfg") || catch_error("Config error","die");
# то же самое:
# !open(F,"program.cfg") && catch_error("Config error","die");
open(F,$defaulttext)||catch_error("DefText error[$defaulttext]:","do not die");
sub catch_error(){
my ($error,$killme)=@_;
open(F,">>program.log");
print F "$error\n";
close(F);
exit if $killme eq "die";
}
Мы создаем функцию - обработчик ошибки. Обработчик пишет в лог, и если ошибка критическая, - завершает программу. В самой программе мы ставим обработчик для открытия файла конфигурации и для текстового файла, используемого программой по умолчанию. Невозможность открытия конфиг-файла - это критическая ошибка, и следует завершить программу. Открытие текстового файла в нашей программе не обязательно, поэтому мы не завершаем программу в случае ошибки. В качестве флага убить/не убить используем параметр в вызове функции, а в самой функции проверяем. Если он равен "die", - умираем.
Предположим, в процессе выполнения нашей программы пользователь нажал Ctrl+C, то есть решил прервать программу. Эта комбинация посылает сигнал INT, обработчик которого мы можем определить с помощью специального хеша %SIG (perldoc perlvar):
$SIG{INT}='IGNORE' # Проигнорировать
$SIG{INT}=sub{
# обработка
}
# или перенаправить на функцию stopsignal.
# в которой мы сделаем обработку
$SIG{INT}=\&stopsignal;
Например, если в вашей программе при завершении необходимо сделать запись в файл или корректно отсоединиться от сервера, то такие обработчики просто обязательны. Сигналы-убийцы, например, могут быть посланы вашей cgi-программе сервером, если она слишком долго выполняется.
Список всех сигналов вашей системы можно посмотреть так:
foreach(keys %SIG){print "$_\n";}
Теперь несколько слов об анализе ошибок. Как правило, сообщение об ошибке содержит описание оной и номер строки в вашей программе, где произошел сбой. В принципе, для программиста этого достаточно, но для начинающего маловато ;-). Поэтому последним необходимо найти на www.citforum.ru HTML версию книги В.В.Маслова "Perl. Учебный курс". Там имеются расшифровки сообщений об ошибках и предупреждений "Перл" на русском языке.
Об ошибках в CGI-скриптах, вызывающих ошибку 500, мы поговорим позже, в соответствующей главе, а пока... о грустном достаточно.
Маленькие хитрости
"А я еще и на машинке шить умею..."
Кот Матроскин
В этой главе мне хотелось бы рассказать о различных "хитрых" инструментах языка "Перл", которые игнорируются начинающими программистами, вследствие неумения их использовать.
Хитрые операторы. Об операторах || и && мы уже говорили, но мне хотелось бы рассказать о них подробнее. Что же можно из них состряпать? Очень много!# Эмулируем оператор switch из других языков, так как
# в Perl его нет.
while(<>){ # Читаем из стандартного ввода в $_ (perldoc perlvar)
# /hello/ это тоже что $_=~m/hello/
# Здесь оператор && проверяет истинность выражения слева и,
# если оно верно, выполняет блок слева.
/hello/ && do{
print "Hi, man!\n";
next;
};
# Функция next переводит выполнение сразу в начало цикла,
# чтобы в случае выполнения блока, другие блоки проигнорировались
# (perldoc -f next)
/how are you/ && do{
print "I'm fine, thanks!\n";
next;
};
/bye/ && do{
print "Good Luck, bro!\n";
sleep 2;
last;
};
# Функция last выводит нас из цикла (perldoc -f last)
}
# Предположим, что пользователь вводит информацию о себе,
# а программа пытается его узнать. Для этого мы создаём
# функцию для определения по фамилии, адресу или телефону.
# test_user();
# Описывать эту функцию не будем, а представим, что они проверяют
# информацию из базы данных.
# Каждая функция возвращает массив данных о пользователе если находит,
# и ничего не возвращает, если не находит.
#
# Кроме того, у нас имеется функция goto_hell(), посылающая
# пользователя к чёрту и завершает программу, если он нам не известен.
print "Fio or Street or Phone->";
chomp($enter=<>);
test_user($enter,"FIO")||
test_user($enter,"STREET")||
test_user($enter,"PHONE")||goto_hell($fio);
# Если программа доработала до этого момента, то....
print "Hello, $_[0]!\n Street:$_[1]\n-Phone:$_[2]";
# Наша программа последовательно ищет введённую строку
# в столбцах базы FIO, STREET и PHONE и возвращает результат
# в массив по умолчанию @_ (perldoc perlvar)
# Если функция не находит пользователя, выводит пустое значение
# которое переключает оператор || на правое выражение, и так по
# цепочке операторов либо до нахождения пользователя, либо до
# функции goto_hell, которая завершает программу.
# Предположим, вы получаете пользовательские данные из формы
# регистрации на сайте. Обязательными полями для успешной
# регистрации являются логин, пароль и e-mail. В скрипте
# мы принимаем все параметры в хеш $F{param}=value
# Приём данных мы рассмотрим позже, а вот как мы проверим
# наши поля. Пусть print_error - это наша функция, которая
# выдаст страницу, сообщающую об ошибке заполнения формы.
# Функция check_email проверяет правильность e-mail адреса
# Функция check_word проверяет наличие недопустимых символов
# Обе эти функции должны в случае ненахождения ошибки
# вернуть положительное значение, пусть это будет "OK".
print_error("Вы не заполнили обязательные поля!") unless($F{login} && $F{password} && $F{email});
print_error("Введите правильный E-mail") if(!check_email($F{email}));
print_error("Логин и пароль могут содержать только англ. буквы и цифры") if(!check_word($F{login}) || !check_word($F{login});
Вот такие хитрые операторы. Надеюсь, вы поняли принцип их работы.
Есть ещё оператор ! ("НЕ"), который меняет значение выражения, стоящего справа от него на противоположное.
!open(F,"несуществующий файл"))
# Вернёт положительный результат
!open(F,"существующий файл"))
# Вернёт отрицательный результат
Вы наверняка уже используете оператор != (НЕ РАВНО) в своих программах. Есть такие операторы и с другими логическими функциями - ||= и &&=, ИЛИ-РАВНО и И-РАВНО соответственно.
Принцип их работы такой:
(Переменная = ИСТИНА) ИЛИ-РАВНА (ЗНАЧЕНИЕ)
(Переменная = ИСТИНА) И-РАВНА (ЗНАЧЕНИЕ)
Например, в HTML-форме пользователь не заполнил необязательное поле "field". Тогда нам нужно записать в соответствующую переменную значение по умолчанию.
# Предполагаем, что параметры формы у нас в %DATA;
$DATA{field} ||= "default value";
Таким образом, если параметр не задан, он становится дефолтным.
Пример второй. Допустим, в форме имеется поле select для выбора порядка сортировки по базе
<select name=search>
<option value=""> Прямая сортировка
<option value="1"> Обратная сортировка
</select>
Полученные данные опять в %DATA
$DATA{search} &&= "DESC";
...
$req=$db->prepare("SELECT......... $DATA{search}");
Таким образом, если переменная = "", в запросе вместо $DATA{search} подставится пустота, а если выбрана обратная сортировка, подставится "DESC".
Широта использования этих операторов ограничена лишь фантазией программиста ;-)
Анонимные "элементы". Анонимные переменные, массивы, хеши, подпрограммы - это элементы программы, не имеющие имени. Обращение к ним происходит по ссылкам, хранящимся в других элементах.
В Perl не существует многомерных массивов, как типа данных, в отличие от других языков. Многомерный массив в Перл представляет собой масив ссылок на анонимные массивы.
$element=$array[$i][$j];
Мы по ссылке, хранящейся в ячейке $i массива @array идем в анонимный массив и берем из него $j-ый элемент.
Структуры из анонимных элементов могут быть самые изощренные.
$element=$array[$i][$j][$k];
$element=$array[$i]{$j}{"name"}{"job"};
$element=$hash{$key}[$j]{$k}[$i];
Для того, чтобы обратиться к анонимному элементу, входящему в состав сложной структуры, нужно разыменовать ссылку на него. Делается это так:
@subarray=@{$array[$i][$j]};
%subhash=%{$array[$i]{$j}{"name"}};
%subhash=%{$hash{$key}[$j]{$k}};
С кратким введением в "анонимы", пожалуй, закончу, и расскажу о некоторых хитростях их использования. Подробнее о них читайте в perldoc perlreftut, perldoc perlref.
# Пример1.
# Имеется таблица базы данных со столбцами:
# ID, Фамилия, Должность, Зарплата, Стаж, Кол-во детей
# ID-уникальный идентификатор, целое число
# Нашей программе предстоит производить много операций
# с прочитанными данными.
while(@temp=get_db_line()){
$P{$temp[0]}{family}=$temp[1];
$P{$temp[0]}{job}=$temp[2];
$P{$temp[0]}{salary}=$temp[3];
$P{$temp[0]}{years}=$temp[4];
$P{$temp[0]}{kids}=$temp[5];
}
# Функция get_db_line() должна выдавать строку таблицы
# в виде массива. После прочтения всей таблицы мы можем
# пользоваться хешем %P как структурой.
@sort_zarplata=reverse sort {$P{$a}{sa-lary}<=>$P{$b}{salary}} keys %P'
# Получим отсортированный по зарплате
(от большей к меньшей)
# список ID рабочих.
Ещё один интересный элемент - анонимные подпрограммы. В главе про ошибки был такой пример:
$SIG{INT}=sub{...};
Ключу хеша присваивается ссылка на анонимную подпрограмму. Можно создать переменные, массив или хеш подпрограмм.
$sub1=sub{...};
$sub1->(); # Вызов
@arraysubs=(\&subroutine1,
\&subroutine2,
sub{...},
sub{...}
);
# Здесь элементы с номерами 0 и 1 содержат ссылки на реальные
# подпрограммы, а элементы 2 и 3 - на анонимные, которые мы
# определяем тут же
$arraysubs[0]->(); # Вызов subroutine1
$arraysubs[1]->(); # Вызов subroutine2
$arraysubs[2]->(); # Вызов анонимной подпрограммы
# В хешах
%ACTIONS=( "login" => \&login,
"exit" => sub{ exit; },
"read" => \&read,
...
);
# Далее в программе мы принимаем параметр "action"
# в переменную $action и ...
$ACTION{$action}->(@params) if exists $ACTION{$action};
# Таким образом освобождаясь от многочисленных if, elsif, else.
Вообще, в Perl имеется столько разных неочевидных примочек, что даже самые продвинутые "перловщики" не знают всех хитростей. К тому же, многочисленные "Перл"-программеры мира не перестают их находить.
Приведу еще один интересный пример. Предположим, наш большой скрипт выполняет много разных действий, в зависимости от входных параметров. Как правило, в локальных программах сообщения об ошибках выдаются пользователю сразу. Также программы могут записывать ошибки в лог-файлы. Разница между выводом ошибки юзеру и записи оной в лог состоит в том, что юзеру пишется понятная фраза типа "Файл не найден", а в лог можно записать различную дополнительную информацию, которая поможет исправить ошибку. Логирование ошибок очень полезно при написании CGI-скриптов. И главная задача в таком случае - не ограничиваться ...||die $!;. Пользователь должен получить понятное сообщение и продолжить работу со скриптом, а последний должен подробно описать ошибку в файле логов.
# 1. Вариант ужасный
...
if(!open(F,$file)){
open(ERR,">>error.log");
print ERR "Ошибка:файл $file";
close(ERR);
print "Content-type:text/html\n\n";
... # напечатка ошибки юзеру
}
# 2. Вариант хитрый
...
logging_error("File error:$file!",-"print_login_form","Не найден файл") if !open(F,$file);
...
# А вот главная функция в этом примере
sub logging_error{
my ($error,$sub,$message)=@_;
# Далее логируем ошибку($error) с датой временем и т.д.
...
...
# А вот и самое интересное:
&{$sub}($message) if defined &{$sub};
}
В этом примере, в случае возникновения ошибки, происходит следующее:
Вызывается подпрограмма logging_error , которая принимает 3 параметра. Первый -сообщение, которое запишется в лог. Здесь удобно указывать точки возникновения ошибки. Второй параметр - имя подпрограммы, которой передается управленив после логирования ошибки, а третий параметр - сообщение для пользователя, которое распечатает подпрограмма из второго параметра.
Запуск подпрограммы происходит в последней строке logging_error.
&{$var} - такая конструкция подставляет вместо скобок строку, содержащуюся в $var. То есть:
$var="myfunction";
&{$var}("Hello,Perl!"); # Эквивалентно &myfunction("Hello,Perl!");
sub myfunction(){
my $text=shift;
print $text;
}
Если параметры, указывающие на подпрограмму, не указаны или не соответсвуют существующей подпрограмме, запуск не произойдет. За это отвечает конструкция .... if defined &{$sub}
Аналогичный эффект можно наблюдать с помощью конструкций: ${$varname}, @{$arrayname}, %{$hashname}
Таким образом, функция logging_error примера становится универсальной и помогает сэкономить на размере кода.
С абстрактным программированием ради программирования на этом закончим. В следующей части этого повествования мы перейдем к прикладному программированию и рассмотрим способы решения различных задач программирования и использования полезных модулей.
Источник: "Компьютер Price", http://www.comprice.ru