Следующие 7 ошибок (#14-8) относятся к "серьёзным". Они ведут к ещё более значительному уменьшению скорости выполнения кода, уменьшению безопасности скриптов; код становится еще более запутанным. Одна из наиболее сильных сторон PHP является, одновременно, и его слабой стороной: PHP очень прост в изучении. Это привлекает многих людей; однако, несмотря на его кажущуюся простоту, не так-то просто научиться использовать этот язык правильно и эффективно. Как правило, дело в недостаточной практике программирования. Неопытные программисты становятся перед лицом необходимости создания сложных веб-приложений. Поэтому сплошь и рядом допускаются ошибки, которых избежал бы опытный программист, такие как необоснованное использование функции printf()или неправильное использование семантики PHP. В этой серии из трех статей представлены наиболее, по нашему мнению, характерные ошибки. Эти ошибки можно классифицировать по нескольким категориям, от некритических до смертельных. Наряду с анализом этих ошибок, представлены способы их избежания, а также некоторые маленькие хитрости, накопленные за многие годы практики программирования. 14. Пренебрежение правилами присвоения имён Одна из наиболее серьёзных ошибок программиста - непродуманная система именования переменных проекта. Нередко приходится тратить уйму времени на разбор кода только потому, что автор вдруг решил ввести в программу переменные $fred и $barney вместо ожидаемых $email и $name. Речь ведётся о реальном проекте, где не менее реальный программист решил все переменные проекта назвать именами героев мультсериала "Flinstones" (Это не шутка). То как вы назовёте переменные и функции программы, определит во многом читаемость её кода. Наиболее распространёнными ошибками являются имена: - слишком короткие или наоборот, чрезмерно длинные;
- не связанные по смыслу с контекстом программы;
- не учитывающие регистрозависимость;
- замедляющие разбор и чтение кода (особенно это касается имён функций).
Именование переменных Регистрозависимость В PHP имена переменных регистрозависимы, то есть $user и $User - две записи в списке переменных скрипта. Однако некоторые программисты активно пользуются этим и производят на свет переменные с совершенно одинаковыми именами, но использующими буквы разных регистров. Это отвратительная привычка. Регистр букв никогда не должен быть единственным отличием двух переменных. Каждая переменная на своём поле действия должна иметь уникальное имя. Слишком короткие имена Для обозначения переменных многие программисты используют одним им понятные аббревиатуры. О чём впоследствии сильно жалеют, ибо смысл сокращения затерялся во времени своего создания. Имя переменной должно отражать характер её значения, то есть содержания и обозначаться полными словами или общепонятными сокращениями. Слишком длинные имена С другой стороны, наблюдаются случаи злоупотребления длинными именами. Наиболее общее правило: имя переменной должно состоять максимум из двух слов. Разделить эти два слова мы можем, поставив understrike (то есть "_") или написав второе слово с заглавной буквы. Пример #1. Положительный. Как правильно присваивать имена переменным: $username = 'sterling'; $password = 'secret';
$teachers = array ('Sadlon', 'Lane', 'Patterson', 'Perry', 'Sandler', 'Mendick', 'Zung');
foreach ($teachers as $teacher); ?>
Пример #2. Отрицательный. Теперь рассмотрим несколько преувеличенные примеры того, как не следует присваивать имена переменным: $username_for_database = 'sterling'; $guMbi = 'secret'; // for the $password
$thelastnamesofteachers = array ('Sadlon', 'Lane', 'Patterson', 'Perry', 'Sandler', 'Mendick', 'Zung');
foreach ($thelastnamesofteachers as $TeaChER); ?>
Имена функций Все правила, применяемые для имён переменных, годятся и для функций. Однако в случае с функциями, грамматические реалии имеют большее значение.
Помните, что в PHP все функции, встроенные или определённые разработчиком, - регистронезависимы. Использование глаголов Функции в PHP можно сравнить с какими-либо действиями, совершаемыми в реальном мире. Таким образом, имена функций должны отражать эту направленность на действие, то есть выражаться глаголами. Причём лучше в настоящем времени.
В качестве примера рассмотрим функцию, генерирующую Гауссовы случайные числа. Предполагается, что из её имени мы должны понять, какая именно формула используется в генерации числа. Вот так: generate_gaussian_rand().
Обратите внимание на использование глагола в имени функции. Именно глагол помещает функцию в правильный контекст: list ($num1, $num2) = generate_gaussian_rand(); list ($num3, $num4) = generate_gaussian_rand(); ?>
Для сравнения, другой пример: list ($num1, $num2) = gaussian_rand_generator(); list ($num1, $num2) = gaussian_rand_generator(); ?>
Видите разницу? Во втором примере для обозначения действия использовано существительное. И если назначение функции ещё прослеживается, название затрудняет чтение кода.
Мораль: используйте глаголы! 13. Непродуманная работа с данными: бд и sql Забавно иногда наблюдать, сколько разных уловок находят люди для организации доступа к базам данных и получения выборки результатов. Среди прочих особенно выделяются комбинации из веток if, циклов do..while, множественных запросов и вызовов функции sql_result() внутри цикла for. Чем, на их взгляд, они занимаются? Код, основанный на методе научного тыка, говорит о недостаточно ясно определённой организации работы с БД. Те, кто прилагают все свои усилия на написание кода, а не на написание правильного кода, рискуют больше потерять, чем заработать. Некорректная выборка данных - яркий тому пример. Некоторые программисты не уделяют достаточно времени на тщательное продумывание этого момента. Естественно, в реальной жизни может и не оказаться того "единственно верного" способа выборки данных, но всегда найдётся тысяча "неверных", это точно. Ошибки в организации выборки данным можно разделить на три класса: Неправильное использование функций обращения к БД Один из PHP-исходников предлагал следующий способ получения выборки из БД (приведённый ниже код в проекте находится после сгенерированных SQL-запросов): if (!($row = sql_fetch_row ($result))) { print "Ошибка: не найдено ни одного ряда"; exit; }
do { print "$row[0]: $row[1]\n \n"; } while ($row = sql_fetch_row ($result)); ?>
Примечание: в данном и последующих примерах $result является дескриптором выборки или указателем на неё. Другими словами, был произведён запрос и получено определённое множество рядов. Примеры демонстрируют методы эффективной обработки этого множества. В этом отрезке кода есть две ошибки: - проверка на "ноль рядов" - это попытка получить хотя бы один.
- полученные данные не хранятся в ассоциативном массиве.
Проверка на "ноль рядов" ($result): неправильный подход Задействовав функцию sql_fetch_row(), данный кусок кода предлагает косвенную проверку выборки на наличие хотя бы одного ряда данных. Но ведь существует прямой способ - это подсчёт количества рядов в выборке $resultфункцией sql_num_rows(), как показано ниже: if (sql_num_rows ($result) <= 0) { print "Ошибка: не найдено ни одного ряда"; exit; }
while ($row = sql_fetch_row ($result)){ print "$row[0]: $row[1]\n \n"; } ?>
Избавляемся от do..while Прежде всего, исчезает необходимость в использовании давно уже поднадоевшего do..while, ибо для проверки на "ноль рядов" функция sql_num_row() не выдёргивает первый рядв $row, и указатель по-прежнему установлен на начало.
В PHP Source как-то был представлен подобный фрагмент кода. Если выборка не была нулевой, то функцияsql_fetch_row() внутри условного блока доставляла первый ряд. Для получения остальных приходилось прибегать кdo..while, потому что получение ряда из выборки ("to fetch" - принести, доставить// Прим. перев.) смещает указатель в ней. Таким образом, сначала вам придётся обработать уже полученный ряд ("do"), только потом получить второй ряд и так далее.
Так чем же do..while так провинился? - в данном примере внутри цикла do..while помещён только один оператор: простой вывод. Теперь представим, что там может оказаться не один, а десять операторов. Тогда редактору кода придётся искать условие whileпосле оператора do и целого блока действий внутри цикла. Занятие не из приятных.
- условие while обычно располагается в начале блока, а не в конце его. Поэтому редактору кода нужно будет уделять этому особое внимание при чтении, чтобы не спутать цикл do..while с предварительным условием while обычного цикла.
Делаем всё просто и понятно В случае получения нулевой выборки, функция sql_num_row() в отличие от sql_fetch_row() делает именно то, что вам нужно сделать: - действие sql_fetch_row(): "При попытке получить первый ряд не найдено ни одного ряда. Это может означать, что в данной выборке их нет".
- Действие sql_num_row(): "Количество рядов в выборке равно нулю".
Но как это отражается на написании кода?
Рассмотрим следующий пример, где операторы внутри условия записаны псевдокодом: - if(!($row = sql_fetch_row($result))){Print Error}:
- Получаем первый ряд из выборки.
- Если выборка пустая, то переменной $row приписываем 0; ноль логически выражается False; отсюда !(0)=True; выводим сообщение об ошибке.
- Иначе, если выборка не пустая, получаем первый ряд, приписываем его переменной $row; $row не равно нулю, то есть True; !(True)=False; выходим на цикл do..while.
- If(sql_num_rows($result)<=0){Print Error}:
- Подсчёт рядов в выборке.
- Если их меньше или равно нулю, выводим сообщение об ошибке.
- Иначе - идём дальше.
Итак, какое из двух выражений проще и быстрее понять? Безусловно, подсчёт рядов - более прямой и короткий путь.
Каково всё же практическое преимущество второго способа? Невелика разница, что мы поместим внутри этого условия - многого тут не выиграть.
Однако на протяжении 10 000 строк вашего кода продуманные, а потому просто и ясно изложенные идеи сэкономят кучу времени редактору кода (вот и первое преимущество). Есть и другие преимущества: разработка скриптов заметно ускоряется и становится более размеренной. Если ваша СУБД не поддерживает sql_num_row() Действительно, некоторые СУБД могут не поддерживать эту функцию. Отнесёмся с сочувствием ко всем владельцам таких систем. Им придётся проверять выборки "на ноль рядов" путем запроса первого ряда. Однако и здесь, рекомендуем использовать булевские переменные: $found = false;
while ($row = sql_fetch_array($result)){ $found = true; }
if (!$found){ print "Ошибка"; } ?>
Получение рядов данных: правила эффективной работы Вторая проблема нашего кода - это использование функции sql_fetch_row() для получения рядов. Как результат своей работы эта функция возвращает лишь пронумерованный массив. Однако существует ещё и функцияsql_fetch_array(), которая возвращает два массива: пронумерованный и ассоциативный: $row = sql_fetch_array ($result); print $row[1]; // Второй столбец print $row[name]; // Столбец name - имя ?>
Примечание: Существуют разные точки зрения на целесообразность использования одинарных кавычек при вставке строковых аргументов. В приведённом примере (столбец name) и далее по статье они не используются. Какая из функций более удобна для разработчика? Ассоциативные массивы позволяют редактору кода ясно и однозначно понять, какая именно выборка из БД будет осуществляться в каждом конкретном случае. Например: if (sql_num_rows ($result) <= 0) { print "Ошибка: не найдено ни одного ряда"; exit; }
while ($row = sql_fetch_array ($result)) { print "$row[name]: $row[phone_number]\n \n"; } ?>
Применение sql_fetch_row($result) Итак, функция sql_fetch_row() имеет целую тонну недостатков. Однако, существует ситуация, где её можно поставить без всякого ущерба "прозрачности" кода: когда sql-запрос формируется пользователем.
До настоящего момента мы рассматривали примеры с заранее известными запросами и определёнными разработчиком. Но иногда возникает необходимость в запросе, сформированном самим пользователем. В таких случаях разработчику неизвестно количество столбцов в выборке.
Здесь для их эффективной обработки полезно использовать функцию sql_fetch_row() в сочетании с count(): for ($i = 0; $i < count($row); $i++){ print "Столбец". ($i + 1). $row[$i]. "\n \n"; } ?>
Ошибки SQL: запрашивается не то, что нужно Практика показывает, что обработка выборки из БД средствами PHP - тоже является ошибкой. Бывали случаи, когда для простого поиска по 2Мб БД программисты использовали PHP, а потом возмущались его медлительностью. А делать выборку "весом" в два метра занимает целую вечность.
Язык Структурированных Запросов (SQL) был специально разработан для запросов и получения данных из таблиц в БД. Идея языка заключается в отсеивании данных ненужных вам (средствами SQL) и получении только тех, которые вам действительно необходимы для дальнейшей обработки (например, средствами PHP).
Если вы заметили, что получаете в выборке данных, больше, чем вам нужно, это верный признак недоработанных SQL-запросов. Условие WHERE Классический пример эффективного применения SQL-запросов - использование условия WHERE в синтаксисе SQL.
Рассмотрим пример кода, производящего выборку и выводящего список имён и телефонов всех пользователей с idравным 5: // В предыдущих строках // устанавливается соединение, и $conn // определяется как дескриптор соединения.
$statement = "SELECT name, phone, id FROM samp_table"; $result = @sql_query ($statement, $conn);
if (!$result) { die (sprintf ("Ошибка [%d]: %s", sql_errno (), sql_error ())); }
if (@sql_num_rows ($result) <= 0) { die ("Получено ноль результатов"); }
while ($row = @sql_fetch_array ($result)){ if ($row[id] & 5) { print "Имя: $row[name]\n \n"; print "Телефон: $row[phone]\n \n"; break; } } ?>
Данный код имеет следующие недоработки: для поиска по всей БД используется PHP; при работе с БД малого размера на это можно и не обращать внимания, но с ростом БД вы обязательно заметите резкое падение скорости работы скриптов.
Выход прост: включите в SQL-запрос условие WHERE: $statement = "SELECT name, phone FROM samp_table"; $statement .= " WHERE id='5'";
WHERE позволит применить более строгие критерии выборки. Фильтром в данном случае будет являться значение аргумента. В нашем примере это "id=5".
Получив нужную вам выборку, вы используете PHP для простого вывода результатов: if (@sql_num_rows ($result) != 1) { die ("Получено неверное количество рядов"); }
$row = @sql_fetch_array ($result); print "Имя: $row[name]\n \n"; print "Телефон: $row[phone]\n \n"; ?>
Обработка результатов выборки средствами PHP Нередко программист намеренно не сортирует выборку при запросе, перекладывая эту работу на PHP. Такой подход неэффективен, ибо сортировка средствами SQL проходит намного быстрее, чем в PHP.
Для сортировки результатов рекомендуем применять синтаксис SQL (ORDER BY), а не PHP-функцию ksort().
Рассмотрим пример использования ksort() для сортировки выборки по имени (name): $statement = "SELECT name, email, phone FROM some_table "; $statement .= "WHERE name IS LIKE '%baggins'";
$result = @sql_db_query ($statement, "samp_db", $conn);
if (!$result) { die (sprintf ("Ошибка [%d]: %s", sql_errno (),sql_error ())); }
while ($row = @sql_fetch_array ($result)){ $matches[ $row[name] ] = array ($row[email], $row[phone]); }
ksort ($matches); ?>
Возникает вопрос: а почему бы ни провести сортировку результатов во время выборки? Это избавит нас от необходимости проходить по всему массиву с результатами дважды.
Итак, убираем ksort() и исправляем SQL-запрос, добавив ORDER BY: $statement = "SELECT name, email, phone FROM some_table "; $statement .= "WHERE name IS LIKE '%baggins' ORDER BY name"; ?>
12. Слабая устойчивость к ошибкам В природе существует огромное количество скриптов абсолютно не справляющихся с пользовательскими ошибками. Своим появлением такие скрипты обязаны программистам, которые не удосуживаются правильно распланировать будущий проект и определить все места возможных ошибок. Причём этим следует заняться до того, как скрипт был написан. Недоработки подобного рода приводят к сбоям программы, что чревато не только получением некорректных результатов, но и падением системы! Предусмотреть худшее Любой скрипт может "свалиться" при наступлении каких-либо "критичных" условий. Чтобы свести такой риск к минимуму всегда нужно: Проверка результатов вызова функций При вызове функции, результаты которой подвергаются дальнейшей обработке, обязательно убедитесь, что возвращаемые данные находятся в интервале допустимых значений.
В приведённом ниже примере на шестом витке цикла возникает ошибка "деление на ноль", поскольку $iнаращивается на 1, а $j уменьшается на 1. На шестом проходе $i=$j=1. mt_srand((double)microtime() * 10000000);
function do_math ($a, $b) { return (($a - $b) * 2) / mt_rand(); }
for ($i = 5, $j = -5; $i > -5; $i--, $j++){ print $j / do_math ($i, $j) . "\n"; } ?>
Проверка результатов системных вызовов При обращении к внешним файлам или процессам всегда проверяйте, всё ли работает корректно.
Блестящий тому пример - проверка ответа системы при вызове функции sql_connect(). Стоит проверить этот ответ и убедиться, что подключение к БД действительно имело место. Если этого не сделать, то все запросы к БД могут не состояться, а некоторые данные могут быть утеряны; вы же будете пребывать в счастливом неведении. $conn = @sql_connect ($host, $user, $pass);
if (!$conn) { die (sprintf ("Ошибка [%d]: %s", sql_errno (), sql_error ())); } ?>
Установка уровня error_reporting в файле php.ini на E_ALL Убедитесь, что PHP правильно сконфигурирован, то есть уровень error_reporting (отображение сообщений об ошибках) выставлено на наивысший уровень. При другой конфигурации, по крайней мере, на время отладки скриптов, многие ошибки типа "неверное регулярное выражение", "недопустимое значение" ускользнут от вашего внимания.
Обратимся ещё раз к примеру, приведённому в части "Проверка результатов вызова функций". Предположим, что error_reporting выставлен не на максимум, а, скажем, на E_ERROR.
Обратите внимание на то, как скрипт выполняет функцию do_math, но не сообщает об ошибке "деление на ноль", которая, однако, имела место (при $i=$j=0 вывода результата просто не было). error_reporting (E_ERROR);
mt_srand ((double)microtime() * 1000000);
function do_math ($a, $b) { return (($a - $b) * 2) / mt_rand(); }
for ($i = 5, $j = -5; $i > -5; $i--, $j++){ print $j / do_math ($i, $j) . "\n"; } ?>
Результат работы скрипта:
-5148.25
-5271
-323.75
-4931
-7713.5
-4702.5
-488.5
-928.5
-1394.75
Свои обработчики ошибок Как правило, PHP выдаёт сообщения об ошибках непосредственно в браузер и не позволяет разработчику подавить или перехватить их. Однако в PHP4 у вас появилась возможность перехвата таких сообщений с помощью функцииset_error_handler().
Функция set_error_handler() применяется для записи ошибок вашего скрипта. Теперь вы можете перехватывать все ошибки и программировать собственные обработчики - warning'и пользователей больше не побеспокоят.
В следующем примере set_error_handler() назначает обработчиком по умолчанию функцию error_handler(). В случае возникновения ошибки вызывается error_handler(), и встроенная функция error_log() регистрирует сбой в файле логаerror_file.
Если происходит ошибка класса E_ERROR, работа скрипта прекращается и выводится сообщение об ошибке. // void error_handler(string type, string message, string file, int line) // Индивидуальный обработчик ошибок, определён функцией // set_error_handler()
function error_handler ($type, $message, $file = __FILE__, $line = __LINE__) { error_log("$message, $file, $line", 3, 'error_file'); if ($type & E_ERROR) { print 'Произошла ошибка, зарегистирована.'; exit; } }
set_error_handler('error_handler'); ?>
|