Vadim Goncharov (nuclight) wrote,
Vadim Goncharov
nuclight

Category:

К вопросу об ублюдочности PHP

Октябрьское обсуждение у potan, перевалившее за три сотни комментов, состоит в основном из флейма. Да, конечно, там много интересных реплик, ссылок, и рассуждений о том, "как оно должно быть", но более-менее систематизированного перечисления недостатков PHP нет (разные "невинные" мелочи вроде отсутствия вменяемого DBMS API или приколов типа setlocale(LC_ALL,"ru_RU.KOI8-R"); echo (float)(string)(float)"1.5"; не в счёт).
Можно долго спорить о том, каким должен быть язык web-разработки (mauhuur выразил претензию, что PHP слишком низкоуровневой язык общего назначения, и я вполне согласен, что DSL - лучше). Вместо это я рассмотрю PHP, главным образом, в сравнении с Perl'ом, поскольку оба претендуют на примерно одну и ту же нишу (на PHP можно и обычные скрипты писать, не для Web), оставив в стороне другие языки (и так слишком много получилось). Дальше будет преимущественно изложен переведенный и обработанный материал документа PHP in contrast to Perl и ссылок из него.

1. Бессистемное именование функций, нелогичные аргументы и возвращаемые значения [1, 3, 4].
 Perl - это стиль программирования. PHP - это его полное отсутствие.
(с) ilya666

Функции могут именоваться разными способами - с подчеркиваниями (getnumberoffiles), слитно без них (get_number_of_files), с чередованием заглавных и строчных букв (getNumberOfFiles - как у функций WinAPI). В PHP применяются все три способа (третий - потому что регистр не различается, а именуют и так и эдак). Это ничего бы само по себе не значило, но в именовании функций нет никакой системы. Имеем функции: snmp_get_quick_print, snmpget, base64_encode, urlencode, htmlentities, html_entity_decode. Нельзя заранее предугадать, как функция будет называться. Кроме того, нет системы и в словах в названиях функций - меняются местами глагол и объект: base64_decode, iptcparse, create_function, recode_string.
На этом сюрпризы в именовании не заканчиваются - в именах функций конверсии есть и "2", и "to": bin2hex, cal_to_jd (jdto*, *tojd), strtolower.
Но и это еще не всё (с). Названия выбираются, видимо, тоже от балды: есть функции unlink, link и rename, которым соответствуют одноименные syscall'ы Unix, но системного вызова touch нет - есть utime. Есть функция mysql_connect, а "парная" ей называется mysql_close - а почему не disconnect ?.. уперли название из соответствующей библиотеки для Си (где единый close для всех дескрипторов, так что название в принципе логично), не потрудившись хоть немного соблюсти стиль?..
Для сравнения, в перле в именах базовых функций нет подчеркиваний вообще, все имена (кроме dbm*) имеют форму "глагол объект" (shm* и msg* соответствуют библиотечным вызовам, а sys - префикс, а не объект).
И это не предел (с). Для некоторых операций существуют много "вроде бы подходящих" функций, и не всегда легко решить, какую же именно из них применить. Вот например, пачку функций поиска совпадений можно свести вот в такую таблицу (что характерно, в документации на PHP такой таблицы нет... может кому-то из PHPистов хоть здесь пригодится):
                         replaces case                 gives   s/m/x offset
                 matches with     insens number arrays matches flags (-1=end)
ereg             ereg             no     all    no     array   no    0
ereg_replace     ereg    str      no     all    no     no      no    0
eregi            ereg             yes    all    no     array   no    0
eregi_replace    ereg    str      yes    all    no     no      no    0
mb_ereg          ereg             no     all    no     array   no    0
mb_ereg_replace  ereg    str/expr no     all    no     no      yes   0
mb_eregi         ereg             yes    all    no     array   no    0
mb_eregi_replace ereg    str      yes    all    no     no      no    0
preg_match       preg             yes/no one    no     array   yes   0
preg_match_all   preg             yes/no all    no     array   yes   0
preg_replace     preg    str/expr yes/no n/all  yes    no      yes   0
str_replace      str     str      no     all    yes    number  no    0
str_ireplace     str     str      yes    all    yes    number  no    0
strstr, strchr   str/char         no     one    no     substr  no    0
stristr          str/char         yes    one    no     substr  no    0
strrchr          char             no     one    no     substr  no    -1
strpos           str/char         no     one    no     index   no    n
stripos          str/char         yes    one    no     index   no    n
strrpos          char             no     one    no     index   no    n
strripos         str              yes    one    no     index   no    -1
mb_strpos        str              no     one    no     index   no    n
mb_strrpos       str              yes    one    no     index   no    -1

