Мы рассмотрели много примеров с программным кодом, да и вы надеюсь не новички и ранее работали с PHP и наверняка уже выработали свои собственные правила и технологии отладки. С возрастанием сложности проектов отладка становится все более затруднительной. Навыки разработчика совершенствуются, но ошибки могут затрагивать несколько файлов либо появляться в результате взаимодействия кода, созданного разными программистами.
Независимо от используемого языка существует три основных типа программных ошибок:
Ниже представлен краткий обзор каждого типа, после чего последует обсуждение тактики обнаружения, обработки, предупреждения и устранения ошибок.
Языки характеризуются набором правил, называемых синтаксисом, который касается правильного использования выражений. Это относится как к естественным языкам, например, английскому, так и языкам программирования, вроде РНР. Если выражение не соответствует правилам языка, говорят, что оно содержит синтаксическую ошибку. Синтаксические ошибки часто называют ошибками разбора в случае интерпретируемых языков, таких как РНР, либо ошибками компиляции, когда речь идет о компилируемых языках, таких как С и Java.
Если мы нарушаем синтаксис английского языка, скорее всего, люди нас поймут. С языками программирования такое случается редко.
Когда в сценарии нарушается синтаксис РНР, программа анализа не сможет обработать сценарий частично или полностью. Люди хорошо умеют извлекать информацию из неполных или противоречивых данных. Компьютеры такой способностью не обладают.
Помимо прочего, синтаксис РНР требует, чтобы операторы завершались точкой с запятой, строки заключались в кавычки, а передаваемые функциям параметры отделялись запятыми и заключались в скобки. Если нарушить эти правила, сценарий окажется неработоспособным, а при первой же попытке его выполнения будет сгенерировано сообщение об ошибке.
Одна из сильных сторон РНР заключается в информативных сообщениях об ошибках. Обычно они указывают характер неполадки, какой файл содержит ошибку и в какой строке она обнаружена.
Ниже приводится пример сообщения об ошибке:
Warning: Unexpected character in input: ''' (ASCII=39) state=1 in D:\svd\phpstroy\test\error.php on line 2
(Предупреждение: Неожиданный символ во входных данных:'''(ASCII= 39) состояние=1 в D:\svd\phpstroy\test\error.php поразделу 2)
К этой ошибке привел следующий сценарий:
<?php
$date = date(m.d.y');
?>
Здесь предпринимается попытка передать в функцию date() строку, но по ошибке пропущена открывающая кавычка, которая отмечает начало строки.
Простые синтаксические ошибки, подобные данной, обычно легко обнаруживаются. Можно допустить подобную, однако более сложную в обнаружении ошибку, если не завершить строку скобкой, как в следующем примере:
<?php
if(true)
{
echo 'Здесь допущена ошибка ';
?>
Этот сценарий генерирует следущую синтаксическую ошибку:
Parse error: parse error in D:\svd\phpstroy\test\error.php on line 6
Очевидно, ошибка не может содержаться в шестой строке, поскольку сценарий состоит лишь из пяти строк. Подобным образом генерируются сообщения об ошибках, когда пропущены закрывающие одинарные или двойные кавычки, а также скобки любого вида.
Обнаружение подобных ошибок может быть затруднено, если они появляются в результате комбинирования нескольких файлов. То же относится к случаю, когда ошибка содержится в файле большого размера. Сообщение об ошибке в строке 1001 (parse error on line 1001) файла, который содержит 1000 строк, может создать большие затруднения. По общепринятому мнению, синтаксические ошибки наиболее просты в обнаружении. Сообщения РНР указывают, где их следует искать.
Ошибки времени выполнения обычно сложнее выявлять и исправлять. Синтаксические ошибки явно содержатся в сценарии и обнаруживаются программой анализа. Ошибки времени выполнения не вызываются исключительно содержимым сценария. Онимогут зависеть от взаимодействия сценариев с другими событиями или условиями.
Следующее выражение:
require ( 'filename' );
вполне допустимо. Оно не содержит синтаксических ошибок.
Однако оно может генерировать ошибку времени выполнения. Если его выполнить, когда файл filename.php не существует, либо выполняющему сценарий пользователю отказано в правах чтения, будет получено примерно такое сообщение об ошибке:
Warning: require(filename) [function.require]: failed to open stream: No such file or directory in D:\svd\phpstroy\test\error.php on line 2
Fatal error: require() [function.require]: Failed opening required 'filename' (include_path='.;c:\php\includes') in D:\svd\phpstroy\test\error.php on line 2
(Внимание: требуется (имя файла) [function.require]: не удалось открыть поток: Нет такого файла или каталога в D:\svd\phpstroy\test\error.php on line 2
Фатальная ошибка: требуется () [function.require]: Ошибка открытия требуется 'имя_файла' (include_path = '; C:. \ PHP \ включает в себя ") в D:\svd\phpstroy\test\error.php on line 2)
Код написан вполне корректно, но указываемый в нем файл в момент выполнения сценария может либо существовать либо отсутствовать. Поэтому возможна ошибка времени выполнения.
Следующие три строки являются допустимыми выражениями РНР. К сожалению, в cовокупности они приводят к попытке выполнить невозможное действие — деление на нуль.
$i = 10;
$j = 0;
$k = $i/$k;
Этот фрагмент кода генерирует следующее предупреждение:
Warning: Division by zero in D:\svd\phpstroy\test\error.php on line 4
Предупреждение: Деление на ноль в D: \ svd\ phpstroy \ test \error.php в строке 4
Оно существенно упрощает исправление ошибки. Не очень многие будут намеренно задавать в коде деление на нуль, но отсутствие проверки пользовательского ввода часто приводит к ошибкам подобного типа.
Это один из многочисленных примеров ошибок времени выполнения, которые могут всплывать в процессе тестирования кода.
Ниже перечислены распространенные причины ошибок времени выполнения:
Рассмотрим кратко все перечисленные причины.
Подобную ошибку легко допустить случайно. Имена встроенных функций часто бывают неоднородными. Почему в имени strip_tags() применен символ подчеркивания, а в имени stripslashes() его нет?
Кроме того, возможен вызов собственных функций, которые не содержатся в текущем сценарии, но могут существовать в другом месте. Если код содержит вызов несуществующей функции, такой как:
nonexistent_function () ;
или
mispeled_function();
будет получено следующее сообщение об ошибке:
Fatal error: Call to undefined function nonexistent_function() in D:\svd\phpstroy\test\error.php on line 2
Фатальная ошибка: Вызов неопределенной nonexistent_function функции () в D: \ СВД \ phpstroy \ Test error.php по разделу 2
Точно так же, если вызвать существующую функцию, но с неверным количеством параметров, будет выведено предупреждение.
Функция strstr() требует передачи двух строк — в одной из них ведется поиск, а другая служит искомым фрагментом. Если вызвать функцию следующим образом:
strstr();
будет выведено следующее предупреждение:
Warning: Wrong parameter count for strstr() in D:\svd\phpstroy\test\error.php on line 2
Предупреждение: Неправильное число параметров для strstr () в D: \ СВД \ phpstroy \ Test error.php по разделу 2
Следующий сценарий содержит такую же ошибку:
<?php
if($var == 4)
{
strstr();
}
?>
Однако вызов функции strstr() не осуществляется за исключением тех случаев, когда переменная $var принимает значение 4. При этом предупреждение также не вы водится.
Неправильный вызов функции легко допустить, но сообщение об ошибке точно идентифицирует строку и имя функции, что делает исправление ошибки таким же простым. Обнаружение подобных ошибок затруднено лишь в случае, когда процесс тестирования несовершенен и не проверяется весь условно выполняемый код. Одна из задач тестирования состоит в выполнении каждой строки кода. Вторая задача — проверка всех граничных условий и классов ввода.
В процессе использования программы могут происходить любые ошибки, но одни из них случаются чаще других. Ошибки доступа к файлам достаточно вероятны, чтобы предусмотреть методы их эффективной обработки. Жесткие диски могут давать сбои либо переполняться, а ошибки пользователей приводят к изменению прав доступа к каталогам.
Обычно в часто приводящих к ошибкам функциях, таких как fopen(), предусматривается возвращаемое значение, указывающее на ошибку. Для функции fopen() таким значением является false.
Для подобных функций необходимо тщательно проверять возвращаемое значение при каждом вызове и обрабатывать ошибки.
Подключение к базе данных MySQL и ее использование может вызвать генерацию множества ошибок. Только функция mysql_connect() может приводить к следующим ошибкам:
Несложно догадаться, что функция mysql_connect() в случае ошибки возвращает значение false. Это означает, что данные типы распространенных ошибок легко отслеживать и обрабатывать.
Если не остановить нормальное выполнение сценария и не предусмотреть обработку ошибок, он попытается продолжить взаимодействие с базой данных. Попытка выполнения запросов и получения результатов без нормального соединения с MySQL заставит посетителей лицезреть непрофессионального вида экран, полный сообщений об ошибках.
Многие другие часто используемые PHP-функции, которые связаны с MySQL, также возвращают значение false в случае ошибки. К подобным функциям относятся mysql_pconnect(), mysql_select_db() и mysql_query().
Если ошибка все же возникает, для получения текста сообщения можно воспользоваться функцией mysql_error(), а для вывода кода ошибки — функцией mysql_errno(). Если последняя функция MySQL не сгенерировала ошибки, mysql_error() возвращает пустую строку, a mysql_errno() — значение 0.
Предположим, что выполнено подключение к серверу и выбрана база данных для использования. Тогда следующий фрагмент кода:
$result = mysql_query( 'select * from does_not_exist' );
echo mysql_errno();
echo '<BR>';
echo mysql_error();
может сгенерировать следующее сообщение:
1146
Table 'dbname.does not exist' doesn't exist
1146
Таблица 'dbname.does not exist' не существует
Обратите внимание, что вывод указанных функций относится к выполнению последней функции MySQL (кроме mysql_error() и mysql_errno()). Если необходимо знать результаты выполнения команды, следует обязательно выполнить проверку до обращения к другим функциям.
Подобно сбоям доступа к файлам, возникают и сбои при взаимодействии с базами данных. Даже после тщательной разработки и тестирования службы случается, что демон MySQL (mysqld) дает сбой либо остается без доступных соединений. Если база данных содержится на другом компьютере, ее работа зависит от другого набора аппаратных и программных компонентов, которые могут сбоить. Это относится к сетевым соединениям, сетевым картам, маршрутизаторам и другим средствам связи между веб-сервером и компьютером с базой данных.
Необходимо проверять успешность запросов базы данных прежде чем использовать результаты. Нет смысла пытаться выполнить запрос после сбоя соединения с базой данных, а также извлекать и обрабатывать результаты запроса, которые не был успешно выполнен.
Здесь следует отметить различие между сбоем запроса и случаем, когда запрос просто не возвращает данные либо не изменяет какие-либо строки таблицы.
SQL-запрос, который содержит синтаксические ошибки SQL либо относится к несуществующим базам данных, таблицам или столбцам, может давать сбой. Например, следующий запрос:
select * from does_not_exist;
генерирует ошибку, поскольку таблица с именем does_not_exist не существует. Сообщение об ошибке и ее номер можно извлечь с помощью функций mysql_errno() и mysql_error().
Синтаксически правильный SQL-запрос, который также адресуется только к существующим базам данных, таблицам и столбцам, обычно не генерирует ошибку. Тем не менее, если запрашивается пустая таблица, либо ведется поиск несуществующих данных, возврата результатов может не быть. Предположим, что выполнено успешное соединение с базой данных, а также существует таблица с именем exists и столбец с именем column name, следующий запрос:
select * from exists where column_name = 'not in database';
будет успешным, но возврата результатов не последует.
Прежде чем использовать результаты запроса, необходимо осуществить проверку на возможное присутствие ошибки, а также на предмет отсутствия возвращаемых данных.
Устройства и программы локальной системы могут давать сбой, но это бывает редко кроме случаев, когда они плохого качества. При использовании сети для подключения к другим компьютерам и программ, выполняемых на них, необходимо учитывать, что определенная часть системы будет часто давать сбой. При соединении между двумя компьютерами задействуются многочисленные устройства и службы, которые пользователем не контролируются.
Еще раз подчеркнем, что необходимо тщательно проверять значения, возвращаемые функциями, которые призваны взаимодействовать с сетевыми службами.
Следующий вызов функции:
$sp = fsockopen ( "localhost", 5000 );
не сгенерирует сообщение об ошибке в случае неуспешной попытки подключения к порту 5000 компьютера localhost.
Если переписать фрагмент кода следующим образом:
$sp = fsockopen ( "localhost", 5000, &$errorno, &$errorstr );
if(!$sp)
echo "Ошибка: $errorno: $errorstr";
будет проверяться возвращаемое значение на предмет возникновения ошибки и отображаться сообщение, которое поможет ее исправить. В этом случае вывод будет таким:
Warning: fsockopen() [function.fsockopen]: unable to connect to localhost:5000 (Попытка установить соединение была безуспешной, т.к. от другого компьютера за требуемое время не получен нужный отклик, или было разорвано уже установленное соединение из-за неверного отклика уже подключенного компьютера. ) in D:\svd\phpstroy\test\error.php on line 2
Fatal error: Maximum execution time of 30 seconds exceeded in D:\svd\phpstroy\test\error.php on line 2
Ошибки времени выполнения сложнее устранить по сравнению с синтаксическими, поскольку программа анализа (интерпретатор) не может их выявить при первом выполнении кода. Поскольку ошибки времени выполнения возникают в результате комбинации событий, могут возникать трудности в их обнаружении и устранении. Программа анализа не может автоматически указать, что определенная строка сгенерирует ошибку. Задача тестирования состоит в том, чтобы создать одну из ситуаций, приводящих к генерации ошибки.
Предупреждение ошибок времени выполнения требует в некоторой степени предвидения возможных ошибочных ситуаций с тем, чтобы предпринять соответствующие действия. Кроме того, необходимо тщательное тестирование с имитацией каждого класса ошибок времени выполнения, которые могут произойти.
Это не означает, что необходимо пытаться имитировать все возможные ошибки. Например, MySQL может генерировать около 200 различных номеров ошибок и сообщений. Следует имитировать ошибки в каждом вызове функции, которая может вызвать сбой, а также ошибки каждого типа, обрабатываемого отдельным блоком кода.
Мы часто делаем предположения относительно вводимых пользователями данных. Если эти данные не оправдывают наших ожиданий, они могут вызвать ошибку — времени выполнения либо логическую.
Классический пример ошибки времени выполнения состоит в обработке вводимых пользователем данных, когда к ним забывают применить функцию AddSlashes() (добавления слешей). В таких случаях, если имя пользователя содержит апостроф, например, O'Генри, функция базы данных сгенерирует ошибку.
Ошибки в результате предположений относительно вводимых данных более подробно рассматриваются далее.