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

Как стать программистом и избежать детских ошибок / Краткость - сестра таланта


Продолжаем тему общения с СУБД, начатую ранее. В следующих выпусках затронем вопрос безопасности (SQL-инъекции) и продолжим обсуждать сокращение рутинного кода.

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

  1. Машинные коды — мы пишем буквально на языке компьютера.
  2. Ассемблер — до человека дошло, что команды приятнее писать словами.
  3. Язык высокого уровня — мы будем писать так, как мы привыкли в математике (Фортран) или вообще на своём языке (Кобол), а компьютер пусть сам переводит на свой. Мы признаём, что компьютер не понимает весь наш язык, поэтому вводим формальные ограничения.
  4. ООП — а почему бы не ввести в язык запись для объектов, раз мы с ними обращаемся (началось с моделирования планет)?
  5. Пролог — хотим записывать прямо те тезисы, которые у нас в голове, не переводя их в алгоритмы.
  6. DSL и дескриптивные языки — хотим записывать все мысли по теме, вообще не трансформируя их в другие формы, а только лишь упорядочивая в ходе записи.
  7. Что дальше?

Параллельно с этим шло и развитие новых форм мышления. Ближайший пример: мы привыкли писать { ... } вместо begin ... end — короче, удобнее, нагляднее, а смысл тот же.

Что у нас в работе

Итак, мы имеем такой код:

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

Рассмотрим первые три строки. Что тут противоречит эволюции? Другими словами: что мы хотели сказать и что мы сказали на самом деле?

  1. Мы хотели проверить, есть ли такой пользователь и если есть, то получить все его данные.
  2. Прямо это не сформулируешь, поэтому очевидно, что нужно попытаться извлечь его запись из базы данных.
  3. База данных нам отдаст запись по запросу «SELECT * FROM user» — это узнаваемо.
  4. Добавляем критерии в WHERE.
  5. Отправляем запрос базе. Пользуемся тем, что все ошибки будут обработаны автоматически в нашей новой функции.
  6. Сохраняем дескриптор запроса.
  7. Извлекаем запись по дескриптору.

Что-то сложно как-то для типовой операции. Сократить тут можно многое, но большая часть ходов потребует перестройки среды (этого мы ещё коснёмся в ORM). А пока, поскольку большинство из Вас всё равно будут много работать без ORM, можно склеить шаги 5–7 в один.

Задумайтесь: если нам нужна запись из таблицы, то зачем мы просим некий дескриптор, чтобы потом извлечь из него запись? Зачем нам вообще дескриптор?

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

Значит надо резать.

Ещё один штрих к нашему фреймворку

Объявляем функцию для вытягивания строки без лишних движений.

function db_query_array($sql)
{
  // Делаем запрос, полагаемся на внутреннюю обработку ошибок
  $q = db_query($sql);

  // Извлекаем строку
  // Если результат пустой, то функция вернёт false,
  // это тоже приемлемо
  return mysql_fetch_array($q);
}

Оптимизировать или не оптимизировать?

Как вариант её можно объявить так:

function db_query_array($sql)
{
  // Делаем запрос, полагаемся на внутреннюю обработку ошибок
  $q = db_query($sql);

  // Извлекаем строку
  // Если результат пустой, то функция вернёт false,
  // это тоже приемлемо
  $r = mysql_fetch_array($q);
  
  // Очищаем память от результатов запроса
  mysql_free_result($q);

  // Возвращаем результат
  return $r;
}

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

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

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

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

И ещё: «любая сложность» не даёт права писать как попало. За некрасивый код всегда наступает расплата, либо при его доработке, либо в виде неудобной привычки, либо клавиатурой по пальцам.

Первый же вариант можно реализовать и так:

function db_query_array($sql)
{
  // Делаем запрос, полагаемся на внутреннюю обработку ошибок
  // и извлекаем строку

  // Если результат пустой, то функция вернёт false,
  // это тоже приемлемо

  return mysql_fetch_array(db_query($sql));
}

