И я уже знаю, что скажете вы, глядя на заголовок статьи:
— Кто ты такой? Почему ты позволяешь себе так говорить?
Отвечу сразу, чтобы не было недомолвок:
- Я профессионально программирую на PHP с 2004 года, то есть вот уже 16 лет на момент написания этой статьи, и продолжаю это делать каждый день
- Я преподаю программирование, в том числе и на PHP, примерно 10 лет и за это время выпустил в свет несколько тысяч студентов
- Я всегда был в восторге от каждой новой версии PHP, что выходила со времен от 5.0 до 7.4 и всегда был адептом подхода «пишем на самой свежей версии, тестируем на следующей»
И всё-таки, несмотря на всё сказанное выше, мне не нравится то, во что превращается PHP сейчас и во что он превратится уже скоро, буквально этой осенью.
Почти каждый принятый в PHP 8 RFC вызывает во мне боль и недоумение. И я готов объяснить и защитить свою позицию.
Меняются базовые концепции языка
С чего обычно начинается изучение нового языка? Значения, выражения, переменные и функции. Так и при изучении PHP — это естественный порядок.
Когда мы начинаем проходить функции, я обращаю особое внимание студентов на то, что в PHP контекст функций замкнут, «герметичен». Это очень просто и совершенно логично — нужно лишь четко запомнить, какие имена видит функция: свои аргументы и свои внутренние переменные.
Когда мы доходим до азов ООП и изучаем понятие «метод», мы опираемся на понятие функции, и добавляем еще один пункт к контексту: $this в динамических методах. Меняется ли что-то еще? Нет. Функции по-прежнему замкнуты, их контекст перестает существовать после вызова, снаружи ничто не протекает в функцию, наружу из нее тоже не утекает.
Доходим до анонимных функций — и тут правила игры тоже не меняются. Аргументы? Да. Внутренние переменные? Да. $this? ОК, давайте обойдем этот вопрос, иначе нам придется обсуждать странную вещь под названием «статическая анонимная функция» :)
Далее добавляем понятие «замыкания». С ним тоже всё логично, отдельное ключевое слово, конечный список переменных из контекста создания, базовые принципы не нарушены. А учитывая, что замыкается значение, а не имя, то вообще всё отлично — функции продолжают быть «герметичными». Ничто снаружу не просочится, ничто наружу не выливается.
А знаете, что происходит дальше?
А дальше у нас появляются стрелочные функции. И это ломает всё.
Я вынужден объяснять, что стрелочные функции нарушают усвоенные с таким трудом принципы, поскольку замыкают на себя ВЕСЬ контекст в момент своего создания.
Опс.
А как же принцип «герметичности»? А никак, на него наплевали в угоду упрощения написания, сэкономив 6 символов — теперь у нас «fn» вместо «function».
Плохо. Непоследовательно.
Вы думаете, что это единственный пример? Как бы не так.
Задайте любому нубу вопрос — с какого символа в PHP начинаются имена? Правильно, с символа "$"
- имена переменных
- имена свойств объектов
- имена свойств классов
- имена аргументов
- даже «переменные переменные» — тоже "$" !
Логично? Да. Последовательно? Да. Нужно только помнить про константы, которым $ не нужен.
Что теперь у нас появляется? Именованные аргументы: wiki.php.net/rfc/named_params
array_fill(value: 50, num: 100, start_index: 0);
Где «доллар»? Нет.
Это проблема.
Теперь придется запоминать, что в сигнатуре функции «доллар» писать нужно, а при вызове — нет. И это очень серьезная проблема, нарушающая целостную систему языка. Это плохо и непоследовательно.
Ради чего? Да так, просто, потому что кому-то захотелось перенести сахарок из Python в PHP, не подумав. Однако в Python хотя бы используется один и тот же символ "=" для сопоставления имени и значения, что в присваивании, что в именованных аргументах, а у нас теперь их будет два — обычное "=" и ":" в новой конструкции.
Почти каждый обсуждаемый для PHP 8 RFC несет одну и ту же проблему — нарушение ранее сложившейся системы языка. И это плохо. Это ломает мозг и тем, кто пишет на PHP давно, и тем, кто только начинает его изучать.
Смотрите: ( wiki.php.net/rfc/match_expression_v2 )
echo match (1) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
};
это новые match-expressions. Можете объяснить, почему в них используется символ "=>", а не привычный по switch-case ":"? И я не могу.
Это снова нарушение уже сложившейся системы. Символ "=>" всегда (до чертовых стрелочных функций, снова они!) обозначал разделитель пары «ключ-значение». Теперь он обозначает еще и разделитель списка аргументов и возвращаемого значения в «стрелке», и символ выбора значения в операторе match.
Это плохо. Это очень плохо. Это крайне непоследовательно. Это даже хуже, чем с ключевым словом static, которое используется как минимум в трех принципиально разных значениях.
Нечитаемость на естественном языке
Покажите носителю английского текст
SELECT *
FROM users
WHERE age>=18 AND name LIKE 'J%'
и он, если его IQ превышает 60, с легкостью объяснит — о чем этот текст и что будет сделано при реализации этого текста, как программы.
Покажите гению, не знакомому с JS, текст
const f = () => 42;
и он не поймет ничего. Константа? f это скобки? Скобки стремятся к числу? Что это?
Я всегда был рад, что PHP далек от того, чтобы принести в жертву читаемость кода в угоду краткости. Я был рад, что он далек от JS, где принцип читаемости кода был отринут в пользу «пиши меньше символов, всё равно этот код читать никто не будет».
Теперь я понял, что в PHP 8 принцип естественной читаемости кода будет нарушен. И, судя по всему, бесповоротно.
Просто посмотрите на эти примеры:
wiki.php.net/rfc/constructor_promotion
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
Теперь у нас вместо аргументов конструктора — сразу и объявление свойств и задание им значений из аргументов.
Догадайтесь, что после знаков "="? Начальные значения свойств? Или дефолтные значения аргументов конструктора? Догадаться невозможно. Узнать, прочтя код — тоже. Это плохо. Еще одно место, которое нужно заучивать наизусть.
wiki.php.net/rfc/property_write_visibility
class User {
public:private int $id;
public:protected string $name;
}
Публично-приватное свойство? Серьезно? Как из этого кода понять, что речь идет о свойстве доступном на чтение и недоступном на запись?
wiki.php.net/rfc/conditional_break_continue_return
function divide($dividend, $divisor = null) {
return if ($divisor === null || $divisor === 0): 0;
return $dividend / $divisor;
}
Серьезно, это кому-то нужно? Кто-то писал годами на PHP и страдал из-за отсутствия возможности написать «return… if» вместо «if… return»? Нам действительно нужен новый йода-синтаксис для банального if и return?
Слишком много способов сделать одно и тоже
PHP всегда нарушал знаменитый принцип «должен быть один и только один способ...» Но он делал это разумно, понемногу и обоснованно.
Теперь же этот принцип растоптан и уничтожен. Каждый принимаемый RFC словно говорит «а давайте добавим еще один способ написать if, ведь я видел такое в Perl/Python/Ruby/Brainfuck!» — причем других обоснований, кроме как «я видел» в общем-то не приводится.
Что было:
if ($y == 0)
return 0;
if ($y == 0) {
return 0;
}
if ($y == 0):
return 0;
endif;
— целых три способа записи одного и того же. Не очень хорошо, приходится объяснять: зачем их три, почему не стоит писать код без операторных скобок и для чего нужен альтернативный синтаксис.
Но этого мало! Подождите еще немного, и вы увидите:
// революционный Йода-If
return if ($y == 0): 0;
Вот так вызывались функции:
foo(bar($baz));
Скоро вы увидите вот такое:
$baz |> 'bar' |> 'foo'
— гениально, не правда ли? Сразу же понятно, что это вызов функций!
И это я еще ничего не написал про стрелочные функции :)
Больше, больше способов сделать то, что делалось и раньше без всяких проблем:
- match-expressions
- оператор "?->"
- два разных синтаксиса для замыкания
- цикл + else
- статические конструкторы
- объявление свойств в аргументах конструктора (!)
и многое другое, что приведет лишь к тому, что код станет сложнее читать, а язык — сложнее изучать.
Итог
PHP развивается. Это важный процесс, который затрагивает огромное количество людей и влияет на всё сообщество программистов.
Однако начинает складываться ощущение, что развитие заходит куда-то не туда. Я опасаюсь, что принимаемые изменения приведут язык к тому, что программы на нем будут становиться всё более короткими и всё более малочитаемыми, к тому, что он будет предоставлять всё больше и больше возможностей сделать одно и тоже разными способами и для изучения всех этих способов будет требоваться всё больше и больше времени.
Мне не хочется увидеть через год на месте PHP новый Perl. А вам?
bm13kk
Без обсуждения самих сахарков.
Это было неизбежно. Любой язык начнет набирать «лишние» и неконсистентные фичи. Это лишь вопрос времени. Скажите спасибо что лично для Вас он держался так долго.
FreeBa
Я вас таки умоляю. Расскажите это создателям C# — который в принципе состоит из таких вещей, чуть менее, чем полностью. И ничего, все счастливы и требуют еще больше сахара!
PS: Нужно признать что там весь сахар органично накладывается на базу языка, а не как в случае с пхп8, но сам факт…
PsyHaSTe
Не все счастливы, я например не очень. Но наличие сахара лучше, чем его отсутствие. Библиотечные расширяемые способы организовывать код были бы лучше, но их пропихивание занимает целые мажорные версии языка, а сахар сыпят в каждом релизе.
FreeBa
И таки что в этом плохого? Путь шарписта есть путь смерти от сахарного диабета.
nullptr
Порог входа в язык неоправданно возрастает. Если лет 10 назад C# был сравнительно лаконичным и согласованным языком, то сейчас новчику придется столкнуться с тоннами «магии» которая которая есть простопотомучто.
Лет через 10 C# превратится в такое же болото, которым сейчас является С++ и никто в здравом уме в это болото не сунется.
FreeBa
Вся прелесть заключается в том, что никто не запрещает пилить код на шарпе 10-летней давности (как и в случае с явой). И работать оно будет ничуть не хуже чем код обмазанный современным сахарком (а зачастую оно будет работать даже лучше, ибо никто по зиро кост абстракшн не упарывается).
Так что это не имеет значения. Порог входа в шарп останется тем же самым, что и 17 лет назад. А если какому-то джуниору придет в голову запихать ArrayList на прод, то ему очень быстро и доходчиво разьяснят, что время таких вещей безвозвратно ушло.
nullptr
FreeBa
Ничто не идеально в нашем мире. Тут вы безусловно правы.
Но никто не говорил, что быть программистом легко. Хочешь, не хочешь а частности, которые нигде не используются, но они есть — придется выучить. С этим ничего не сделать, увы.
Но это приходит потом. Совсем потом.
da411d
Я студент который пропускал пары по шарпам. Почему не хорошо запихать ArrayList на прод?
Zam_dev
Если коротко, лучше юзать к примеру List, т.к. он типизированный, в отличии от ArrayList который воспринимает все как object, что влечет к лишним упаковкам и распаковкам (unboxing)
PsyHaSTe
При всех минусах, до плюсов шарпу ещё лет 40 так же развиваться, а пистать на новых версиях удобнее, чем на старых.
nullptr
Тут спору нет, но тем, кто сейчас пытается зайти во всю эту махину с нуля можно только посочувствовать, наверное.
PsyHaSTe
А что там такого невыучиваемого добавилось? Каждый релиз это пяток фич которые прочитваются в блогпосту на 15 минут.
Stanislavvv
Прочитать про действительно новую фичу, которую не видел до этого — не то же самое, что и выучить её. Неиспользуемое на практике — забывается…
PsyHaSTe
Там все фичи уровня "один раз прочитал — и умеешь пользоваться". НУ что там — readonly структуры? Оператор
?.
? То что разрешили инициализировать проперти без сеттеров? То что разрешили для однострочных функция не писать return?А все версии сишарпа начиная с пятой выглядят именно так. И в следующей версии судя по результатам митингов опять добавят мелочь типа "можно не писать скобочки у неймсейса" или "Relax rules for trailing whitespaces in format specifier".
Druu
Потому что надо не языки учить а базовые концепции. Вон как автор статьи жалуется на связывание переменных, которое что-то "ломает".
PsyHaSTe
Ну основная проблема как я вижу в нерасширяемости и ограниченности сахара, а также фрагментация. Вот сделали оператор
?.
— окей, но что делать с вызовом статического метода?x?.Foo()
написать можно аFoo(x?)
или что-то такое уже не сделать. Соответственно код фрагментируется на тот или иной, и минорный рефакторинг привносит серьезные изменения, даже если дописатьthis
в метод, сделав из обычного хелпера метод-расширения, и уже везде ИДЕ будет ругаться "а вот можно упростить", и наоборот.Во-вторых расширяемость. От синтаксиса нельзя абстрагироваться. И это вообще не удобно.
Наконец, многий сахар просто костыльный. Например, завезли нуллябельные типы, вроде все рады? Но код вида
T? Foo<T>()
написать просто невозможно. Почему? Потому чтоT?
для реф и вэлью типов означает совершенно разные вещи.FreeBa
Вот тут вы не правы. Hемного странно в посте о пхп рассуждать о шарпе — но что имеем, то имеем.
Шарп базируется на 3 абстракциях — это делегаты, перечисления и синтаксические деревья (привет LINQ).
Указанный вами оператор?.. — это всего лишь частный случай вызова делегата. Он не может вызываться как то вне семантики делегатов — т.е. Foo(x?) — будет невалидным с точки зрения именно базового устройства языка. Причем Foo(x)?.(z) — вполне себе идиоматичная конструкция (хоть и не используемая).
Весь сахар он четко накладывается именно на каркас языка, на то что в него вложили почти 20 лет назад. Да, кажется — что некоторые конструкции уходят прямо в какие то сверхвысокоуровневые дебри, но это только кажется.
За «нуллябельные» типы, в целом согласен, их очень сильно сбоку впилили, но то требование времени и хоть какая то попытка решения проблемы NullReferenceException, не самая идеальная, но ребята стараются. Лучше так, чем никак совсем.
PsyHaSTe
Оператор
x?.y
к делегатам отношения не имеет, это просто сахар дляx is null ? null : x.y
. Где тут какие делегаты?Но с точки зрения того же рефлекшна инстансные методы эквивалентны статическим с дополнительным this-аргументом. И правильно делает, кстати.
Druu
Так это не проблема ?, это проблема реф-типов и велью-типов в генериках. Ей уже нцать лет.
PsyHaSTe
?.. в генерик реф-типах появился в последней версии сишарпа, привет. Да и сам
?.
не особый долгожитель в языке.Druu
Тaк я и говорю, что описанная тобой проблема, по сути, никак не связана с?.. Поведение? — это просто симптом. А проблема, которая его вызывает, существовала задолго до.
PsyHaSTe
Могли просто сделать опшн и задеприкейтить нуллябл, как сделала скала. Они же смогли на Span API как-то переползти, могли бы и опционалньые значения нормальные сделать.
Но им слишком дорого это делать, а их ЦА это монстры с 20-летним легаси которым неинтересны эти игрушки, им херак-херак и в продакшн интереснее, обмазавшись динамиками и рефлекшном вкупе с COM. Что весьма грустно, в середине-конце нулевых язык выглядил революционно, а сейчас просто беттер жаба для дотнета, причем не факт что в самом деле беттер.
altmf
C# еще только обрастает сахарком, гляньте, например, на Visual FoxPro, за долгие годы развития он оброс этим самым сахарком (ну по крайней мере когда-то это казалось сахарком), что в 9 версии было уже не видно, где сахар, а где сам язык. Впрочем, функции он свои выполнял, вахту отстоял и ушел на покой.
bm13kk
То что язык Х сегодня все еще развивается консистентно — еще не значит что так и будет дальше. С++ в версии 89 был еще очень даже консистентным. Я бы даже сказал что 9x еще можно было как-то структурировать. Но в двухтысячных С и С++ начали активно наливать все новые фичи и теперь их код я уже не могу читать.
serge-phi
Писатели кода может и счастливы, а вот читатели — не очень, как мне кажется. Сделать одно и то же несколькими способами — это не всегда преимущество.
mikhailian
Ну, go пока держится. Даже дженерики ещё не приняли.
HEKET313
К сожалению
newpy
Я не понимаю порой, как можно тащить в язык дженерики, и думать о них, когда в языке нет нормального Set (множества), который сделан на Map. Как по мне, это тоже пример непоследовательного подхода и попытки угодить страждущим.
И вот ниже resetme подсказывает, что в таком виде, не стоят дженерики того. Лично я скорее согласен. Они нужны, но в таком виде, субъективно, нет.
resetme
К радости. В таком виде не надо.
Hett
Эм, а разве в go нет дженериков?
khim
Ну вот свеженькое обсуждение — меньше месяца назад.
Впрочем 10 лет назад когда Go только появился про них уже говорилось: Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do.
Так что нельзя сказать, что это прям вот неожиданно возникшая фича.
Vilaine
Они там изначально есть в виде контейнеров, просто вы свои не сможете объявить.
bm13kk
Я не понимаю хабр. Почему этот комментарий имеет минус — понятно, логично и правильно.
Но за что минус в карму? Какие правила я тут нарушил? Кого обидел?
hd_keeper
Издержки местной системы кармы. Расслабьтесь и получайте удовольствие.
Nex_Otaku
Нет, не любой. Особенность PHP в том, что нет человека, который бы направлял его развитие.
Автор языка, Расмус не накладывает вето на подобные «улучшайзинги». Он устранился от руководства. Решения, каким будет PHP, принимаются в духе демократии, голосованием нескольких десятков наиболее активных персон…
Итог закономерен — под руководством толпы язык сползает в мейнстрим.
В мейнстриме сейчас нечитаемый яваскрипт. Поэтому и PHP всё больше становится похожим на яваскрипт и нечитаемым.