[Закрыть]
 
popoff.donetsk.ua
Не стремись знать все, чтобы не стать во всем невеждой. /Демокрит/
Начало | Новости | Статьи | Форум | Опросы | Карта сайта | Обо мне
popoff.donetsk.ua - Статьи - Программирование - Технологии программирования - Обработка ошибок программистами и обработка сообщений об ошибках программами
Я это делаю
Персональное меню
Голосование
Деньги, либо любимое занятие? Постоянный адрес этого вопроса
Ваш возраст (не обязательно):

Введите целое число от 3 до 99.
Почему? (не обязательно):
Другие вопросы
Поиск по сайту
Реклама
Обмен электронных валют
money.dn.ua
Статистика

Обработка ошибок программистами и обработка сообщений об ошибках программами

Постоянный адрес статьи

Содержание

Факт о наличии ошибок в программах

Факт. Любая программа содержит ошибку.

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

Интересно такое наблюдение: чем меньше программист знает и умеет, тем сильнее он уверен в отсутствии ошибок в его программе.

Из этого факта следует два следствия.

Следствие 1. Допустить ошибку - очень просто.

Единственное, что нужно для того, чтобы допустить ошибку в программе - это написать программу. Больше ничего не требуется. Не нужно тратить время на то, чтобы искать, куда бы внести ошибку. Не нужно на этой ошибке концентрироваться, не нужно стараться не забыть её добавить. Не нужно даже знать языка программирования, на котором Вы хотите допустить ошибку. Можно вообще ничего не уметь и ничего не знать - для внесения ошибок в программу нужно ТОЛЬКО эту программу написать.

Следствие 2. Найти ошибку - сложно.

Собственно, если бы это было не так, то мы жили бы в мире безглючных программ, и, скорее всего, большинство пользователей вообще не подозревали бы о такой возможности, как «ошибка в программе» или «зависший компьютер».

Чтобы найти ошибку, в простейшем случае на ней нужно сосредоточить своё внимание. В более сложных случаях - запустить отладчик. В очень сложных случаях поиск ошибки может занять от целого рабочего дня до нескольких недель работы.

Что обычно делают для того, чтобы не допустить ошибку

Ошибки можно не только искать. Поиск ошибок всегда занимает много времени, поэтому профессионалы придумывают для себя много разных подходов к программированию, чтобы избежать появления ошибок в программе. Здесь я приведу несколько примеров - лишь для демонстрации того, что такие подходы существуют.

Например, один из таких подходов состоит в том, что при сравнении переменной с константой на первом месте нужно ставить константу, а на втором - переменную. Например:

<?php if(1==$a)
?>

Эта рекомендация предназначена для тех случаев, когда человек может попутать оператор == с оператором = и машинально поставить присваивание вместо сравнения. Если на первом месте стоит переменная и Вы поставите присваивание, то программа отработает нормально, но условие будет проверено не корректно. Например:

<?php $a=2;
  if(
$a=1)
    echo 
"2=1";
  else
    echo 
"2!=2";
?>

Выведет 2=1, что не соответствует действительности. Если на первом месте поставить константу, то такая неопрятность с операторами приведёт к тому, что интерпретатор PHP сгенерирует для Вас сообщение об ошибке, что значение константы нельзя изменить - в таком случае найти ошибку будет гораздо проще.

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

Есть ещё одна интересная рекомендация, которая позволяет защитить Ваше приложение на PHP c использованием баз данных от SQL-инъекции. Она состоит в том, что при вставке любых данных в запрос, эти данные берутся в кавычки и прослешиваются специальной функцией. Например:

<?php mysql_query("
    select
      *
    from
      my_table
    where
      id='"
.mysql_real_escape_string($_POST['id'])."'
    "
);
?>