Тут мы бы сэкономили машинное время на создании локальной переменной. Конкретно для языка PHP это существенно из-за особенностей обработки переменных. Читаемость текста тоже не особо пострадала, что важно.

Я предположил, что эти функции объявляются вместе с db_query, поэтому внутри них, где можно, используются прямые обращения к утилитам «mysql_*» (экономия на вызове промежуточной функции). Хотя можно было записать то же самое и через вызовы «db_*». Таким образом эти функции не пришлось бы править при смене СУБД.

Аналогично db_query_array() определяем функции db_query_assoc() и db_query_row().

Полный список must-have функций

  • db_query($sql) — обёртка для mysql_query с обработчиком ошибок. Описана в предыдущей статье.

  • db_query_array($sql), db_query_assoc($sql), db_query_row($sql) — извлечение строки по запросу. Сам я обычно не объявляю первую из них, поскольку пользуюсь исключительно двумя другими.

  • db_query_value($sql) — получить одно значение (скаляр). Возвращается нулевой элемент полученного массива. Если получен пустой набор, то вернуть false, который можно при случае распознать оператором «===» (тожество). Для других языков это может быть undef, undefined или что-то в этом роде, но не NULL, поскольку NULL иногда приходит из СУБД. Примеры:

    $total = db_query_value("SELECT COUNT(*) FROM users");
    
    $mail_from = db_query_value("SELECT `value` FROM settings
      WHERE `key` = 'admin_email'");
    
  • db_query_col($sql) — получить столбец в виде массива. В массив добавляются нулевые элементы полученных строк.

    echo 'Наши имена:<br>';
    foreach (db_query_col("SELECT DISTINCT first_name FROM user")
        as $name)
      echo htmlspecialchars($name), '<br>';
    
  • db_query_hash($sql) — получить ассоциативный массив из двух столбцов. В качестве ключей берутся нулевые элементы строки, в качестве значений — первые.

    $settings = db_query_hash("SELECT `key`, `value` FROM settings");
    
    $city_options = db_query_hash("SELECT city_id, title
      FROM geo_cities WHERE country_id = ".$country_id);
    

Кроме того, иногда полезно извлечь результаты запроса в таблицу целиком, хотя я бы не советовал злоупотреблять этим. Поэтому функцию db_query_table() [или, скажем, db_query_matrix()] не описываю, но Вы можете рассмотреть её полезность.

Результат

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

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

Осмысленней стал, не правда ли? И писать теперь меньше.

Но это ещё далеко не всё. У нас не решена проблема с безопасностью. Продолжение следует.

Ответы на вопросы

...не могли бы вы рассказать про программирование на C#?) Ну или хотя бы показать сайт на, котором можно про это почитать...

[Что есть сегодня:] C# на уровне 3 курса (пока что обучаюсь в университете), но пока не на уровне проф. разработчика. Так же постепенно начинаю осваивать WPF.

Вячеслав

Не могу чего-то однозначно посоветовать.

Во-первых, я не знаю хороших систематизированных курсов по программированию — от и до. Именно поэтому я и создал Школу программистов, чтобы заполнить пробел. Зато фрагментарных в Интернете очень много (о них чуть ниже).

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

Где стоит поискать:

  • В книжных магазинах. Обращайте внимание на книги, названия которых указывают на разработку ПО, а не на изучение языка. Мне в своё время сильно помогли книги «Разработка ПО на Турбо Паскале» и «Создание переносимых приложений под Windows». Эти книги описывали много деталей из реального опыта, выходящих далеко за пределы тем про Паскаль и Windows API соответственно.
  • На intuit.ru — курсы.
  • На habrahabr.ru — соответствующие блоги. Подпишитесь на них в RSS или ещё как-нибудь. Членство на самом Хабре для этого не обязательно.

Задать вопрос

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


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


В избранное