Для сравнения, в перле вся перечисленная функциональность обеспечивается набором из 4 простых операторов. Я уж умолчу о том, что для регистрозависимых и регистронезависимых сравнений в PHP надо применять разные функции (в перле lc(), /i). Или про небезопасный вызов system(), сделанный через одно место [3] - мало того, что аргументы нельзя передать безопасно, так еще и возвращаемое значение системного вызова system() возвращается в другом аргументе PHP-функции system(), а сама она вернет последнюю строку вывода запускаемой команды (кому нужна одна лишь последняя строка? Программерам явно было лениво реализовывать сохранение всего вывода. Хотя, есть же функция passthru() - так что вообще идиотизм).

2. Чрезмерно большое количество глобально видимых функций [1, 4, 9].
По состоянию на ноябрь 2003 года, PHP имел 3079 глобально видимых функций (примерно 4400 в феврале 2006) при сборке со всеми возможными расширениями, тогда как Perl - всего 206 core-функций.
"У нас есть всё необходимое!"

Средняя длина имени функции в PHP составляла 13 символов, в перле же - 6 символов.
Вывод - в перле необходимо учить и помнить гораздо меньше (особенно с учетом пункта 1, с более строгим именованием функций).
Происходит же это как за счет использования в перле модулей, так и из-за того, что в PHP обычно существуют несколько очень похожих функций. Вот наиболее характерные примеры:
* Сортировка:

    * PHP: (16)
    sort, arsort, asort, krsort, ksort,
    natsort, natcasesort, rsort, usort,
    array_multisort, uasort, uksort, dbx_sort,
    imap_sort, ldap_sort, yaz_sort

    * Perl: (1)
    sort

* Обработка списков

    * PHP: (10)
    array_filter, preg_grep, array_search,
    array_unique, in_array, array_map, array_walk,
    array_count_values, array_change_key_case, array_sum

    * Perl: (2)
    map, grep

* Разбиение строк:

    * PHP: (8)
    split, explode, strtok, spliti, chunk_split, mb_split, preg_split,
    str_split

    * Perl: (1)
    split

* Замена подстроки:

    * PHP: (12)
    ereg_replace, eregi_replace, mb_ereg_replace, mb_eregi_replace,
    preg_replace, str_ireplace, str_replace, ltrim, rtrim, trim, nl2br

    * Perl: (1)
    s///

* Печать/вывод:

    * PHP: (14)
    print, echo, printf, fprintf, vprintf, dio_write, fwrite, fputs,
    gzwrite[2], socket_send, socket_sendmsg, socket_sendto, socket_write,
    socket_writev

    * Perl: (5)
    print, printf, syswrite, send, write


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

3. Отсутствие раздельных пространств имен; области видимости [1, 2, 4, 6, 7, 9].
Одним из наиболее серьезных недостатков PHP является наличие только одного единого namespace'а. PHP не поддерживает модули ни в каком виде. Все подключаемые файлы (файлы, а не модули!) разделяют то же самое пространство имен, что здорово затрудняет работу в команде - разработчикам приходится следить за именованием функций, чтобы не назвать их одинаково. Да, и не попасть случайно названием (даже похожим, чтобы не путаться) в одну из более 4000 функций, уже имеющихся в языке. Весело? Вот и получаются длинные уродливые названия функций вроде xsl_xsltprocessor_transform_to_xml - надо же как-то отличать функции по принадлежности...
Надо отметить, что ООП в PHP5 проблему смягчает, но вовсе не устраняет.
А меж тем PHP не то что бы знать не знает про lexical scoping и dynamic scoping (забудьте, это заоблачная высь) - проблемы есть с гораздо более приземленными вещами. Вроде использования глобальных переменных в функциях. Привычная по другим языкам видимость переменных во вложенных блоках есть:
$message = 'Hello, world!';
if (!$quiet) {
    echo $message, "
";
}

А вот внутри функций - почему-то уже нет:
$message = 'Hello, world!';

function print_message ()
{
    // Tries to print a nonexistent local variable
    echo $message, "<br />";
}


Даже опытные PHP-программисты, бывает, проводят часы в поисках ошибок, связанных с забытым импортом глобальных переменных внутрь функций:

$message = 'Hello, world!';

function print_message ()
{
    // Bring the variable into this function's scope
    global $message;
    echo $message, "<br />";
}


