Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Как стать программистом и избежать детских ошибок / Начинаем абстрагироваться от СУБД


Рассылка, получив статус «серебряной», пополнилась новыми подписчиками, которых я категорически приветствую! Рекомендую прочитать в первом выпуске о формате рассылки. Меня зовут Павел Корягин, писать мне можно на pavel@koryagin.com

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


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

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

План действий

Чтобы достичь необходимого минимума профессиональных знаний по БД, нам нужно будет рассмотреть такие вещи, как фреймворк, теорию баз данных (это просто, хоть и звучит пугающе), ORM и выпадающие из вводных курсов возможности SQL. Я их всех со временем коснусь в своих статьях.

Начнём с фреймворка. Те, кто не знает этого термина, сейчас быстро и самостоятельно при помощи Интернета в общих чертах разберитесь, что за зверь такой.

Этот навык — быстро находить знания по их обрывкам — ключевой. Курсанты Школы программистов начинают зарабатывать его себе с первых дней обучения.

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

Ищем неприятностей

Большинство программирует запросы к БД примерно так:

$q = mysql_query("SELECT * FROM user WHERE login = '".
  $_GET['login']."' AND password = '".$_GET['password']."'");
$user = mysql_fetch_array($q);
if (!$user)
  echo "Пароль или имя пользователя неверны.";

Кстати, снова напомню: язык примеров — наш заслуженный народный PHP.

Я постоянно встречаю такую запись с небольшими вариациями.

В таком коде кроется целый ворох проблем.

Сегодня страхуем форс-мажор

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

Вы должны обрабатывать все нештатные ситуации. И тут многие снова решают проблему «в лоб»:

$q = mysql_query("SELECT * FROM user WHERE login = '".
  $_GET['login']."' AND password = '".$_GET['password']."'")
  or die(mysql_error());

Что тут плохо? То, что объём текста увеличился. Почему это плохо? Потому что объём смысла остался прежним. Следовательно, мы заработали новых проблем:

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

Скидываем рутину

Почему компьютер сам это не сделает? Очень хороший вопрос!

Если бы разработчики PHP встроили die(mysql_error()) в mysql_query — это поставило бы крест на PHP в профессиональной среде.

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

  1. Выводят пользователю сообщение о том, что у программы трудности, с которыми справится только техподдержка. Причём сообщение появляется в обычном дизайне CMS, чтобы клиент не паниковал почём зря.
  2. Корректно прерывают программу.
  3. Записывают событие в лог (справедливости ради отметим, что этот шаг делать PHP умеет сам).
  4. Отправляют уведомления всем заинтересованным лицам: администратору сайта, программисту сайта, их начальнику, а также, на всякий случай, проект приказа на увольнение первых двух — в отдел кадров.

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

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

function db_query($sql)
{
  // Стандартный ход
  $q = mysql_query($sql);
  
  // Всё хорошо?
  if ($q)
    return $q; // Да всё отлично!
    
  // Случай ошибки:

  // Уведомляем уполномоченного о бедствии
  mail($GLOBALS['tech_support_email'],
    'Fail at '.$_SERVER['HTTP_HOST'],
    'DB ERROR: '.mysql_error().
    "\n\nQuery: ".$sql.
    "\n\nStack: ".print_r(debug_backtrace(), true).
    "\n\nDump: ".print_r($GLOBALS, true),
    'Content-Type: text/plain; charset=UTF-8');

  // Стираем данные, подготовленные для браузера
  /* Работает только, если мы пользуемся ob_start().
     Об этом будет сказано, когда речь пойдёт
     о шаблонизаторах, то есть ещё не скоро. */
  while (ob_get_level())
    ob_end_clean();

  // Используем шаблон ошибки
  require 'templates/errors/db.php';
  
  // Останавливаем программу
  die;
}

Обращаем внимание на детали

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

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

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

    Конечно, в реальности всё не так просто, поскольку диалекты SQL разнятся, но шанс, что после смены СУБД многое продолжит работать само, таки есть.

    Это называется абстракцией от реализации. Мы определяем для себя более общие функции, которые скрывают от нас то, с чем они на самом деле работают, если эта информация нам мешает.

  3. Довольно универсальный алгоритм обработки ошибок прописан в функции явно, хотя его можно было использовать и для других проблем. Предоставляю Вам самостоятельно вынести его из функции db_query() в функцию error(). Можете поделиться результатами в комментариях вблоге.
  4. Ошибки функции mail() в данном случае не обрабатываем. А то кому бы мы их предъявили?

Что сделано

Теперь исходный вызов выглядит так:

$q = db_query("SELECT * FROM user WHERE login = '".
  $_GET['login']."' AND password = '".$_GET['password']."'");
$user = db_fetch_array($q);
if (!$user)
  echo "Пароль или имя пользователя неверны.";

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

Извлечение массива я тоже здесь написал с префикса «db_», чтобы не портить картину и не лишаться абстракции. Соответствующую функцию сделать просто.

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

Я не слишком подробно пишу? Что-то можно было сократить или выбросить? Или наоборот, следовало разъяснить ещё подробнее? pavel@koryagin.com

Вести с блога

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

Мой ответ


Этот выпуск Вы можете прокомментировать в Живом Журнале.


В избранное