Если Вы приучите себя на уровне автоматизма все переменные в запрос вставлять так и только так, то это почти гарантирует Вам отсутствие SQL-инъекций на Вашем сайте. Почему «почти»? Потому что Вы можете быть не достаточно внимательны или достаточно ленивы, и однажды вставить значение переменной без кавычек или без обработки функцией mysql_real_escape_string().

Скажете, «а зачем так делать, если данные предварительно проверяются?» Ответ прост: программа проверки данных может содержать ошибку. Вы можете не достаточно внимательно проверить данные, и тогда в Вашей программе возникнет потенциальная «дыра».

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

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

Обработка ошибок и обработка сообщений об ошибках

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

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

Никогда не следует путать обработку ошибок и обработку сообщений об ошибках. Например, оператор @ управляет обработкой сообщений об ошибках.

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

Между обработкой ошибок и обработкой сообщений об ошибках существует известная связь:

  • Чем больше информации содержит в себе сообщение об ошибке, тем проще программисту исправить эту ошибку.

  • Чем раньше программист прочтёт сообщение об ошибке, тем раньше он эту ошибку исправит.

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

  • Системное сообщение об ошибке, выведенное в браузере хакеру (любой посетитель Вашего сайта потенциально может являться хакером), похоже на транспарант с надписью: «Взламывать здесь!». Хакер, вероятно, будет рад обработать ошибку вместо программиста.

Переменные, которые должны были прийти из формы, но не пришли

Есть мнение, что такой код:

<?php $action = isset($_GET['action']) ? $_GET['action'] : null;
?>

длиннее, непонятнее и хуже, чем такой:

<?php $action=@$_GET['action'];
?>

Причина этому мнению - лень.

Во втором случае автор считает, что совпадут одновременно все следующие условия:

  • Сообщения об ошибках выводятся на экран пользователю и не записываются в журнал - иначе журнал заполонило бы сообщениями об ошибках в случае, если эта переменная может часто не передаваться

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

  • Даже если отключить только notice, а записывать только «более важные» (на самом деле, всё, ошибки, предупреждения и notice - имеют исключительную важность и их нельзя сравнит и сказать, что что-то важнее чего-то) - то будут проигнорированы все до единого notice, в том числе и те, которые могут указывать на потенциальное место ошибки в скрипте (не только в этом месте, и не только связанные с несуществующим индексом массива - но и вообще все). А вот лично Вы знаете, какие ещё notice там бывают, к чему может привести их необработка и чего Вы можете не заметить, если будете их подавлять?

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

  • В будущей версии PHP значение по умолчанию не изменится (в истории РНР был случай, когда это значение поменялось - раньше считалось, что это пустая строка)

Рассмотрим ещё пример, оставленный в форуме:

<?php @$array[$key1][$key2] .= get_something();
?>

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

А вот как предлагаю сделать я:

<?php function http_load_unescape($v)
  {
    if(!
get_magic_quotes_gpc()) return $v;
    if(!
is_array($v)) return stripslashes($v);
    
$k=array_keys($v);
    for(
$i=0;$i<count($k);$i++)
      
$v[$k[$i]]=http_load_unescape($v[$k[$i]]);
    return 
$v;
  }

  function 
http_load_get(&$get)
  {
    
$k=array_keys($get);

    for(
$i=0;$i<count($k);$i++)
    {
      
$c=$k[$i];
      if(!isset(
$_GET[$c])) continue;
      
$get[$c]=http_load_unescape($_GET[$c]);
    }
  }

  
$get=array('id' => 0,'s_filter' => '');
  
http_load_get($get);
  echo 
$get['id'];
?>

Слишком много? Но Вам не нужно в каждом Вашем файле писать весь приведённый выше код. Функции можно вынести в отдельный файл и подключать его, тогда в Вашем коде останутся только последние три строчки.