Всё становится еще смешнее, когда дело касается встроенных переменных PHP. Если вы не импортируете внутрь функции, к примеру, переменную $HTTP_USER_AGENT, взятую от web-сервера, то PHP вам и не даст к ней доступ. Доступ к переменной, которую он создал для вашего же удобства.
Сюда же надо отнести и известные проблемы с настройкой register_globals, когда для удобства создавались отдельные глобальные переменные, а потом, после отключения сей переменной, все завязанные на это скрипты разом перестали работать. Вообще, совместимость между разными версиями PHP - сплошная головная боль для админов...

4. Недостатки реализации [4, 6].
Отдельная тема - непосредственно сам интерпретатор PHP, работающий на серверах. Из наиболее важного следует отметить плохую поддержку рекурсии - PHP4 использует для размещения данных стек, а не heap. Другая проблема - не все подключаемые модули PHP тредобезопасны. В результате чего авторы PHP официально не рекомендуют использовать Apache 2 в multithreaded-режиме. Более того, есть мнение, что именно эта недоработка PHP до сих пор препятствует широкому распространению Apache 2 (см. Slashdot: Sites Rejecting Apache 2?). На этом фоне уже мелочью выглядит то, что производительность PHP может быть повышена до 500% (!). Почему же не повысят? А потому что как тогда Zend будет зарабатывать деньги на своем Zend Accelerator? (Существуют и другие коммерческие компиляторы PHP - потому что для больших сайтов интерпретатор их серьезной нагрузки просто не выдержит). Производители некоторых модулей (например, генерации PDF-Файлов), кстати, тоже поставляют их под коммерческой лицензией.
Впрочем, эти проблемы в принципе поправимы. А про особенность проектирования под названием "php.ini" такого сказать уже нельзя. Настройки в этом файле едины для всех сайтов, располагающихся на том же сервере. А им может быть требоваться совершенно разная конфигурация. Начиная от того, что кому-то может быть нужен E_NOTICE, а соседа и warning'и не интересует, и заканчивая уже упомянутой опцией register_globals и другой, не менее коварной - magic_quotes_gpc. Последняя автоматически экранирует полученные от пользователя данные для безопасного помещения их в базу данных. Видимо, в надежде, что они сразу пойдут только туда, а программист туп, и сам их заэкранировать не догадается. А программист может захотеть не положить данные в базу, а вывести на экран. В результате пользователь получит мусор. Таким образом, для написания переносимых скриптов придется проверять наличие этой переменной и обрабатывать данные самостоятельно, если такое поведение нежелательно:
if (isset($param1) && get_magic_quotes_gpc())
    $param1 = stripslashes($param1);

[Update: Здесь [6], вообще говоря, ошибается - существует возможность переопределения почти всех настроек php.ini в httpd.conf или .htaccess, а многие можно сменить прямо в скрипте функцией
ini_set()
(см., однако, комментарии по приведенным ссылкам). Тем не менее, утверждение о порочности практики изменения поведения языка с помощью директив какого-то конфигурационного файла остается в силе.]
Еще здесь можно отметить, что PHP до сих пор не поддерживает Unicode. PHP-исты правда радуются, что это уже в планах и она скоро появится. В то время, как прочие языки это уже давно умеют, ага...

5. Недостатки языка [1, 2, 5, 6, 7].
Языковые недостатки PHP уже были частично рассмотрены в предыдущих разделах - из отсутствия namespace многое вытекает. Однако программиста ожидают заботливо разложенные грабли и в других, самых неожиданных местах.
PHP был разработан с целью максимально легкого обучения и использования не-программистами. Кроме того, язык, в отличие от других "доживших до наших" популярных сейчас языков, не был спроектирован одним человеком либо небольшой группой специалистов. Он вырос - Расмус Лердорф, автор PHP, принимал любые добавления в код от сторонних авторов. Отсюда и проистекает отсутствие единой концепции, что проявлется в том же хаотичном именовании функций. А стремление упростить приводит в перспективе лишь к избыточной сложности при попытке описания сложных вещей.
Вот, к примеру, массивы в PHP. Многие языки имеют как обычные массивы с числовыми индексами, так и массивы, индексируемые строками (хэши в Perl). PHP комбинирует оба в единый тип. С одной стороны, неопытному программисту не приходится заботиться о лишних типах (как в Бейсике когда-то, ага). С другой стороны - посмотрим на вот такой код:
$a1 = Array(10, 'Anne' => 32, 11, 'Bob' => 28, 12);
$a2 = Array(1 => 21, 2 => 22, 3 => 23);
$a2[0] = 20;

Сразу же возникает куча вопросов:
  • По какому индексу доставать значение 11 из $a1 ?

  • Каков порядок итерации по $a2 ? Он нумеруемый или хэшируемый?

  • Что будет, если попытаться добавить элементы в "конец" массива конструкцией $a2[] = ... ?

  • Могут ли в нумеруемых массивах отсутствовать элементы? А если да, можно ли простым способом пройтись по массиву в порядке индексов?

