21 ошибка программиста PHP
Введение
Одна из наиболее сильных сторон PHP является, одновременно, и его слабой стороной: PHP очень прост в изучении. Это привлекает многих людей; однако, несмотря на его кажущуюся простоту, не так-то просто научиться использовать этот язык правильно и эффективно.
Как правило, дело в недостаточной практике программирования. Неопытные программисты становятся перед лицом необходимости создания сложных веб-приложений. Поэтому сплошь и рядом допускаются ошибки, которых избежал бы опытный программист, такие как необоснованное использование функции printf()или неправильное использование семантики PHP.
В этой серии из трех статей представлены наиболее, по нашему мнению, характерные ошибки. Эти ошибки можно классифицировать по нескольким категориям, от <некритических> до <смертельных>. Наряду с анализом этих ошибок, представлены способы их избежания, а также некоторые <маленькие хитрости>, накопленные за многие годы практики программирования.
Часть 1: Описываются 7 <детских> ошибок (№ 21-15, в обратном порядке, в соответствии со степенью серьезности по нашей классификации). Такие ошибки не вызывают серьезных проблем, но приводят к уменьшению эффективности работы программы, а также выражаются в громоздком трудночитаемом коде, в который, к тому же, трудно вносить изменения.
Часть 2: Следующие 7 ошибок (№ 14-8) относятся к <серьезным>. Они ведут к еще более значительному уменьшению скорости выполнения кода, уменьшению безопасности скриптов; код становится еще более запутанным.
Часть 3: Описания семи, последних, <смертельных> ошибок. Эти ошибки концептуальны по своей природе и являются причиной появления ошибок, описанных в 1-ой и 2-ой частях статьи. Они включают и такие ошибки, как недостаточное внимания, уделенное как проекту в целом, так и коду программы, в частности.
21. Неоправданное использование функции printf()
Функция printf() предназначена для вывода форматированных данных.
Например, ее следует использовать при необходимости вывода переменной в формате с плавающей запятой с определенной точностью, либо в любом другом случае, когда возникает необходимость изменения формата выводимых данных.
Ниже приведен пример обоснованного применения функции printf(). В данном случае она используется для форматированного вывода числа <пи>:
<?php
printf ("Число Пи: %2fn<br>n", M_PI);
printf ("Это тоже число Пи: %3fn<br>n", M_PI;
printf ("И это Пи: %4fn<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 ( "Меня зовут %sn<br>n
Я работаю %s, %sn<br>n
Мой адрес E-mail:%sn<br>n",
$name, $job, $company, $email );
?>
В данном случае возможно (и желательно!) применение print():
print "Меня зовут $namen<br>n
Я работаю в $company, $jobn<br>n
Мой адрес E-mail: $emailn<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().
20. Неверное применение семантики языка
Многие программисты используют в своей работе PHP , фактически не понимая тонкостей этого языка. Одна из тонкостей - разница между синтаксисом и семантикой PHP.
* Синтаксис 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 заключена в двойные кавычки, что однозначно определяет ее как строку, тогда как функция fopen() ожидает в качестве первого аргумента дескриптор, но не строку. Соответственно, вам следует использовать переменную, которая может содержать дескриптор.
Примечание: В данном случае, строковый тип допустим синтаксически.
Для решения проблемы следует просто убрать двойные кавычки:
<?php
$fp = @fopen ( 'somefile.txt', 'r' )
or die ( 'Не могу открыть файл somefile.txt' );
while ( $line = @fgets ($fp, 1024) )
{
print $line;
}
@fclose ($fp)
or die ( 'Не могу закрыть файл somefile.txt' );
?>
Как избежать неправильного приложения семантики?
В приведенном примере генерируется сообщение об ошибке. Но PHP предоставляет программисту больше свободы, чем другие, традиционные языки программирования. Это позволяет получать интересные результаты. Как минимум, теоретически возможно написать корректный код, неправильно используя семантику языка.
Но будьте осторожны, заигрывая с семантикой языка! Возможно появление трудноуловимых ошибок в программах. Если же вы все-таки решили поэкспериментировать, вам следует понимать три ключевых момента:
* Типы: В PHP каждая переменная в любой момент времени относится к определенному типу. И это несмотря на тот факт, что ее тип можно свободно изменять. Другими словами, в языке PHP переменная не может существовать, при этом не относясь к определенному типу (и, соответственно, не обладая характеристиками, присущих этому типу). В PHP есть 7 основных типов переменных: Boolean, resource, integer, double, string, array и object.
* Область видимости: В PHP переменные имеют область видимости, которая определяет то, откуда она может быть доступна, и насколько долго будет существовать. Недопонимание концепции <области видимости> может проявляться в виде различного рода <плавающих> ошибок.
* php.ini: При написании кода следует понимать, что не все пользователи имеют такую же конфигурацию программно-аппаратных средств, как и вы. Таким образом, совершенно необходимо лишний раз убедиться, сохраняется ли работоспособность вашего кода в той конфигурации, в которой программа должна работать, а не в той, в которой разрабатывалась.
19. Недостаточно либо излишне комментированный текст
Плохо документированный текст программы является признаком эгоистичного программиста. Результатом попытки анализа вашей программы с целью внесения улучшений будет только головная боль. Причем все программисты считают самодокументированный код хорошим тоном, но сами крайне редко пишут комментарии.
Следует также избегать избыточных комментариев. Это тоже встречается очень редко, и, опять же, создает трудно читаемый исходный код. Следующий пример это иллюстрирует:
<?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];
}
?>
18. Слишком много переменных - слишком большое время выполнения
Некоторые прямо-таки страдают навязчивой идеей вводить временные переменные где надо и где не надо. Совершенно невозможно понять, чем руководствовался человек, написавший такой код:
<?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);
}
?>
Золотое правило
Если вы думаете, ввести или нет еще одну временную переменную, задайте себе два вопроса:
* Будет ли эта переменная использована хотя бы дважды?
* Значительно ли улучшится с ее введением читаемость кода?
Если на любой из этих вопросов вы ответили <да>, тогда введите временную переменную. Иначе комбинируйте вызовы функций (если это необходимо) и обойдитесь без ее использования.
17. Переписываем стандартные функции
Кое-кто рекомендует переименовывать стандартные функции для того, чтобы программистам на Visual Basic'е проще было перейти к использованию языка PHP:
<?php
function len ($str) {
return strlen ($str);
}
?>
Встречаются также рекомендации, приступая к программированию на PHP, первым делом заменять имена встроенных функций более привычными.
Существует, как минимум, две причины этого не делать. Во-первых и прежде всего, мы получаем менее читаемый код. Люди, читающие ваш код, увидят массу очевидно ненужных функций и будут сильно удивлены, почему же вами не использовались стандартные функции PHP.
Ну и, наконец, это замедляет программу. Дело не только в необходимости обработки большего объема кода, но и в том, что для вызова такой пользовательской функции, требуется больше времени, чем для прямого вызова стандартной функции.
Используйте стандартные функций языка!
Иногда так трудно устоять! Ведь программист редко знает сразу весь набор функций - у него обычно нет времени запомнить их все. Почему бы просто не переименовать функцию? Но, повторимся, этого не следует делать в силу изложенных выше причин.
Хорошо бы иметь под рукой справочник по функциям PHP (удобно использовать индексированную версию в формате PDF). И перед тем как написать какую-либо функцию, внимательно посмотреть - не существует ли она уже в списке стандартных функций.
Но следует заметить, что в кодах программ можно встретить пользовательские функции, написанные еще до их введения в качестве стандартных (например, функции сравнения двух массивов). Это не означает, что вы обязательно должны переписать код и заменять их стандартными функциями.
16. Клиентская часть программы не отделяется от серверной части
Многие программисты рекомендуют объединять код HTML (интерпретируемый на стороне клиента) и код PHP (выполняемый сервером) в один большой файл.
Для маленьких сайтов это, возможно, неплохо. Но, когда ваш сайт начнет расти, вы можете столкнуться с проблемами при необходимости добавить какие-либо новые функции. Такой стиль программирования приводит к очень <непослушному> и громоздкому коду.
API функций
Если вы собрались отделить код 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 "ttt
<a href="$links[$i]">$names[$i]</a>
n<br>n";
}
}
?>
Очевидно, такой код лучше читаем. Еще одно преимущество использования этой концепции - возможность изменения дизайна без модификации самого кода программы.
Плюсы использования API функций
* Относительно чистый, ясный код
* Быстрый код
Минусы использования API функций
* Не настолько наглядно как система шаблонов
* Все-таки, для модификации дизайна требуется некоторое знание PHP
Система шаблонов
Второй способ, используемый для разделения 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>
Затем пишем программу, просматривающую код шаблона и при выводе заменяющую тэги вида %%:%% нужной информацией.
Примечание: неплохой класс для использования его в системе шаблонов - FastTemplate, его можно скачать с http://www.thewebmasters.net/.
Плюсы использования шаблонов:
* Предельно просто и ясно
* Для изменения шаблонов не требуется знание PHP
Минусы использования шаблонов:
* Более медленный способ - ведь надо сканировать весь шаблон и лишь потом выводить данные
* Сложнее внедрить на практике
15. Использование устаревшего синтаксиса и функций
Некоторые программисты вновь и вновь используют старые библиотеки и старые наработки. Например, код, написанный еще под PHP 2, до сих пор используется с PHP4, хотя уже начиная с версии PHP3 были добавлены стандартные функции, реализующие то же самое.
Использование устаревших функций и синтаксиса могут снизить скорость выполнения кода и, к тому же, сделать его нечитаемым. Другие программисты могут быть незнакомы со старыми функциями. Но тем не менее, если вы встретили участок старого кода, не обязательно его переписывать с учетом новых правил языка. Просто не надо его использовать при написании новых программ.
Пример использования старых языковых конструкций:
<?php
// Старый стиль
while (1):
print "5";
if ( $idx++ == 5 ):
break;
endif;
endwhile;
// Лучше написать так
// (впрочем, код можно оптимизировать)
while (1)
{
print "5";
if ( $idx++ == 5 ) {
break;
}
}
?>
Почему же следует следовать новым стандартам? Причины следующие:
* Использование старых конструкций не очень распространено и, таким образом, новички в PHP будут в замешательстве, увидев два разных варианта синтаксиса.
* Старый синтаксис отличается от синтаксиса других языков программирования, и, следовательно, при переходе с другого языка на PHP программисту будет сложнее понять и привыкнуть.
* Но самое главное - в одной из новой версий, возможно, будет исключена поддержка старого синтаксиса, тем самым это заставит вас переписать код заново. Как бы то ни было, скобки всегда останутся часть языка PHP.
Подобные участки кода можно встретить во многих программах. Вам, как правило, следует руководствоваться правилами, приведенными в документации по PHP, большей часть обновленной - в ней отражается развитие языка. Периодически просматривайте документацию, ведь язык развивается, добавляются новые функции. Таким образом, вам никогда не придется писать пользовательские функции, выполняющие ту же работу, что и стандартные.
Резюме
В этой статье мы рассмотрели первые 7 из 21 наиболее общих ошибок PHP программиста. Как правило, они не нарушают работоспособности программ, но, тем не менее, их следует избегать:
* Необоснованное применение функции printf(): Ее следует использовать только для вывода форматированных данных.
* Неправильное применение семантики языка: Многие программисты не имеют достаточно времени, чтобы разобраться во всех тонкостях языка, что впоследствии выражается в ошибочном коде.
* Плохо комментированный код: Всегда пишите комментарии! Перед каждой функцией указывайте, что делает данная функция, и какие аргументы она требует. Также комментируйте сложные участки кода и внесенные изменения.
* Слишком много временных переменных: Временные переменные хорошо использовать для предотвращения повторного вызова функций или последовательностей функций.
* Изобретаем велосипед - переписываем стандартную функцию: Сначала загляните в руководство по PHP - не описана ли там функция, которую вы собираетесь написать для, казалось бы, расширения набора стандартных функций PHP.
* Смешан PHP и HTML код: Попробуйте сделать код как можно более модульным. Потом вам (и другим тоже) можно будет сменить дизайн страницы без изменения кода PHP.
* Используются старые языковые конструкции и устаревшие функции: То, что вы можете сделать, не всегда следует делать. Загляните в документацию и литературу по PHP, как писать правильно. Отличные книги - "Разработка веб-приложений с использованием PHP (Web Application Development with PHP) и "Профессиональный программист PHP (Professional PHP). (Эх, где бы их еще найти! ;)) - прим. переводчика)
Об авторе
Стерлинг Хьюз (Sterling Hughes) - независимый разработчик веб-сайтов, занимается созданием динамических веб-приложений для некоторых крупнейших мировых компаний. Участвовал в создании cURL and SWF расширений PHP с открытым исходным кодом. Его книга, <Настольная книга программиста PHP> (The PHP Developer's Cookbook), была издана в октябре 2000 года издательством .
О переводчике
Дмитрий Короленко. Ну что о нем много говорить? ;)) Благодаря ему люди, не знающие английского, или знающие, но очень ;)), смогут прочесть весьма познавательную статью Стерлинга Хьюза. О замеченных недостатках перевода просьба сообщать на мыло: lkx2@mail.ru Сразу хочется пояснить, что дословный перевод не был самоцелью, но, тем не менее, стиль и смысл оригинальной статьи по возможности ;)) сохранялись. Все замечания и пожелания приветствуются и будут учтены, ибо готовится перевод второй части этой статьи. Только просьба ногами не пинать ;)) Все же я надеюсь,что если вы до этого места дочитали, то не все так плохо ;))
Рекомендуем почитать