Вот преимущества этого кода:

  • В массиве $get есть все те и только те элементы, которые Вы определили при начальной инициализации.

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

  • Как дополнительная фича - Вы сразу же можете забыть о настройке «волшебных кавычек» и считать, что они всегда отключены.

    Вы считаете отключение волшебных кавычек дурным тоном? Отключение волшебных кавычек повышает вероятность SQL-инъекции? Но правда состоит в том, что прослешивать перед подстановкой в SQL-запрос нужно не только те данные, которые пришли от пользователя, но и вообще все данные. Включение волшебных кавычек Вас развращает и приучает к тому, что данные можно не прослешивать, и тогда легко забыться и не прослешить что-нибудь такое, что следовало бы прослешить даже при включённых волшебных кавычках.

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

<?php while(@$i++<100) { ... }
  @
$statistic[date("Y-m-d")]++
?>

Из нашего опыта: из-за того, что не было явной инициализации переменной, одна таблица стала нумероваться не с 1, а с 290. Ошибку искали целый день.

Писать программы без явной инициализации переменных нельзя ни на одном языке программирования!

Когда Вы пишите так, как в приведённом выше примере, то Вы исходите из того, что переменная $i не определена в начале цикла и её значение станет равным 1 после первого $i++. Это, наверное, так, если этот код содержится внутри коротенькой функции и нигде перед этим циклом не задаётся явно значение переменное $i, отличное от 0. Если же где-нибудь на две страницы выше перед этим циклом будет другой, такой же, то поведение этого цикла уже будет совсем не таким как ожидалось.

Не следует полагаться на внимательность и на то, что Вы не забудете учесть, какие переменные у Вас уже инициализированы, а какие - нет. Приучив себя писать так в одном месте, Вы можете не заметить, как напишете так же в другом месте, и тогда это другое место будет глючить, а Вы - будете долго выяснять, почему 2*2=10.

Неправильные значения переменных

Мы в нашей работе часто сталкиваемся с людьми, которые полагают, что для того, чтобы не делать лишних проверок, является ли переменная - массивом, можно написать так: @foreach($..) или @count($..).

Всегда нужно проверять все исходные данные. И проверка на то, является ли переменная массивом, в случае, когда Вы допускаете обратное - вовсе не является лишней.

Задайтесь вопросом: «в какой ситуации эта переменная может оказаться немассивом?» Скорее всего, это данные, пришедшие от пользователя. А это означает, что пользователь как-то модифицировал форму или подставил свои значения переменных в адресной строке браузера. Ваш скрипт взламывают, а Вы хотите это игнорировать?

Если значение переменной восстанавливается, например, при помощи unserialize() из базы данных, и в результате получается не массив, то это означает, что испорчена база данных. Вы хотите это игнорировать?

В Ваших скриптах не должно возникать ситуаций, когда Вы ожидаете на вход массив, а получаете не массив.

Или вот ещё одно довольно часто встречающееся заблуждение: если программа глючит, когда переменные не существуют или не того типа, то дополнительная проверка, позволяющая не писать собаку и выводить лог не на экран либо замусорит журналы ошибок (если проверка записывала сообщения в журнал ошибок) так, что сообщения именно от проблемной строки будет очень сложно найти, либо позволит явно прописать код для обработки особой ситуации, что никак не повлияет на общую логику программы.

В этих рассуждениях кроется ошибка. Ошибка состоит в том, что предлагается записывать в журнал ошибок сообщения об ошибках в случае, когда неправильный тип переменной - не является чрезвычайной ситуацией. На самом деле, первый или второй вариант следует выбирать не на основании, что Вам больше нравится (писать сообщения об ошибках в журнал или задавать действия по умолчанию), а на основании, являются ли неправильные переменные допустимыми в этом месте (если переменные допустимы - задаём действие по умолчанию, если нет - пишем сообщение об ошибке).

