Стерлинг Хьюз.
29-09-2003
Описываются 7 "детских" ошибок (21-15 в обратном порядке, в соответствии со степенью серьезности по нашей классификации). Такие ошибки не вызывают серьезных проблем, но приводят к уменьшению эффективности работы программы, а также выражаются в громоздком трудночитаемом коде, в который, к тому же, трудно вносить изменения.
Одна из наиболее сильных сторон PHP является, одновременно, и его слабой стороной: PHP очень прост в изучении. Это привлекает многих людей; однако, несмотря на его кажущуюся простоту, не так-то просто научиться использовать этот язык правильно и эффективно.
Как правило, дело в недостаточной практике программирования. Неопытные программисты становятся перед лицом необходимости создания сложных веб-приложений. Поэтому сплошь и рядом допускаются ошибки, которых избежал бы опытный программист, такие как необоснованное использование функции printf()или неправильное использование семантики PHP.
В этой серии из трех статей представлены наиболее, по нашему мнению, характерные ошибки. Эти ошибки можно классифицировать по нескольким категориям, от некритических до смертельных. Наряду с анализом этих ошибок, представлены способы их избежания, а также некоторые маленькие хитрости, накопленные за многие годы практики программирования.
Эта серия статей предназначена для тех программистов на языке PHP, которые хотят избежать наиболее общих ошибок в написании кода. Читатель, как минимум, должен знать общий синтаксис PHP, а также весьма желателен некоторый опыт использования языка на практике.
Одна из наиболее сильных сторон PHP является, одновременно, и его слабой стороной: PHP очень прост в изучении. Это привлекает многих людей; однако, несмотря на его кажущуюся простоту, не так-то просто научиться использовать этот язык правильно и эффективно.
Как правило, дело в недостаточной практике программирования. Неопытные программисты становятся перед лицом необходимости создания сложных веб-приложений. Поэтому сплошь и рядом допускаются ошибки, которых избежал бы опытный программист, такие как необоснованное использование функции printf() или неправильное использование семантики PHP.
В этой серии из трех статей представлены наиболее, по нашему мнению, характерные ошибки. Эти ошибки можно классифицировать по нескольким категориям, от "некритических" до "смертельных". Наряду с анализом этих ошибок представлены способы их избежания, а также некоторые "маленькие хитрости", накопленные за многие годы практики программирования.
Часть 1: Описываются 7 "детских" ошибок (21-15 в обратном порядке, в соответствии со степенью серьезности по нашей классификации). Такие ошибки не вызывают серьезных проблем, но приводят к уменьшению эффективности работы программы, а также выражаются в громоздком трудночитаемом коде, в который, к тому же, трудно вносить изменения.
Часть 2: Следующие 7 ошибок (14-8) относятся к "серьезным". Они ведут к еще более значительному уменьшению скорости выполнения кода, уменьшению безопасности скриптов; код становится еще более запутанным.
Часть 3: Описания семи последних, "смертельных" ошибок. Эти ошибки концептуальны по своей природе и являются причиной появления ошибок, описанных в 1-ой и 2-ой частях статьи. Они включают и такие ошибки, как недостаточное внимание, уделенное как проекту в целом, так и коду программы, в частности.
Функция printf()
предназначена для вывода форматированных
данных.
Например, ее следует использовать при необходимости вывода переменной в формате с плавающей запятой с определенной точностью, либо в любом другом случае, когда возникает необходимость изменения формата выводимых данных.
Ниже приведен пример обоснованного применения функции printf()
.
В данном случае она используется для форматированного вывода числа "пи":
<?php printf ("Число Пи: %2f\n<br>\n", M_PI); printf ("Это тоже число Пи: %3f\n<br>\n", M_PI); printf ("И это Пи: %4f\n<br>\n", M_PI); ?>
Примечание: Наблюдаются случаи патологической боязни функции
printf()
, когда люди пишут свои функции форматированного вывода,
порой по 30-40 строк, хотя все проблемы мог бы решить один-единственный вызов
функции printf()
.
Многие программисты используют функцию printf()
для вывода
переменных, результатов вызова функций, а иногда даже обычных текстовых данных.
Наиболее часто это происходит в следующих двух случаях:
print()
;
print()
Вызов функции printf()
зачастую используется там, где следовало
бы использовать print()
. В следующем примере функция
printf()
используется для вывода четырех переменных:
<?php $name = 'Sterling Hughes'; $job = 'Senior Engineer'; $company = 'DesignMultimedia'; $email = 'shughes@designmultimedia.com'; printf ( "Меня зовут %s\n<br>\n Я работаю %s, %s\n<br>\n Мой адрес E-mail:%s\n<br>\n", $name, $job, $company, $email ); ?>
В данном случае возможно (и желательно!) применение print()
:
print "Меня зовут $name\n<br>\n Я работаю в $company, $job\n<br>\n Мой адрес E-mail: $email\n<br>\n";
Использование print()
вместо printf()
в случаях,
когда выводятся неформатированные данные, как в данном примере, дает
следующие выгоды:
printf()
форматирует
свои аргументы перед выводом. Таким образом, время ее выполнения
больше, чем для функций print()
или echo()
.
printf()
затрудняет чтение кода (имеющих достаточный опыт
программирования на C это, конечно, касается в меньшей степени). Чтобы
функция printf()
не повела себя самым неожиданным для вас
образом, требуется как знание синтаксиса данной функции, (т.е. %s
определяет строковый формат вывода, тогда как %d
- десятичный),
так и знание типов переменных. printf()
для вывода значения,
возвращаемого функциейЕще одна характерная ошибка использования функции printf()
-
вывод значения, возвращаемого функцией, как в следующем примере:
<?php printf ("Найдено %d вхождений строки %s", count ($result), $search_term);
?>
Наряду с функцией print()
при использовании ее в тех же целях,
следует использовать оператор ".
" В данном случае этот оператор
добавляет текст к результату вызова функции:
<?php print "Найдено " . count ($result) . " вхождений строки $search_term"; ?>
Использование оператора .
в паре с функцией print()
позволяет избежать использования более медленной функции
printf()
.
Многие программисты используют в своей работе PHP, фактически не понимая тонкостей этого языка. Одна из тонкостей - разница между синтаксисом и семантикой PHP.
Заметьте: "следует". В языках с четким разделением типов (таких как Java или C) нет понятия "следует" (в общем случае, хотя бывают и исключения). В таком случае компилятор вынудит использовать переменные строго определенного типа.
Языки, в которых отсутствует само определение типов переменных, предоставляют больше гибкости в написании кода. Но, как бы то ни было, в случае неправильного использования семантики для большинства функций PHP следует ожидать появления сообщения об ошибке.
Возьмем кусок кода, который открывает файл и выводит его построчно:
<?php $fp = @fopen ( 'somefile.txt', 'r' ) or die ( 'Не могу открыть файл somefile.txt' ); while ($line = @fgets ( "$fp", 1024)) // Здесь ошибка! { print $line; } @fclose ("$fp") // И здесь тоже color or die( 'Не могу закрыть файл somefile.txt' ); ?>
В данном случае появится сообщение об ошибке типа:"Warning:
Supplied argument is not a valid File-Handle resource in tst.php on line
4"
("Внимание: аргумент не может являться дескриптором файла")
Это вызвано тем, что переменная $fp
заключена в двойные кавычки,
что однозначно определяет ее как строку, тогда как функция fgets()
ожидает в качестве первого аргумента дескриптор, но не строку.
Соответственно, вам следует использовать переменную, которая может
содержать дескриптор.
Примечание: В данном случае строковый тип допустим синтаксически.
Для решения проблемы следует просто убрать двойные кавычки:
<?php $fp = @fopen ( 'somefile.txt', 'r' ) or die ( 'Не могу открыть файл somefile.txt' );
while ( $line = @fgets ($fp, 1024) )
{ print $line; } @fclose ($fp) or die ( 'Не могу закрыть файл somefile.txt' ); ?>
В приведенном примере генерируется сообщение об ошибке. Но PHP предоставляет программисту больше свободы, чем другие, традиционные языки программирования. Это позволяет получать интересные результаты. Как минимум, теоретически возможно написать корректный код, неправильно используя семантику языка.
Но будьте осторожны, заигрывая с семантикой языка! Возможно появление трудноуловимых ошибок в программах. Если же вы все-таки решили поэкспериментировать, вам следует понимать три ключевых момента:
Boolean
, resource
, integer
,
double
, string
, array
и
object
.
Плохо документированный текст программы является признаком эгоистичного программиста. Результатом попытки анализа вашей программы с целью внесения улучшений будет только головная боль. Причем все программисты считают самодокументированный код хорошим тоном, но сами крайне редко пишут комментарии.
Следует также избегать избыточных комментариев. Это тоже встречается очень редко, и, опять же, создает трудно читаемый исходный код. Следующий пример это иллюстрирует:
<?php // Начало кода $age = 18; // Возраст равен 18 $age++; // Увеличим $age на один год // Напечатаем приветствие print "Вам сейчас 19 лет, и это значит, что Вам уже было:"; print "\n<br>\n<br>\n"; // Цикл "для" чтобы вывести все // предыдущие значения возраста for ($idx = 0; $idx < $age; $idx++) { // Напечатаем каждое значение возраста print "$idx лет\n<br>\n"; } // Конец кода ?>
Итак, какой же объем комментариев следует помещать в скрипт?! Это зависит от многого: от времени, которым вы располагаете, от политики компании, сложности проекта и т.д. Тем не менее, запомните несколько основных принципов, которым надо следовать при написании программ вне зависимости от вашего решения:
#
- используйте только /*
*/
либо //
. Следующий пример иллюстрирует хороший стиль комментариев:
<?php // Random_Numbers.lib // Генерация случайных чисел различного типа mt_srand((double)microtime()*1000000); // // mixed random_element(array $elements[, array weights]) // Возвращает случайный элемент массива-аргумента // Массив weights содержит относительные вероятности // выборки элементов // function random_element ($elements, $weights = array()) { // Для корректного функционирования этого алгоритма // количество элементов массива должно быть равным // количеству элементов массива относительных вероятностей if (count ($weights) == count ($elements)) { foreach ($elements as $element) { foreach ($weights as $idx) { // Примечание: мы не используем $idx, потому что // нам не нужен доступ к отдельным элементам // массива weights $randomAr[] = $element; } } } else { $randomAr = $elements; } $random_element = mt_rand (0, count ($randomAr) - 1); return $randomAr [$random_element]; } ?>
Некоторые прямо-таки страдают навязчивой идеей вводить временные переменные где надо и где не надо. Совершенно невозможно понять, чем руководствовался человек, написавший такой код:
<?php $tmp = date ("F d, h:i a"); // т.е. формат даты February 23, 2:30 pm print $tmp; ?>
Для чего здесь использована временная переменная?! Она просто не нужна:
<?php
print date ("F d, h:i a");
?>
К сожалению, многие программисты никак не могут избавиться от этой дурной привычки. Использование временных переменных замедляет выполнение программы. Для увеличения скорости кода, где это возможно, лучше воспользоваться вложением функций. Использование временных переменных зачастую увеличивают время выполнения скриптов почти на четверть.
Еще одна причина, по которой следует избегать использования излишнего количества временных переменных, это ухудшение читаемости кода. Сравните два приведенных примера. Какой из них выглядит более элегантно - с использованием временной переменной или без? Какой код проще прочесть? Использование лишних временных переменных ведет к написанию менее читаемого и ясного кода.
Введение временных переменных позволяет упростить некоторые сложные выражения или вызовы функций. Еще они приносят пользу, когда позволяют избежать многократного вызова функции с одними и теми же аргументами.
Вот пример, в котором не используется лишних переменных:
<?php // string reverse_characters (string str) // Переворачивает строку символов function reverse_characters ($str) { return implode ("", array_reverse (preg_split ("//", $str))); } ?>
Вызову функции implode()
в качестве одного из параметров
передается результат выполнения вложенных функций, поэтому такой код трудно
прочесть. В данном случае нам может здорово помочь использование временной
переменной:
<?php // string reverse_characters (string str) // Переворачивает строку символов function reverse_characters ($str) { $characters = preg_split ("//", $str);
$characters = array_reverse ($characters);
return implode ("", $characters);
}
?>
Если вы думаете, ввести или нет еще одну временную переменную, задайте себе два вопроса:
Если на любой из этих вопросов вы ответили "да", тогда введите временную переменную. Иначе комбинируйте вызовы функций (если это необходимо) и обойдитесь без ее использования.
Кое-кто рекомендует переименовывать стандартные функции для того, чтобы программистам на Visual Basic"е проще было перейти к использованию языка PHP:
<?php function len ($str) { return strlen ($str); } ?>
Встречаются также рекомендации приступая к программированию на PHP, первым делом заменять имена встроенных функций более привычными.
Существует, как минимум, две причины этого не делать. Во-первых, и прежде всего, мы получаем менее читаемый код. Люди, читающие ваш код, увидят массу очевидно ненужных функций и будут сильно удивлены, почему же вами не использовались стандартные функции PHP.
Ну и, наконец, это замедляет программу. Дело не только в необходимости обработки большего объема кода, но и в том, что для вызова такой пользовательской функции, требуется больше времени, чем для прямого вызова стандартной функции.
Иногда так трудно устоять! Ведь программист редко знает сразу весь набор функций - у него обычно нет времени запомнить их все. Почему бы просто не переименовать функцию? Но, повторимся, этого не следует делать в силу изложенных выше причин.
Хорошо бы иметь под рукой справочник по функциям PHP (удобно использовать индексированную версию в формате PDF). И перед тем как написать какую-либо функцию, внимательно посмотреть - не существует ли она уже в списке стандартных функций.
Но следует заметить, что в кодах программ можно встретить пользовательские функции, написанные еще до их введения в качестве стандартных (например, функции сравнения двух массивов). Это не означает, что вы обязательно должны переписать код и заменять их стандартными функциями.
Многие программисты рекомендуют объединять код HTML (интерпретируемый на стороне клиента) и код PHP (выполняемый сервером) в один большой файл.
Для маленьких сайтов это, возможно, неплохо. Но, когда ваш сайт начнет расти, вы можете столкнуться с проблемами при необходимости добавить какие-либо новые функции. Такой стиль программирования приводит к очень "непослушному" и громоздкому коду.
Если вы собрались отделить код PHP от HTML кода, у вас есть два варианта. Один способ - - создать функции динамического формирования вывода и поместить их в нужное место на веб-странице.
Например, так:
index.php - код страницы <?php include_once ("site.lib"); ?> <html> <head> <title><?php print_header (); ?></title> </head> <body> <h1><?php print_header (); ?></h1> <table border="0" cellpadding="0" cellspacing="0"> <tr> <td width="25%"> <?php print_links (); ?> </td> <td> <?php print_body (); ?> </td> </tr> </table> </body> </html> site.lib - Сам код программы <?php $dbh = mysql_connect ("localhost", "sh", "pass") or die (sprintf ("Не могу открыть соединение с MySQL [%s]: %s", mysql_errno (), mysql_error ())); @mysql_select_db ("MainSite") or die (sprintf ("Не могу выбрать базу данных [%s]: %s", mysql_errno (), mysql_error ())); $sth = @mysql_query ("SELECT * FROM site", $dbh) or die (sprintf ("Не могу выполнить запрос [%s]: %s", mysql_errno (), mysql_error ())); $site_info = mysql_fetch_object ($sth); function print_header () { global $site_info; print $site_info->header; } function print_body () { global $site_info; print nl2br ($site_info->body); } function print_links () { global $site_info; $links = explode ("\n", $site_info->links); $names = explode ("\n", $site_info->link_names); for ($i = 0; $i < count ($links); $i++) { print "\t\t\t <a href=\"$links[$i]\">$names[$i]</a> \n<br>\n"; } } ?>
Очевидно, такой код лучше читаем. Еще одно преимущество использования этой концепции - возможность изменения дизайна без модификации самого кода программы.
Второй способ, используемый для разделения PHP и HTML кода, - использование шаблонов. В данном случае некоторые элементы дизайна заменяются пользовательскими тегами, а сама программа сканирует файл на предмет их наличия и заменяет их необходимой информацией.
Пример использования шаблонов:
<html> <head> <title>%%PAGE_TITLE%%</title> </head> <body %%BODY_PROPERTIES%%> <h1>%%PAGE_TITLE%%</h1> <table border="0" cellpadding="0" cellspacing="0"> <tr> <td width="25%">%%PAGE_LINKS%%</td> <td>%%PAGE_CONTENT%%</td> </tr> </table> </body> </html>
Затем пишем программу, просматривающую код шаблона и при выводе заменяющую
тэги вида %%SOME%%
нужной информацией.
Примечание: неплохой класс для использования его в системе шаблонов - FastTemplate, его можно скачать с http://www.thewebmasters.net/.
Некоторые программисты вновь и вновь используют старые библиотеки и старые наработки. Например, код, написанный еще под PHP 2, до сих пор используется с PHP4, хотя уже начиная с версии PHP3 были добавлены стандартные функции, реализующие то же самое.
Использование устаревших функций и синтаксиса могут снизить скорость выполнения кода и, к тому же, сделать его нечитаемым. Другие программисты могут быть незнакомы со старыми функциями. Но тем не менее, если вы встретили участок старого кода, не обязательно его переписывать с учетом новых правил языка. Просто не надо его использовать при написании новых программ.
Пример использования старых языковых конструкций:
<?php // Старый стиль while (1): print "5"; if ( $idx++ == 5 ): break; endif; endwhile; // Лучше написать так
// (впрочем, код можно оптимизировать) while (1) { print "5"; if ( $idx++ == 5 ) { break; } } ?>
Почему же следует следовать новым стандартам? Причины следующие:
Подобные участки кода можно встретить во многих программах. Вам, как правило, следует руководствоваться правилами, приведенными в документации по PHP, большей частью обновленной - в ней отражается развитие языка. Периодически просматривайте документацию, ведь язык развивается, добавляются новые функции. Таким образом, вам никогда не придется писать пользовательские функции, выполняющие ту же работу, что и стандартные.
В этой статье мы рассмотрели первые 7 из 21 наиболее общих ошибок PHP программиста. Как правило, они не нарушают работоспособности программ, но, тем не менее, их следует избегать:
printf()
: Ее следует
использовать только для вывода форматированных данных.
Стерлинг Хьюз (Sterling Hughes) - независимый разработчик веб-сайтов, занимается созданием динамических веб-приложений для некоторых крупнейших мировых компаний. Участвовал в создании cURL and SWF расширений PHP с открытым исходным кодом. Его книга, "Настольная книга программиста PHP" (The PHP Developer's Cookbook), была издана в октябре 2000 года издательством "Sams publishing".
Дмитрий Короленко. Ну что о нем много говорить? ;)) Благодаря ему люди, не знающие английского, или знающие, но не очень ;)), смогут прочесть весьма познавательную статью Стерлинга Хьюза. О замеченных недостатках перевода просьба сообщать на мыло: lkx2@mail.ru Сразу хочется пояснить, что дословный перевод не был самоцелью, но, тем не менее, стиль и смысл оригинальной статьи по возможности ;)) сохранялись. Все замечания и пожелания приветствуются и будут учтены, ибо готовится перевод второй части этой статьи. Только просьба ногами не пинать ;)) Все же я надеюсь, что если вы до этого места дочитали, то не все так плохо ;))