Как стать программистом и избежать детских ошибок / Контекст
Приветствую новых подписчиков! Рекомендую прочитать в первом выпуске о формате рассылки. Меня зовут Павел Корягин, координаты для связи — в конце выпуска.
Продолжаю цикл статей про апгрейд методов работы с БД, начатый здесь и здесь. Сегодня поговорим о том что пользователям PHP достаётся даром. Им будет полезно заметить эту особенность, чтобы потом использовать тот же трюк в других задачах. А остальным будет полезно её сымитировать в собственном фреймворке.
В следующем выпуске коснёмся анонсированного ранее вопроса безопасности. Кстати, сегодняшние примеры кода нам пригодятся, чтобы заметить, что в Perl с этим дела обстоят изначально лучше.
Классический подход
В большинстве языков работа с базой данных происходит примерно таким образом (курсивом блок многократных действий):
Устанавливаем соединение, сохраняем дескриптор
Готовим запрос, используя дескриптор соединения, получая дескриптор запроса
Выполняем запрос, используя его дескриптор
Извлекаем данные, используя дескриптор запроса
Закрываем соединение с БД, используя дескриптор соединения
Борьба с излишествами
В первой статье я упомянул, что соединение с базой данных как правило одно на всё приложение. А во второй я сказал, что лучше организовать работу так, чтобы не приходилось прописывать объекты, не несущие смысла в пределах решаемой задачи.
Так, несколько лет назад, работая на Perl и ещё не зная PHP, я смекнул, что дескриптор соединения нужен только один. Поэтому его можно сделать глобальной переменной, которая не участвует в тексте программы, а используется только в недрах библиотеки.
В итоге сообразил нечто в этом роде (поскольку Perl гораздо менее известен, чем PHP, тут пишу с подробными комментариями и без экзотических конструкций):
# Глобальная переменная $dbhmy$dbh;# Функция соединенияsub db_connect{# Аналог PHP'шного mssql_connect()$dbh=DBI->connect("dbi:ODBC:driver={SQL Server};Server=$DBHost;database=$DBDatabase",$DBLogin,$DBPass);# Тут проверки, но они вырезаны}# Функция описана для PHP в первом выпуске этого цикла статей# Заметим, что параметры запроса передаются в массиве @Params,# а не вклеиваются в строчной параметр $SQL, как обычно в PHPsub db_query($;@) {my($SQL,@Params)=@_;my$out=$dbh->prepare($SQL);$out->execute(@Params);# Текст обработки ошибок тут тоже не показанreturn$out;}# Блок, который вызывается при завершении программыEND{if(defined($dbh)){$dbh->disconnect;}}
Эра фреймворков
В итоге, при подключении этой библиотеки, работа с БД в моих программах выглядела так:
Устанавливаем соединение
Выполняем запрос, получая его дескриптор
Извлекаем данные, используя дескриптор запроса
Разумеется, я восхитился, когда обнаружил, что в PHP требуется запись именно в таком виде уже на уровне стандартной библиотеки. Разработчики учли опыт работы с другими языками.
Что тут можно сократить?
Дескриптор убрать уже нельзя, потому что часто хочется тянуть данные из двух запросов одновременно. Хотя я несколько раз встречал фреймворки, которые пытались от него избавиться.
Ядро AbleDating позволяет использовать условный номер запроса, который нужно писать только в том случае, если запросов несколько. Проблема в том, что его нужно помнить, чтобы не пересечься во вложенных функциях.
Несколько других ядер CMS вообще не подразумевают одновременного открытия нескольких запросов.
На мой вкус ни то, ни другое не удобно.
А вот установку соединения можно убрать также, как мы убрали его закрытие перенести на уровень фреймворка, где оно будет происходить автоматически.
Очевидно, его можно автоматически вызывать при старте программы. Но это не всегда хорошо, поскольку некоторые вызовы, вроде преобразования файлов «на лету», не требуют подключения к базе данных. Чтобы уж наверняка избавиться от холостых срабатываний, соединение с БД можно сделать «ленивым».
О «ленивых» функциях будет отдельный выпуск.
Контекст
Куда же делся дескриптор соединения? Он был помещён в глобальный контекст.
Слово контекст часто используется в языках программирования, обозначая разные, зачастую пересекающиеся понятия. Нужно их различать, чтобы не запутаться. В этой статье идёт речь про контекст использования. Он похож на контекст имён переменных, но не эквивалентен ему.
В программе можно выделить иерархию контекстов.
Глобальный это обычно тот, который соответствует запущенной программе. В нём размещаются данные, необходимые в произвольные моменты её работы. Всё, такие данные следует найти и вынести в глобальный контекст.
Контекст объекта. Название говорит само за себя. Один из столпов ООП инкапсуляция. Суть её заключается в том, что всё необходимое объекту доступно внутри его методов без дополнительных ухищрений. То есть не следует таскать набор переменных через последовательность функций: выделите соответствующий объект (класс) и будет Вам чистый код.
Контекст функции это, пожалуй, наиболее частный контекст. Если у Вас есть переменные или создаются объекты, которые не нужны за пределами функции, присваивайте их локальным переменным. Пусть исчезнут при выходе из функции.
Есть ещё контекст над глобальным это те данные, которые сохраняются между вызовами программы. Объекты и функции могут быть вложенными, порождая вложенные контексты. Неймспейсы также привносят возможностей в распределение данных.
Не будем углубляться в дебри. Сейчас важно:
увидеть разные контексты в пределах программы,
научиться их различать,
начать задумываться относительно каждой переменной в каком контексте она будет использоваться осмысленно.
Причём долго думать не придётся. Вы довольно быстро наработаете опыт, вследствие чего станете интуитивно видеть, где разместить ту или иную переменную.
И помните: как показывает приведённый здесь пример, те решения, которые Вам предлагают стандартные библиотеки, не всегда оказываются наилучшими для Вас.
Вы можете задать мне любой вопрос. Все полученные мною вопросы могут быть опубликованы в рассылке. Если Вы желаете скрыть свои личные данные из вопроса укажите это в тексте письма, поскольку в дальнейшем, письма будут публиковаться с полными подписями (без емейлов, разумеется).