В этих рассуждениях также указывается на то, что нет необходимости явно прописывать реакцию программы на неправильные исходные данные, если реакция интерпретатора по умолчанию совпадает с желаемой реакцией. На самом деле, это не правильный подход - такими рассуждениями мы привязываемся к конкретной реализации интерпретатора. И даже если вероятность того, что в других версиях поведение интерпретатора по умолчанию изменится - очень маленькая, привязка к текущей версии - это, всё же, дурной тон.

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

Сообщение об ошибке можно записать в отдельную переменную и потом его использовать?

Следующее часто встречаемое заблуждение гласит: раз при работе с функциями mysql доступно получение текста ошибки через mysql_error() и ручная запись его в лог, то предупреждение PHP является излишним и его можно подавить.

Вот наши возражения:

  • Сообщение об ошибке - это манна небесная для программиста.

    Лучше я потрачу своё время, внимательно прочитаю и обработаю 1000 сообщений об ошибках, которые для меня сгенерировал интерпретатор PHP, чем я буду сидеть и в ужасе ожидать, что клиент скажет фразу типа: «У тебя там ничего не работает!», после которой я должен буду долго выяснять, что именно не работает, при каких обстоятельствах, в какой строчке ошибка? Никакой клиент не скажет мне номер строчки - эта информация появляется только в сообщении об ошибке.

    Поэтому, это только те люди, которые не хотят тратить своё время на создание хороших, добротно сделанных программ, которых вполне удовлетворит, если программа работает на контрольном примере - только они будут игнорировать сообщения об ошибках.

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

  • После каждого mysql_query() и всех остальных операций Вы должны НЕ ЗАБЫТЬ проверить правильность выполнения запроса и записать сообщение об ошибке в журнал ошибок в случае ошибки.

    Записать сообщение об ошибке в журнал ошибок. А Вы записываете эти сообщения? Вы записываете все сообщения, после обращения к любой функции MySQL?

    В принципе Вы при любых обстоятельствах не должны забывать проверять правильность выполнения вызываемых функций. Но полагаться на память - дело не благодарное: не слишком много нужно забыть, чтобы пропустить ошибку.

  • Приведённую выше мысль иногда начинают развивать и распространять её на любую функцию, которая может не выполниться нормально. Забывая при этом, что у любой нету функции любая_error(), и системное сообщение будет единственным подробным описанием ошибки.

А вот с тем, что для каждой функции mysql_* нужно иметь собственный обработчик, мы, без сомнения, согласны. Вот Вам пример:

<?php function mysql_query_log($q)
  {
    
$r=mysql_query($q);
    if(!
$r)
      
trigger_error(mysql_error().' '.$q,E_USER_ERROR);
    return 
$r;
  }
?>

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

После добавления этой функции просто меняете все вызовы mysql_query() на mysql_query_log() и наслаждаетесь.

В этот обработчик можно добавлять специй на свой вкус: поскольку все запросы выполняются через эту функцию, можно легко посчитать общее количество запросов на странице; можно добавить возможность отслеживания на уровне PHP долгих запросов и генерации ошибок в случае, если запрос выполняется слишком долго. С использованием функции debug_backtrace() можно просмотреть весь стек вызовов, что значительно упростит поиск строки с ошибкой.

Ошибки внутри функции eval()

Совершенно, на наш взгляд, чудовищное заблуждение, гласит: сообщения об ошибках можно подавлять, если я хочу посчитать значение арифметического выражения, но при этом допускаю, что в выражении может быть ошибка: $x='an invalid string'; @eval("\$y=$x+2");

Никогда не используйте в Ваших программах функцию eval() без особой на то необходимости. Использование этой функции значительно увеличивает шансы Ваших скриптов на подверженность PHP-инъекциям.

Что произойдёт, к примеру, если $x='0;mysql_query("drop database mysql");//';? Это хорошо, если не окажется прав на удаление той таблицы. А если окажутся? Размышляя об арифметических выражениях, Вы продумали такую возможность?

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

А если не хотите писать, можете воспользоваться одной из готовых.

Ошибки при работе с файлами

Часто можно услышать: файлы можно открывать так: @fopen(не существующий или недоступный из-за прав файл), @opendir() и т.д.

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

В приведённом примере вопрос не в том, интересуют эти сообщения или нет, а в том, показывать эти сообщения пользователю или не показывать.

Есть ещё одно замечание: сообщение, сгенерированное интерпретатором PHP скажет о том, почему именно файл не может быть открыт. Для поиска причины ошибки важно знать, толи файл не существует, толи к нему нет доступа, толи это ограничения SAFE_MODE, толи ещё что.

Проблема новичков состоит в том, что они смотрят эффективный работающий код, но выносят из этого только одно суждение: «В таких случаях нужно ставить собаку». И выкидывают «лишние» части конструкций.

Некоторые особо упорные спорщики говорят: функция file_exists() возвращает:
(1) false + WARNING, если доступ в чужой каталог получить нельзя (2) false, если доступ есть, но файла нет
(3) true, если файл есть.
Вы предлагаете для ответа на вопрос «ЕСТЬ ЛИ ТАКОЙ ФАЙЛ» пользоваться не кодом из одной функции, а как минимум дополнительно поставить if { и какой-то сопутствующий код. } Это приведёт к раздутию и нечитаемости кода. Мне же разницы между (1) и (2) улавливать не нужно.

Замечание: Файл может быть удалён между вызовом file_exists() и fopen().

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

Следующее возражение звучит примерно так: Вы предлагаете все функции переписать своими. Очень хорошо. А хоть кто-нибудь серьёзно это пытался сделать до Вас? Сколько это займёт времени? А кто «сверху» будет оплачивать такую работу?

Сколько стоит написать четыре строчки программы, из которых одна - это заголовок функции и две - это открывающая и закрывающая операторная скобка?

Лично я очень ценю своих клиентов и мне нравится делать людям приятное: лёгким движением руки, добавив у себя в коде в одном месте вызов одной функции добавить для клиента бесплатно функциональность, без которой клиент имел бы гарантированную головную боль. Свои деньги я заработаю.

Случаи, которым я не нашёл объяснения

Объяснение, на самом деле есть, просто оно становится видным не сразу, а после повторного прочтения всей статьи. Объяснение состоит в том, что люди, предлагающие делать так, как выделено курсивом ниже, исходят из того, что у них всегда включена настройка display_errors=on. Эту настройку следует всегда отключать при введении сайта в эксплуатацию.

@mysql_free_result после не SELECT запроса (чистка памяти на на всякий случай)

Но проверить, была ли выделена память при выполнении sql-запроса - очень просто. Для этого служит функция is_resource(). Почему бы ей не воспользоваться?

@set_time_limit(300)/ini_set() и аналогичное, когда менять время запрещено safemod'ом

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

@getimagesize(), если файл не картинка или нет прав и аналогичные функции (пользователю пишут словами - левую картинку сунул, а не китайскую грамоту в виде php warning; прерывают программу)

Убирание @ ни коем образом ничему не поможет. Например, в случае с открытием файлов $f=@fopen всегда есть последующий
if (!$f) exit("файл не открыт");
(писать программы без такой проверки в КАЖДОМ месте нельзя ни на одном языке программирования). В других случаях результат невыполненности функций проверятся отдельно, например
if ($d=@opendir(".")) { ...; closedir($d); }
Абсолютно не важно, будет ли открыт каталог, т.к. алгоритм подразумевает отсутствие результата.

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

Дополнительно, пользователю не должны никакие системные сообщения об ошибках показываться, а не только это. Собака же скроет сообщения только в этом одном месте.

Вопросы и ответы

Как можно сделать так, чтобы ошибок в программе не было?

Очень просто: закрыть глаза на ошибки. Просто не искать ошибки. Убедить себя и всех в том, что ошибок нет. Для этого можно подавить сообщения об ошибках, не вести журнал ошибок, и вообще выключить все ошибки наф***к. Кстати, именно с этой целью - не найти ошибок - программы отдают в эксплуатацию, не протестировав их.

Если такой способ Вам не подойдёт, то Вам придётся смириться с тем, что ошибки в Вашей программе всё равно будут. Единственное, что Вы можете сделать - это уменьшить их количество. Для этого нужно внимательно наблюдать за работой программы, и фиксировать все, и ещё раз все ошибки, которые возникают в Вашей программе.

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

Часто идёт спор о том, что лучше: $login (с включённым register_globals или любым иным способом записанные переменные из массива $_GET в локальное или глобальное пространство имён) или $_GET['login'].

Написать $login вместо $_GET['login'] это, без сомнения, очень, очень экономит время разработки программы. Это, правда, если не учитывать время на поиск ошибок или верить в то, что к ошибкам подобные « упрощения» не приведут.

Или так: «Это ж сколько лишних символов на клавиатуре набирать! Да ещё дополнительный вызов функции - он не нужен!»

<?php id='".mysql_real_escape_string($_POST['id'])."'
?>

Люди, которые считают приведённый выше код излишеством, убедили себя и других в том, что в их программе нет ошибок. И находят тому подтверждение: «Конечно! Мой сайт работает уже так давно и до сих пор никто не взломал!». Эти люди забывают о том, что просто никто не пытался взломать. А когда их сервер превратится в зомби, рассылающий спам и занесённый во все блек-листы, будут бегать по форумам и спрашивать: «Ну как такое возможно?! Это всё глючный linux!!!»

Люди, которые считают использование специальных технологий для избежания ошибок напрасной тратой времени, скорее всего, уверены в том, что поиск ошибок и отладка программы не являются составной частью разработки программы. Действительно, если разработка программы состоит в наборе текста программы, то с точки зрения скорости набора - чем меньше, тем лучше.

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

error_reporting(0) для всей программы ставить нельзя, ровно как и display_error=off. Если ошибка будет в коде, появится на экране PHP warning/error, портящий дизайн пользователю, от чего тот соизволит отбагорепортовать.

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

Зачем мучить пользователя? Зачем он должен багрепортовать, если багрепорт можно сгенерировать автоматически? Не будет ли удобнее записать сообщение в журнал ошибок и выслать этот журнал администратору на почту?

Боитесь, что ящик будет засорён уведомлениями? Не высылайте сообщения размером больше 1 кб - одной темы сообщения вполне достаточно, чтобы понять, что произошла системная ошибка и что нужно открыть файл с журналом ошибок и посмотреть, что там за ошибки; не высылайте сообщения чаще чем один раз в час.

А если это клиент? Лично мне не нравится, когда клиент говорит мне, что у меня всё глючит, а я при этом начинаю у него выпытывать, когда и при каких обстоятельствах. Мне больше нравится, когда клиент вечером ходит по сайту, и, если вдруг он нарывается на баг, то утром, когда он говорит, что «там ничего не работает!», я ему говорю: «Да, вы вчера вечером ходили там-то и там-то, занимались тем-то, и там действительно были ошибки. Я их уже исправил и сейчас всё работает!» или даже так: «Как не работает?! Ану-ка давайте попробуем! Смотрите, видите?! Всё работает!».

У меня есть пример скрипта, в котором оператор @ используется 37 раз. Я совсем не уверен что если ввести 37 проверок, то время выполнения ощутимо замедлится. То, что его придётся дольше писать (набивать) - это и так понятно.

А не требуется 37 проверок.

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

Это же касается и других случаев: всегда можно продумать схему обработки данных, при которых не нужно ставить столько проверок.

Также, разработка программы лишь на какую-то долю процента состоит в её наборе. Основное время при разработке любой программы тратится именно на поиск ошибок.

Для чего был введён оператор @?