Разумется, на вопросы можно ответить, внимательным чтением документации и экспериментами. Но нужно ли такое счастье, даже опытному программисту?..
Похоже дело обстоит с типами данных. Когда нет разницы, число или строка, начинающему программисту вроде бы проще. И в PHP один оператор сравнения ==, не привязаный к типам данных. А теперь смотрим:
$n1 = 0;      $n2 = 10;
$s1 = 'foo';  $s2 = 'bar';

$n1 == $n2;  // Числовое сравнение, вернет false
$s1 == $s2;  // Строковое сравнение, вернет false
$n1 == $s1;  // Хороший вопрос
$s1 == $n1;  // Есть ли разница?

В таких случаях язык уже не может определить, что хочет программист. И вот в PHP4 уже появлется оператор ===, сравнивающий одинаковые типы - за что боролись, на то и напоролись...
В перле аналогичные правила преобразования строк в числа и обратно. Но перл имеет два разных оператора сравнения, для строк и для чисел. А PHP явно отвергает этот подход, утверждая в руководстве к PHP2, что это усложнит язык.
Есть и другие недостатки. В PHP отсутствуют замыкания (closures). В PHP нет анонимных фукнций. А как насчет хэша строк, вычисляемых eval'ом в процессе исполнения? Нет и других полезных мелочей. Например, в PHP нельзя легко повторить перловое $foo = bar() || $baz (если вы считаете, что эквивалентным выражением будет $foo = bar() ? bar() : $baz;, подумайте о функции bar() как об исполняющейся 10 секунд и имеющей сторонние эффекты вроде инкремента счетчика). Массивы в PHP не разворачиваются в строку, как в перле. И т.д.

6. Пригодность к разработке серьезных приложений [2, 3, 4, 5, 6, 8].
 Я не знаю ни одного программиста, который бы продолжил развиваться как программист, перейдя на пхп. Даже профессиональные программисты, переключаясь на пхп, со временем начинают деградировать (вплоть до переквалификации в манагеров :-)).
(с) potan

Опыт показывает, что PHP плохо приспособлен для разработки больших проектов. Это касается не только проблем взаимодействия разработчиков или особенностей языка, рассмотренных ранее - это вытекает из самой предлагаемой PHP организации страниц. PHP поощряет смешение разметки страниц и логики приложения, тогда как этого следует всячески избегать. Для PHP только недавно (!) начал разрабатываться стандартный framework (существует некоторое количество их от сторонних разработчиков), однако большинство авторов сайтов на PHP, похоже, вовсе не слышали ни о том, что такое framwork, ни об MVC-модели.
Отсутствует и много других важных для масштабной разработки вещей. Например, для Perl есть DBI - единый интерфейс к базам данных. Для PHP же, чтобы сменить одну СУБД на другую, придется переписать кучу кода (да, и в случае Perl скорее всего потребуется изменить часть запросов ввиду отличающихмя диалектов, но для PHP работы по переделке будет гораздо больше). Этим дело не ограничивается - для PHP нет ни хорошего HTML-парсера, ни удобных библиотек работы с WWW или почтовыми MIME-вложениями (что-то отличное от простых действий придется реализовывать своими руками на низком уровне). В то время как в CPAN есть не только это (см. LWP, MIME::Lite, WWW::Mechanize), но и многое-многое другое (PEAR до такого ой как далеко).
Вообще, профессионализм PHP-истов оставляет желать лучшего. Существующие сайты предназначены в основном для не-программистов. Оно и понятно, ориентируются-то на начинающих. Поэтому и сам язык развивается далеко не лучшим образом. Весьма актуальна, например, проблема XY [3]: когда вопрошающий хочет, чтобы было X, тогда как в действительности он хочет Y, и думает, что X поможет ему достичь Y - и, как правило, ошибается (сразу возникают ассоциации на "Как правильно задавать вопросы" - видимо, они этого не читали).
В результате многие разработчики вынуждены самостоятельно изобретать велосипед. Рассмотрим такой типичный проект среднего размера, как phpBB. Этот форум поддерживает несколько распространенных СУБД, умеет отправлять письма почтой, поддерживает "темы" (полностью изменяющие внешний вид), в том числе для разных языков, предоставляет сносные возможности по администрированию и модерации - в результате, очень популярен.
И что же мы видим внутри? Свой собственный уровень абстракции для доступа к базам данных. Свою собственную реализацию составления e-mail сообщений (соответствующих RFC по MIME). И полное разделение программного кода и HTML-разметки - более того, создана своя собственная система шаблонов - HTML-код в шаблонах параметризуется множеством идентификаторов, в которые и подставляется результат работы. То есть разработчики были попросту вынуждены отказаться от одной из ключевых возможностей PHP - вставки тегов с кодом непосредственно в разметку.
Понятно, что при всём вышеперечисленном у phpBB код далеко не самого худшего качества. И, тем не менее, phpBB - постоянный гость в новостных лентах сайтов, посвященных безопасности - за несколько лет существования проекта в нем было обнаружено поистине огромное для его размеров количество дыр (даже червь существовал, поражающий эти форумы), и многие хосты стали просто отказываться от него. И это при том, что документация заявляет, что он "...Designed with security as a priority" !
Дыры нередки и во многих других приложениях, написанных на PHP. Что поделать, если язык плодит неквалифицированных программистов. Но в таких условиях, выбор PHP как инструмента для создания больших проектов - рискованное решение.

