Подавление ошибок php

Некоторое время назад мне сделали замечание, что использовать @ — «плохая привычка». На предложение доказать, что это именно так, я получил ссылку на Хабр, со статьей про управление ошибками.

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

На мой взгляд, плохого здесь нет и быть не может. Применение @ — это такое же управление ошибками, как и функция error_reporting() или директива в php.ini.

Однако, детальный разбор показал несколько иное.

Немного теории


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

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

При своём запуске php смотрит в конфигурационный файл, обычно php.ini, который среди прочего содержит и директиву обработки ошибок, это error_reporting.

В комментариях к этой директиве содержится описание уровней обработки ошибок, приведу его полностью:
; Error Level Constants:
; E_ALL             - All errors and warnings (includes E_STRICT as of PHP 6.0.0)
; E_ERROR           - fatal run-time errors
; E_RECOVERABLE_ERROR  - almost fatal run-time errors
; E_WARNING         - run-time warnings (non-fatal errors)
; E_PARSE           - compile-time parse errors
; E_NOTICE          - run-time notices (these are warnings which often result from a bug in your code, but it's possible that it was intentional (e.g., using an uninitialized variable and relying on the fact it's automatically initialized to an empty string)
; E_STRICT          - run-time notices, enable to have PHP suggest changes to your code which will ensure the best interoperability and forward compatibility of your code
; E_CORE_ERROR      - fatal errors that occur during PHP's initial startup
; E_CORE_WARNING    - warnings (non-fatal errors) that occur during PHP's initial startup
; E_COMPILE_ERROR   - fatal compile-time errors
; E_COMPILE_WARNING - compile-time warnings (non-fatal errors)
; E_USER_ERROR      - user-generated error message
; E_USER_WARNING    - user-generated warning message
; E_USER_NOTICE     - user-generated notice message
; E_DEPRECATED      - warn about code that will not work in future versions of PHP
; E_USER_DEPRECATED - user-generated deprecation warnings
Очевидно, что в зависимости от ваших потребностей, вы можете установить любой уровень обработки ошибок: отключить их все, выводить только предупреждения, уведомления и т.д.

Немного про ошибки


По большому счету можно выделить 4 типа ошибок:
E_FATAL — ошибка ужасна, скрипт не может продолжать работу.
E_ERROR — ошибка серьезная, но работать можно, скорее всего, неправильно.
E_WARNING — предупреждение, что ты ленивый программист и не пользуешься IDE. на работу не особо влияет, хотя и может порушить логику приложения.
E_NOTICE — уведомление, что ты не объявил переменную, или обратился к несуществующему элементу массива. Лично я вообще не считаю это ошибокой, ибо поломать логику работы приложения она не может.

Конечно, есть и предупреждения об использовании устаревших функций, и ошибки совместимости, но в целом — их можно отнести к 4м этим типам.

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

Немного практики


Итак, давайте определим наш дефолтный код, на котором будем проверять теорию:
error_reporting(-1); // вывод всех ошибок
$time = microtime(true);
$array = array();
for ($i = 0; $i < 1000000; $i++) {
	$key = rand();
	if (array_key_exists($key, $array)) {
		$array[$key] += 1;}
	else {
		$array[$key] = 1;
	}
}
echo microtime(true) - $time;
Здесь мы обращаемся к случайному ключу массива, проверяем его наличие и, если он есть, прибавляем единичку, а если нет — просто объявляем элемент массива.

Этот код отрабатывает у меня за 2.2096891403198. Хороший, правильный код, без ошибок и уведомлений, миллион итераций проходит за 2.2 сек.

А теперь код, который будет обращаться к несуществующему элемента массива, с отключенными уведомлениями:
error_reporting(0); // все ошибки скрыты
$time = microtime(true);
$array = array();
for ($i = 0; $i < 1000000; $i++) {
	$key = rand();
	$array[$key] += 1;
}
echo microtime(true) - $time;
Этот код гарантированно генерирует E_NOTICE при обращении к несуществующему элементу массива, но я заранее их спрятал функцией error_reporting(0).

Этот плохой, нехороший код со спрятанными ошибками выполняется за 1.8394958972931. Да, он быстрее, ибо не проверяет наличие ключа в массиве.

Обратите внимание: я подавил возможные уведомления, указав error_reporting(0) и плохой код работает быстрее. Но я сжульничал.

А теперь серьезно


В моем примере используется array_key_exists(), который, как мне недавно доказал Евгений Борисов, гораздо медленнее обычного isset().

А вот что будет, если немного изменить наш цикл:
error_reporting(-1);
$time = microtime(true);
$array = array();
for ($i = 0; $i < 1000000; $i++) {
	$key = rand();
	if (isset($array[$key])) {
		$array[$key] += 1;}
	else {
		$array[$key] = 1;
	}
}
echo microtime(true) - $time;
Этот код выполняется за 1.3799779415131. Он быстрее чуть ли не в 2 раза, чем c array_key_exists() и работает точно так же.

И вот, первый серьёзный вывод: проверка ключа массива может работать медленнее, чем подавление ошибок. То есть, 100% правильный код не гарантирует скорости, сам по себе.

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

А теперь финальный тест — подавление ошибки через @.
$time = microtime(true);
$array = array();
for ($i = 0; $i < 1000000; $i++) {
	@$array[$key] += 1; // подавляем E_NOTICE
}
echo microtime(true) - $time;
И вот этот код работает уже за 2.2371110916138. То есть, медленнее чем правильный код с isset() и медленнее, чем отключение ошибок заранее. И дело тут не в том, что подавление ошибок происходит в цикле, @ тормозит само по себе, даже если его указывать с безошибочным кодом. @ тормозит работу, без вариантов.

Однако, наш неправильный код работает наравне с правильным, который использует array_key_exists().

Выводы


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

И вот ответ на вопрос: плохо ли подавлять ошибки php? После проведённых тестов ответ однозначен: да, плохо. Не люблю признавать свою неправоту, но так и есть. Код с ошибками работает медленнее и подавлять его через @ — не хорошо.

Отсюда логично заключить, что ровно так же не хорошо и подавление ошибок через error_reporting(). И конечно, указание в php.ini E_ALL & ~E_NOTICE — тоже плохо.

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

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

Кстати говоря, с тех пор как я начал использовать PhpStorm, количество таких глупых ошибок резко сократилось. Если кто не читал — вот заметка про работу с этой прекрасной IDE (требуется подписка).
Василий Наумкин
06 июля 2013, 16:45
modx.pro
3
11 161
0

Комментарии: 6

Это сообщение было удалено
    Василий Наумкин
    07 июля 2013, 05:37
    0
    Коля, ты забанен за то, что не умеешь нормально вести диалог и доказывать свою точку зрения.

    Делать себе замечания я умею и сам.
    Peter Zenin
    07 июля 2013, 06:53
    0
    По-моему программистов не самоучек быть не может :-)
    Спасибо за работу!
      Это сообщение было удалено
      Alex Vakhitov
      07 июля 2013, 08:16
      0
      Такое использование использование символа "@" мне нравится больше (:
        Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
        6