Принимая PHP всерьёз
Ракета Союз, доставленная на поезде на пусковую площадку. Фото из общественного достояния NASA.
Это перевод статьи Taking PHP Seriously, автор которой является одним из инженеров известного приложения Slack. Он рассказывает о недостатках и преимуществах PHP, а также о языке Hack и виртуальной машине HHVM, на которую почти завершил переход Slack.
Slack использует PHP для большей части своей серверной логики, что является не самым популярным выбором в наши дни. Почему же мы решили написать новый проект именно на этом языке? Следует ли вам поступать также?
Большинство программистов, которые немного игрались с PHP, знают две вещи про него: это плохой язык, который они никогда не станут использовать при наличии выбора, и что некоторые из чрезвычайно успешных проектов в истории мира используют его. Это не совсем противоречие, но этот факт должен заставить вас задуматься. То есть, Facebook, Wikipedia, Wordpress, Etsy, Baidu, Box и в последнее время Slack — все они успешно решают проблемы, не смотря на то, что используют PHP? Были ли бы они более успешными, если бы они использовали у себя Ruby? Erlang? Haskell?
Вполне возможно, что нет. Язык PHP имеет множество недостатков, которые, несомненно, замедлили его развитие, но среда PHP имеет такие достоинства, которые более чем компенсируют данные недостатки. И он также имеет способы разрешения его собственных языковых проблем, которые вполне впечатляют. По итоговым результатам, PHP предоставляет наилучший фундамент для создания, изменения и эксплуатации успешного веб-проекта по сравнению с конкурирующими средами. Сегодня я хотел бы начать новый проект на PHP, с парой оговорок, но не видя причин извиняться за это.
Исторический экскурсPHP родился в уникальной для современных языков среде веб-сервера. Его сильные стороны связаны с контекстом запроса на сервере.
PHP изначально назывался «Персональная Домашняя Страница». Он был опубликован в 1995 г. Расмусом Лердорфом, с нацеливанием на поддержку маленьких, простых динамических веб-приложений, вроде гостевых книг и счётчиков посетителей, популярных на заре Интернета.
С момента релиза PHP, он был использован в намного более сложных проектах, чем изначально ожидали его авторы. Он претерпел несколько мажорных изменений, каждое из которых принесло новые механизмы для обуздания этих сложных приложений. Сегодня, в 2016, он является богатым фичами членом семьи Смешанной Парадигмы Продуктивных Языков (MPDPL) [1], которая включает JavaScript, Python, Ruby и Lua. Если вы пробовали PHP в ранние 2000-ные, современная кодовая база PHP может удивить вас трейтами, замыканиями и генераторами.
Добродетели PHPPHP имеет несколько очень глубоких и, однозначно, верных особенностей.
Первая, состояние. Каждый веб-запрос начинается с совершенно чистого листа. Его пространство имён и глобальные переменные не инициализированы, за исключением некоторых стандартных глобальных переменных, функций и классов, которые предоставляют примитивную функциональность и жизнеобеспечение. Начиная каждый запрос с известного состояния, мы получаем своего рода изоляцию от возможных ошибок; если запрос t сталкивается с неполадкой ПО и завершается с ошибкой, данный баг не оказывает никакого влияния на выполнение последующего запроса t+1. В действительности, конечно же, помимо программной кучи, состояние приложения находится и в других местах, и вполне возможно полностью испортить базу данных, memcache или файловую систему. Но PHP разделяет эту слабость со всеми мыслимыми средами, которые позволяют сохранять состояние. В то же время, разделение программных куч между запросами снижает цену большинства программных ошибок.
Вторая, параллелизм. Индивидуальный запрос работает в одном PHP потоке. На первый взгляд, это кажется глупым ограничением. Но так как наше приложение выполняется в контексте веб-сервера, мы имеет натуральный источник параллелизма: веб запросы. Асинхронный curl’инг на локалхост (или даже другой веб-сервер) предоставляет неразделяемый (shared-nothing), копирование-в/копирование-из подход использования параллелизма. На практике, это безопаснее и устойчивее к ошибкам, чем подход с блокировками и разделяемым состоянием, который используется в других языках общего назначения.
В заключении, тот факт, что PHP программы оперируют на уровне запросов, означает, что рабочий процесс программиста является быстрым и эффективным, и остаётся быстрым после изменения приложения. Множество языков продуктивной разработки претендуют на это, но если они не очищают своё состояние при каждом запросе, и основной поток событий разделяет программный уровень состояния между запросами, они почти всегда требуют некоторое время на запуск. Для типичного сервера приложений на Python’е, типичный цикл отладки будет выглядеть примерно как «подумать, отредактировать, перезапустить сервер, отправить несколько тестовых запросов». Даже если «перезапустить сервер» занимает всего несколько секунд из общего количества часов, это забирает большой срез из 15–30 секунд наших человеческих мозгов на необходимость удержания в голове самой ненужной части текущего состояния.
Я утверждаю, что разработка на PHP в стиле «подумать, отредактировать и перезагрузить страницу» делает разработчиков более продуктивными. В долгих и сложных циклах разработки проектов это даёт ещё больший прирост.
Доводы против PHPЕсли всё это является правдой, почему же его так ненавидят? Когда вы уберёте все красочные гиперболы в сторону, что основные жалобы о PHP кластере сведутся к следующим проблемам:
- Сюрпризы при преобразованиях. Почти все языки в наши дни позволяют сравнить, например, integer и float с оператором >=; Чёрт, даже C позволяет. Совершенно понятно, что здесь имеется ввиду. Менее очевидно сравнение строки и числа с помощью ==, и разные языки делали разный выбор. Выбор PHP в данной ситуации наиболее порочен, что приводит к сюрпризам и неприятным ошибкам. К примеру, 123 == '123foo' оценивается как истина (что он там делает?), но 0123 == '0123foo' является ложью (хмм).
- Несогласованность вокруг ссылок и семантических значений. PHP 3 имел чёткую семантику передачи аргументов, возвращения всего по значению, создавая логическую копию данных в запросе. Программист может выбрать ссылочную семантику вместе со знаком & [2]. Это возникло вместе с введением объектно-ориентированных средств программирования в PHP 4 и 5. Большинство PHP ОО-аннотаций были позаимствованы из Java, и Java имеет семантику, в которой объект передаётся по ссылке, в то время как примитивные типы передаются по значению. В итоге, текущее состояние семантики PHP заключается в том, что объекты передаются по ссылке (выбираем Java, вместо, скажем, C++), примитивные типы передаются по значению (здесь Java, C++, и PHP согласен), но старая ссылочная семантика и знак & остались, время от времени взаимодействуя с новым миром неоднозначными способами.
- Философия вычислений, игнорирующих отказы. PHP пытается очень, очень трудно сохранять запрос запущенным, даже если он уже наделал чего-то совсем странного. Так, к примеру, деление на ноль не бросает исключения, не возвращает INF, и не завершает фатально запрос. По умолчанию, он просто предупреждает и присвоит значение как false. Так как false молча рассматривается как 0 в числовых контекстах, множество приложений разворачиваются и запускаются с недиагностированными делениями на ноль. Конкретно эта проблема была разрешена в PHP 7, но импульс в дизайне к обработке неоднозначностей, даже когда они могут иметь смысл, пропитывает в том числе и библиотеки.
- Противоречия в стандартной библиотеке. Когда PHP был молодым, его аудитория была наиболее знакома с C, и множество API использовали дизайн стандартной библиотеки языка C: шести-символьные имена в нижнем регистре, ответы об успешном/неуспешном выполнении и возвращающие реальное значение в вызываемый параметр «out», и так далее. По мере развития PHP, C-шный стиль разделения на пространства имён через префиксы с _ стал более распространённым: mysql_…, json_…, и так далее. А совсем недавно, camelCase стиль именования методов из Java на классах CamelCase стал самым распространённым способом введения новых функциональных возможностей. Так, что в итоге, иногда мы видим примеры кода с перемешанными выражениями вроде DirectoryIterator ($path) вместе с if (!($f = fopen ($p, «w+»))… в сбивающей с толку логике.
Чтобы не показаться нерефлективным апологетом PHP: всё это серьёзные проблемы, которые позволяют более вероятно создавать дефекты. Они являются явными ошибками (unforced errors). Здесь нет присущего компромисса между Хорошими Частями PHP и данными проблемами. Должна быть реализована возможность создать PHP, который разрешит данные недостатки, сохранив при этом все хорошие стороны.
HHVM и HackЭтот преемник системы PHP зовётся Hack [3]
Hack — это такой язык программирования, который люди называют «постепенная система типов» для PHP. «Система типов» значит, что он позволяет программисту составлять автоматически проверяемые инварианты о данных, которые протекают через код: данная функция берёт строку или число и возвращает лист Fribbles, как например в Java или C++ или Haskell, или в любом другом статически типизированном языке, который вы выберете. «Постепенная» означает, что некоторые части вашей кодовой базы могут быть статически типизированными, в то время как другие её части могут всё ещё находится в беспорядочном, динамическом PHP. Возможность совмещать эти подходы позволяет постепенно мигрировать большие кодовые базы.
Вместо того чтобы разлить здесь тонны чернил в описании системы типов Hack и того, как она работает, просто поиграйтесь с ним. Я буду здесь, когда вы вернётесь.
Это аккуратная система, и она весьма амбициозна в том что она позволяет вам выразить. И наличие возможности постепенной миграции проекта на Hack, в случаях когда он разрастается сильнее, чем вы ожидали изначально, является уникальным преимуществом экосистемы PHP. Проверки типов Hack сохраняют рабочий процесс в стиле «думать, отредактировать, перезагрузить страницу», потому что они запускаются в фоне, постепенно обновляя модель кодовой базы, когда он видит модификации в файловой системе. Проект Hack предоставляет интеграции со всеми популярными редакторами и IDE, так что вы сможете увидеть обратную связь об ошибках типов уже тогда, когда завершите печатать код, также как в веб-демонстрации.
Давайте рассмотрим совокупность реальных рисков, которые создаёт PHP, в свете Hack:
- Сюрпризы при преобразованиях становятся ошибками в Hack файлах. Весь класс данных проблем уходит прочь.
- Семантика ссылок и значений в Hack очищена простым запретом использования ссылок старого стиля, так что они больше не нужны в новой кодовой базе. Это делает поведение семантики аналогичной стилю объектов-по-ссылке-и-всего-остального-по-значению, также как в Java или C#
- PHP-шные вычисления, игнорирующие отказы являются больше свойством среды выполнения, и их сложнее обрабатывать анализатором семантики вроде Hack, чтобы внедрить его прямо в эти системы. Тем не менее, на практике, большинство форм вычислений, игнорирующих отказы, требуют тех самых сюрпризов при преобразованиях. К примеру, проблемы, которые возникают из-за получения false после деления на ноль, в итоге не возникнут на пересечении границы проверки типа [4], которая провалится из-за попытки преобразовать булево в число. Эти границы встречаются чаще в кодовой базе Hack. Вместе с простой возможностью описывать данные типы, Hack на практике уменьшает «тормозной путь» для множества некорректных запусков.
- В завершении, противоречия в стандартной библиотеке остаются. Большинство в Hack надеются на то, что смогут сделать эту проблему менее болезненной через оборачивание её в более безопасные абстракции.
Hack предоставляет возможность, которой не имеют другие популярные члены семьи MPDPL: возможность ввести систему типов уже после основной разработки, и только частично, в тех частях системы, где значение перевешивает цену.
HHVMHack изначально был разработан как часть виртуальной машины HipHop, или HHVM, виртуальной среды с открытым исходным кодом для PHP. HHVM предоставляет другую важную опцию для успешного проекта: возможность запустить ваш сайт быстрее и более экономно. Facebook докладывает о приросте производительности в 11.6 раз на процессорной эффективности над интерпретатором PHP, а Wikipedia сообщает об ускорении в 6 раз.
Slack недавно перевёл свои веб-окружения на HHVM и получил значительные снижения задержек на всех точках выхода, но нам не хватает измерений в стиле apples-to-apples на процессорные нагрузки на момент написания этого текста. Мы также находимся в процессе перемещения нашей кодовой базы на Hack и будем сообщать о своём опыте здесь.
Смотря вперёдМы начали с очевидного парадокса о том, что PHP является очень плохим языком, который используется во многих успешных проектах. Мы считаем, что его репутация как бедного языка, в изоляции, довольно заслуженна. Успех проектов, использующих его, имеет много общего с основными свойствами среды PHP, и возможностью ускоренной разработки, которая также предоставляет PHP. И преимущества от этого окружения (сниженное количество багов через изоляцию ошибок; безопасная параллельность; высокая пропускная способность программистов) являются более ценными, чем проблемы, которые возникают из-за недостатков языка.
Кроме того, в отличии от других членов семьи MPDPL, он предоставляет чёткий путь для миграции на более производительную, безопасную и обслуживаемую среду в виде Hack и HHVM. Slack находится на последних стадиях к переходу на HHVM, и на ранних этапах перехода на Hack, и мы оптимистично настроены, так как они позволяют нам производить более качественное программное обеспечение в более быстрые сроки.
Примечания (они тоже из блога разработчика):
[1] Это я придумал термин MPDPL. В то время как существует мало генетических связей между ними, данные языки сильно повлияли друг на друга. Глядя на прошлый синтаксис, можно увидеть, что они имеют намного больше общего, чем отличий. Во вселенной языков программирования ассамблеи MIPS, Haskell, C++, Forth и Erlang, трудно отрицать, что MPDPL образуют плотный кластер в пространстве языковых дизайнов. [назад к тексту]
[2] К сожалению, & обозначается в получаемом, а не в вызывающем значении. Так что когда программист объявляет о своём желании получить параметры по ссылке, это не отображается никаким образом. Это делает сложный для понимания код и анализ того, что может измениться, и значительно затрудняет эффективную работу с PHP. Смотрите Рисунок 2 в dl.acm.org/citation.cfm? id=2660199. [назад к тексту]
[3] Да, Hack является практически негуглёжным названием для языка программирования. Иногда используется Hacklang, когда возможна неоднозначность. Если уж Google сами могут назвать свой популярный язык ещё более негуглёжным Go, то почему бы и нет? [назад к тексту]
[4] Эти проверки типов в программе на Hack также применяются во время выполнения по умолчанию, так как они работают на основе PHP-шной системы подсказок типов. Это увеличивает безопасность смешанных кодовых баз, где Hack и классический PHP смешиваются друг с другом. [назад к тексту]
Комментарии (7)
12 ноября 2016 в 05:01
–7↑
↓
Golang хотя-бы звучит.12 ноября 2016 в 09:25
–3↑
↓
Лично я, выбирая между PHP и чем-то другим, выбрал бы что-то другое, просто потому, что PHP нигде не работает одинаково (только если у вас не дефолтный php.ini, но такого практически не бывает) — и именно этот факт является самым большим источником ошибок и прочих проблем.Но PHP все же лучше других скриптовых языков подходит для разработки больших долго-живущих проектов, благодаря своей выразительности и достаточно простой структуре кода (хотя, Python для этого подходит гораздо больше).
12 ноября 2016 в 11:49
–1↑
↓
PHP нигде не работает одинаково
Не могли бы вы раскрыть этот момент подробнее?
12 ноября 2016 в 11:19
–1↑
↓
«Я утверждаю, что разработка на PHP в стиле «подумать, отредактировать и перезагрузить страницу» делает разработчиков более продуктивными. В долгих и сложных циклах разработки проектов это даёт ещё больший прирост.»Вот тут вы очень сильно ошибаетесь, ибо перезагрузить страницу, где куча состояния и взаимодействия, чтобы добраться до какой-то минифичи — это неземная боль.
12 ноября 2016 в 11:26
+1↑
↓
Это же перевод…12 ноября 2016 в 11:36
+2↑
↓
Ну автор оригинальной статьи, заблуждается (или нарочно вводит в заблуждение).
12 ноября 2016 в 12:15
0↑
↓
Это не так, на самом деле во многих других языках очень популярны инструменты имитирующие такую логику, тот же JRebel и Java, а в Spring Framework (Java) вообще практически из коробки идут инструменты позволяющие на лету при изменении исходников либо рестартить сервер, либо релоадить загруженные классы.P.S. А если взять популярную нынче тему с микросервисами, то микросервис обычно простое веб-приложение, которое не хранит кучу состояний, да и вообще, состояние это противоестественно для REST!