Вместо того, чтобы прятать ошибки программиста, разработчики PHP вытащили их на самое видное место, при этом дав возможность полностью запретить вывод ошибок. Почему они так сделали - не очень понятно, наверное для большей популяризации языка. Искусственное подавление ошибок походит на боязнь стоматолога и помогает убедить себя в отсутствии ошибок: «Конечно, ошибок нет! Смотрите, видите? Ни одного сообщения!».

Что лучше - собственное сообщение об ошибке или сообщение PHP?

Технология выбора - простая: чем больше информации, тем лучше.

Сообщение PHP не содержит в себе IP-адрес посетителя, а он требуется для обработки ошибки? Пишите своё сообщение.

В своём сообщении Вы не можете установить причину ошибки, а знаете только о факте её возникновения? Пишите сообщение PHP.

Хотите получить максимум? Пишите оба сообщения.

Особенности кода любителей-собаководов.

Если программист собственноручно заботится об обработке исключительных ситуаций

<?php @fopen(...) OR die(__FILE__.__LINE__);
?>

это вполне приемлемо.

Но вот когда в процессе написания кода проявляется небрежность, спешка, невнимательность и вторая часть приведённой конструкции пропускается, вот тогда эти собаки становятся ночным кошмаром.

Убрать всех собак и включить ошибки помогает не очень сильно, так как со всех сторон начинаются сыпаться notice.

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

Очень «радуют» конструкции типа

<?php @$$func(current($$var));
?>

Вызов функции, имя которой записано в переменной с именем, которое записано в другой переменной, в качестве аргумента этой функции передаётся текущий элемент массива, который хранится в переменной с именем, записанным в четвёртой переменной. При этом всём разработчик вполне допускает, что эта конструкция может не работать правильно, и если она не отрабатывает правильно, то это нормальная, обычная ситуация.

Как поступает автор этой статьи с собаками?

Автору этой статьи всё равно, есть в его коде собаки или нет в его коде собак, потому что автор этой статьи в начале каждого скрипта выполняет последовательность:

<?php set_error_handler('_debug_handler');
  
ini_set(&quot;log_errors&quot;,&quot;1&quot;);
  
ini_set(&quot;error_log&quot;,'/var/www/popoff/writable/debug/error.log');
?>

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

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

А как же ш автор этой статьи отлаживает скрипты? Ведь очень не удобно каждый раз лезть в журнал и проверять, возникла ли ошибка - гораздо удобнее видеть сообщение прямо на странице!

У нас есть два режима работы скриптов: стандартный и отладочный.

В стандартном режиме никакие сообщения об ошибках не появляются в браузере. Всё пишется только в журнал.

В режиме отладки сообщения об ошибках записываются в журнал и дублируются выводом в браузер. При этом сообщения об ошибках с точки зрения системы являются отладочной информацией. Отладочная информация, кроме сообщений об ошибках, может содержать в себе также, например, информационные сообщения о прохождении контрольных точек скриптов, значения переменных, количество выполненных SQL-запросов, общее время генерации страницы и времена выполнения контрольных участков скриптов. В общем, всё, что может понадобиться для улучшения качества программного продукта.

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

Чтобы не пропустить сообщение об ошибке, в случае возникновения ошибок на странице добавляется надпись: Errors occured («Возникли ошибки»). Видя эту надпись, мы открываем отладочную информацию и просматриваю сообщение об ошибке.

Пример странички с отладочной информацией (для показа отладочной информации кликните по Show debug info в самом верху страницы).

Такой простой вопрос - «Использовать ли собаку?» Зачем так много написано?

Если бы ответ на этот вопрос было бы однозначное «да» или однозначное «нет», то в первом случае эту статью вообще не нужно было бы писать, а во втором - его исключили бы из языка.

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

Резюме

  • Никакие системные сообщения об ошибках не должны показываться пользователю.

  • Никакие системные сообщения об ошибках не должны игнорироваться программистом.

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

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

  • Любая программа содержит ошибку. Выражение 2*2 - это программа. Следовательно, это выражение содержит ошибку. (Утрированно, но суть в том, что всегда нужно быть настороже: даже очень простые на первый взгляд выражения могут содержать в себе подводные камни.)

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

  • Часто путают обработку ошибок с обработкой сообщений об ошибках. Следует понимать, что обработка ошибок - это процесс исправления этих ошибок в программе программистом. Обработка сообщений об ошибках - это действия программы с текстом сообщения: его можно показать на экран, записать в журнал, отправить по электронной почте и т.п. Понятно, что цель - это исправить ошибки в программе, то есть обработать ошибку; при этом совершенно не важно, каким именно способом программист узнает о возникновении ошибки.

  • Собака может применяться только тогда, когда программист предусмотрел собственные средства обработки сообщений об ошибке, то есть, будет полностью проинформирован о характере ошибки. В качестве источника информации для такого средства можно порекомендовать set_error_handler(), $php_errormsg, debug_backtrace() и error_log().

Интересные цитаты

  • Любая программа содержит ошибку. Допустить ошибку - очень просто. Найти ошибку - сложно.

  • Единственное, что нужно для того, чтобы допустить ошибку в программе - это написать эту программу. /Popoff/

  • Сообщение об ошибке - это манна небесная.

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

  • Сообщений об ошибках много не бывает. Всякая информация полезна.

  • Эффект страуса: «если я ошибку не нашёл, значит её нет». Не будьте страусом! (Замечание: страусы не прячут голову в песок - это наговорил на них пару тысяч лет назад Гай Плиний, но, что удивительно, до сих пор некоторые в это верят) /Popoff/

  • Писать программы без явной инициализации переменных нельзя ни на одном языке программирования!

  • Ошибка не должна быть частью алгоритма. /Фанат/

  • Сообщение об ошибке - хлеб программиста. Ему на него молиться надо. /Фанат/

  • «Дыра» – это место в программе, рядом с которым пытливый наблюдатель увидит вывеску: «Взламывать здесь», хотя для обычного человека эта вывеска может быть не заметна. /Popoff/

P.S.

Пока я писал эту статью, кто-то оставил в форуме вопрос:

Запускаю такой файл:
<?php if (!@scheck()) die('HACKED');
  echo 
'Im not die?!';
?>
Ничего не выводит. Совершенно ничего. Может кто подсказать, чего следует ожидать от такого кода?

Возможно, это сообщение не было бы оставлено в форуме, если бы программист, написавший этот код, не поставил бы собаку.

Благодарности

Основная часть материала для этой статьи взята из форумов PHP-клуба.

Смотрите также

Документация по PHP. Функции обработки и протоколирования ошибок.
http://php.net/manual/ru/ref.errorfunc.php
Прочтите внимательно всё, что написано на этой странице! Разберитесь в том, для чего нужны и как работают те функции, на которые есть ссылки на этой странице!

Отладка
Как искать ошибки в программах? Что такое отладка и для чего она используется.

Сравнение скорости работы скриптов с @ и без:
http://popoff.donetsk.ua/try/dog.html
Тесты, демонстрирующие, что с использованием @ скрипт работает в среднем в 3-5 дольше, чем с использованием isset(). Тест имеет только теоретическую ценность, так как ускорение, получаемое от замены одной @ на isset() - очень незначительное.

Что считать ошибкой?
С точки зрения пользователя и с точки зрения программиста.

Тест на скорость и некоторые примеры кода были взяты из этого топика форума:
http://phpclub.ru/talk/showthread.php?postid=615211#post615211

______________________________
Юрий Попов, popoff.donetsk.ua, для PHPClub Cookbook & FAQ

Последняя модификация: 25.11.07 23:50

Не проходите мимо! Оставьте Ваш комментарий в форуме! >>>