Резюме.
PHP весьма удобен для написания небольших динамических сайтов (домашних страничек, например), но категорически не является подходящим инструментом для создания чего-то большего. В случае, если ваш проект состоит более чем из десятка скриптов, лучше использовать что-то более заточенное под web-разработку, хотя бы Perl, если нет ничего лучше (Perl тоже отнюдь не идеален и многим плох - см., например, "Критический анализ языка Perl" или Perl: Terse and Ugly?).

Ссылки:
[1] PHP in contrast to Perl
[2] Re^2: Is Perl a good career move?
[3] Yaywoo!
[4] Why PHP sucks
[5] I hate PHP
[6] Experiences of Using PHP in Large Websites
[7] PHP Annoyances
[8] PHP: A love and hate relationship
[9] My list of PHP shortcomings
[10] "Держитесь подальше от PHP!"
(если какой-либо документ недоступен, используйте http://web.archive.org)

Дополнения (ответы на некоторые возражения):
1. Получилось сравнение на уровне хорошо (Perl) - плохо (PHP), и Perl типа рулит. А где разбор недостатков перла? И вообще, какой-то детский сад, "что круче, Феррари или Порше?".

Это именно разбор глюков PHP ("какое говно Запорожец"), а Perl приводится лишь для сравнения. Насчет перла в конце ссылки даны, например "Критический анализ языка Perl" - сходите и удивитесь, там 200 килобайт разбора недостатков перла.

2. Фигня про глобальные переменные, функции и модули написана - в PHP 5 же есть нормальное ООП.

Написал же. Проблему смягчает, но не устраняет - имена классов, например, по-прежнему глобально видимы. Сравните, например, с пакетами в Java (хотя это далеко не лучшая реализация, но в PHP и этого нет).

3. Ну и что, что массивы и хэши объединены - пример кода специально подобран, разве будет такое в реальном web-приложении?

Ну вот из [2]:
You can't just translate
$foo[4] = 4; $foo[2] = 2; foreach $element (@foo) { print $element }
to
$foo[4] = 4; $foo[2] = 2; foreach ($foo as $element) { print $element }

The Perl version prints 24, PHP insists on 42. Yes, there is ksort(), but that isn't something you can guess. It requires very in-depth knowledge of PHP. And that's the one thing PHP's documentation tries to avoid :)

Вы хотите сказать, что в реальном приложении у вас никогда не может случиться такого присвоения не в том порядке (разбавленного другими строками, естественно)?

4. Изобретают велосипед, говорите? Проблемы негров шерифа не волнуют Пусть мучаются, и наступают на грабли, а опытный программист таких ошибок не допустит, выберет все нужные инструменты/подходящий framework/библиотеки, и сделает качественный продукт. Так что не пугайте голым PHP.

Проблема в том, что язык культивирует именно дилетантский подход. Понятно, что профессионал сделает лучше, только вот мало их, профессионалов (вот и имеем дырявые продукты...). И вообще, можно и из говна конфетку сделать, но зачем тратить столько усилий, когда можно взять более подходящий материал?

5. Пока вы тут рассуждаете, насколько ублюдочен PHP, и на чем на самом деле надо писать для web, миллионы программистов (и не очень) делают на нем реально полезные сайты.

Читаю, и перед глазами живо встает картина: 20-е годы, советская власть - "Пока вы, бездельники, тут трактор изобретаете, миллионы крестьян запрягают в плуг быков и делают реальное и полезное дело, хлеб выращивают!". А что, ведь два миллиона леммингов не могут ошибаться действительно полезным делом занимаются, ага...
Tags: web-разработка, программирование, сравнение ЯП и компиляторы
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 270 comments
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →