Статьи

Обнаружение ошибок без лишних усилий (или лучше!) С помощью утверждений PHP

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

Утверждения могут сделать это за нас.

Не пойми меня неправильно. Тестирование и отладка являются важными частями разработки. Дайте им короткий риск на свой страх и риск. Но правильное использование утверждений приводит к более раннему и более тщательному обнаружению и диагностике ошибок. Это приводит к более легким исправлениям, которые освобождают вас от более приятных действий. Как? Так как:

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

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

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

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

Что такое утверждения?

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

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

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

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

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

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

Использование утверждений

На первый взгляд, утверждения в PHP выглядят так же, как вызов функции:

assert(false); 

Если утверждения включены (по умолчанию в PHP), этот оператор вызовет предупреждение:

 Warning: Assertion failed in test.php on line 43 

В то время как:

 assert(true); 

не вызовет никаких предупреждений.

В PHP аргументом assert является либо логическое значение (как в предыдущих примерах), либо строка, которая должна оцениваться как код PHP. Использование строкового параметра обычно более информативно:

 assert('false'); 

результаты в:

 Warning: Assertion "false" failed in test.php on line 43 

Вот еще два простых примера:

 $one = 1;  $three = 3;  assert("$one + $one == $three");  assert('$one + $one == $three'); 

в результате чего

 Warning: Assertion "1 + 1 == 3" failed in test.php on line 45  Warning: Assertion "$one + $one == $three"  failed in test.php on line 46 

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

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

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

 define('MAX_CACHE_AGE', 60 * 60 * 24);  //...  assert('time() - $timestamp <= MAX_CACHE_AGE'); 

И утверждение, чтобы проверить, что размер носков не XL, может быть:
define('XL', 'Extra Large'); //... assert('$socks->size() != XL');

Вы можете проверить, есть ли в таблице customer строка для записи в корзине покупок, написав функцию, скажем customer_exists , и вызвав ее в утверждении, например так:

 assert('customer_exists($cart->get_customer_id())'); 
Где использовать утверждения

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

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

  1. Переключатель без значения по умолчанию.
    Когда оператор switch не имеет регистра по умолчанию, часто существует неявное предположение, что все возможности были охвачены. Это можно сделать явным с помощью утверждения:
     switch ($some_variable) {  case 'a':    //...    break;  case 'b':    //...    break;  //...  default:    assert('$some_variable != ' . "$some_variable");    break;  } 

    Утверждение потерпит неудачу, если будет достигнут случай по умолчанию, и в сообщении об ошибке будет описано «невозможное» значение.

    Аналогичная возможность существует, когда ряд операторов if-else предназначен для охвата всех случаев.

  2. Тип проверки.
    PHP — это свободно и гибко типизированный язык, который хорошо работает в большинстве случаев. Но иногда важно, чтобы переменная была строкой, числом, объектом или какого-либо другого типа.
     assert('is_resource($fp)'); 

  3. Проверка параметров.
    Утверждения могут использоваться для проверки правильности параметров, передаваемых в процедуру.
    function some_function($positive_int_parameter) { assert('isset($positive_int_parameter)'); assert('is_int($positive_int_parameter)'); assert('$positive_int_parameter > 0'); //... }

    Важно не использовать утверждения для проверки параметров, если вам нужно сделать это в производственной системе. Например, если значение $positive_int_parameter может быть тем, что набрал пользователь, используйте обычные методы, чтобы проверить, что оно находится в диапазоне, и сделайте что-то вроде жалобы или исправьте значение, если проверка не удалась. Если мы сделаем это с утверждениями, жалоба / исправление исчезнет, ​​когда исчезнет проверка утверждений, что отнимет функциональность у производственной системы.

    Проверка результатов функции. Вы можете сделать проверки вменяемости
    $result = process_string($input_string); assert('strlen($result) <= MAX_RESULT_LEN');

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

     $output = expensive_crunching_routine($input);  assert('$output == alt_expensive_crunching_routine($input)'); 

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

  4. Многогосударственные утверждения.
    Вообще говоря, выражения в утверждениях не должны иметь побочных эффектов. Одним из важных исключений является случай, когда для утверждения требуется несколько утверждений. Например:
    assert(list($january, $ignore) = explode(',', $year_data)); assert('$january > MIN_FIRST_MONTH');

    Вызов explode обернут в assert, чтобы он не выполнялся в производственной системе. Это гарантированно будет успешным, поскольку explode возвращает false только тогда, когда разделитель является пустой строкой. Поскольку он всегда будет успешным, нет необходимости передавать параметр в виде строки (сообщения об ошибке не будет). Важное утверждение, конечно, второе.

    PHP накладывает ограничения на эту технику. Например, невозможно определить функцию или класс внутри утверждения, чтобы избежать их определения в рабочем коде. Тем не менее, снижение производительности и удобства использования «вспомогательных» функций и классов в производственном коде обычно невелико, так что это не принципиальная проблема.

С практикой вы обнаружите множество возможностей для утверждений.

Управление утверждениями

Процедура, используемая для управления утверждениями в PHP, называется assert_options . Требуется два аргумента: один для указания параметра, который нужно установить, а другой для указания значения этого параметра. Константа для включения утверждений — ASSERT_ACTIVE с возможными значениями 0 или 1. Так что во время разработки вы захотите сделать это где-то на ранней стадии
assert_options(ASSERT_ACTIVE, 1);
но в производстве он должен читать
assert_options(ASSERT_ACTIVE, 0);
Вы также можете контролировать, прекращаете ли вы выполнение при сбое подтверждения (ASSERT_BAIL) и (ASSERT_BAIL) ли предупреждения во время оценки подтверждения (ASSERT_QUIET_EVAL) . Будьте осторожны с ASSERT_QUIET_EVAL ! Если он включен и возникает проблема с выполнением написанного вами утверждения, возможно, из-за синтаксической ошибки или из-за того, что вы вызвали несуществующую функцию, PHP не скажет вам, и ваше приложение может вести себя странно или останавливаться, когда достигает это утверждение. Лучше всего, чтобы оценки производили шум, устанавливая его на 0 (по умолчанию), если только у вас нет особых причин для его включения.

Два других интересных варианта — ASSERT_WARNING и ASSERT_CALLBACK . Это позволяет вам настраивать реакцию вашего приложения на утверждения. По умолчанию ошибка подтверждения генерирует предупреждение, как мы видели ранее. Но вы можете получить больший контроль над обработкой утверждений, написав собственную процедуру обратного вызова и установив ее с помощью ASSERT_CALLBACK . Если вы это сделаете, вы можете подавить предупреждения по умолчанию с помощью ASSERT_WARNING . Давайте посмотрим на пример.

Получение фантазии

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

Существует решение в форме пакета с открытым исходным кодом, называемое phpAssertUnit . Он остается «в стороне», предоставляя отдельное окно Assertion Reporter для сообщения о неудачных утверждениях. Он также предоставляет возможности утверждений для JavaScript и PHP.

Я призываю вас изучить пакет, но в этой статье я просто покажу вам, как использовать Assertion Reporter для просмотра ошибочных утверждений PHP. Основная идея состоит в том, чтобы написать обработчик утверждений PHP, который отправляет результаты в Assertion Reporter, и зарегистрировать обработчик в качестве обратного вызова подтверждения PHP. Комментарии здесь объясняют детали.

 <?php  include_once('assert.mod');    // phpAssertUnit PHP code  define('FAILED_PHP_ASSERTION',0);  // used to create a                                        // failed phpAssertUnit                                        // assertion  assert_options(ASSERT_CALLBACK,    phpAssertUnit_callback);          // register the handler  assert_options(ASSERT_WARNING, 1);  // set to 0 for no                                        // on-page warning  assert_options(ASSERT_QUIET_EVAL, 0); // leave off if you can                //  function phpAssertUnit_callback($file, $line, $code) {                                        //    global $Assert;                  // from phpAssertUnit  //  // Windows: take care of ':' because it's a trigger  // for assertUnit, and prepare '' for HTML display  // this should have no effect on filepaths from  // Unix/Linux unless they contain these characters).  //    $f = str_replace(':', '&#'.ord(':').';',  $file);    $f = str_replace('\', '\\', $f);  //  // If PHP assertion was called with a Boolean expression,  // $code will be unset or empty  // so just label it 'Boolean expression'.  //    if (!isset($code) || $code == '') {      $code = 'Boolean expression';    }  //  // Prepare the code for HTML display...  //    $c = htmlentities($code);  //  // ...and invoke the phpAssertUnit assertion  //    $Assert->isTrue(FAILED_PHP_ASSERTION,  true,  //always report PHP assertions  "PHP Assertion failed on Line $line, File $f: $c");  }  ?> 

Включите это рано в ваш файл PHP (или еще лучше, сделайте его частью файла config.php, который включен на каждой странице), убедитесь, что файлы phpAssertUnit (assert.mod; reporter.html; reporter.js; reporter.css ) находятся в нужных местах (согласно документации phpAssertUnit и любой выполненной вами конфигурации), и у вас будет информативная, ненавязчивая отчетность по утверждениям в отдельном окне, интегрированном с утверждениями JavaScript, если вы того пожелаете.

Вы можете увидеть в браузере утверждений отчеты об утверждениях, обсуждаемых в этой статье, здесь .

Другие Простые Методы

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

  1. Уровни ошибок
    Рассмотрите возможность установки уровней ошибок E_ALL во время разработки. Конечно, вы получите некоторые уведомления, которые могут не иметь значения, например, о переменной, на которую ссылаются перед установкой, когда все, что вы хотите знать, это то, является ли она NULL . Но когда вы ссылаетесь на неустановленную переменную, которая должна иметь значение, вы будете рады, что PHP так быстро об этом вам рассказал. Стоит выработать привычку держать ваш код в состоянии, которое не вызывает никаких предупреждений или уведомлений, поэтому, когда они произойдут, вы будете знать, что у вас есть что посмотреть.

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

  2. Проверка HTML
    Большая часть кода PHP генерирует HTML для веб-сайтов. Еще один простой способ раннего обнаружения проблем — использовать средство проверки HTML, чтобы проверить, что генерирует ваш код. Существует много доступных сервисов валидации, в том числе хороший бесплатный на http://validator.w3.org/ .
  3. Модульное тестирование
    Современные методологии разработки, такие как экстремальное программирование, рекомендуют писать модульные тесты перед написанием кода. Независимо от того, хотите ли вы зайти так далеко, вы обнаружите, что тратите меньше времени на написание модульных тестов, если у вас есть помощь в автоматизации их выполнения. Подумайте об использовании PhpUnit , потрясающего модуля тестирования с открытым исходным кодом, основанного на JUnit для Java.

    Кстати, эффекты утверждений и модульных тестов дополняют друг друга. Оставьте утверждения включенными для модульных тестов, и вы автоматически увеличите количество и охват тестов в своем коде.

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