Мне не нравится то, во что превращается PHP
И я уже знаю, что скажете вы, глядя на заголовок статьи:
— Кто ты такой? Почему ты позволяешь себе так говорить?
Отвечу сразу, чтобы не было недомолвок:
- Я профессионально программирую на 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' |> 'bar'
— гениально, не правда ли? Сразу же понятно, что это вызов функций!
И это я еще ничего не написал про стрелочные функции :)
Больше, больше способов сделать то, что делалось и раньше без всяких проблем:
- match-expressions
- оператор »?→»
- два разных синтаксиса для замыкания
- цикл + else
- статические конструкторы
- объявление свойств в аргументах конструктора (!)
и многое другое, что приведет лишь к тому, что код станет сложнее читать, а язык — сложнее изучать.
Итог
PHP развивается. Это важный процесс, который затрагивает огромное количество людей и влияет на всё сообщество программистов.
Однако начинает складываться ощущение, что развитие заходит куда-то не туда. Я опасаюсь, что принимаемые изменения приведут язык к тому, что программы на нем будут становиться всё более короткими и всё более малочитаемыми, к тому, что он будет предоставлять всё больше и больше возможностей сделать одно и тоже разными способами и для изучения всех этих способов будет требоваться всё больше и больше времени.
Мне не хочется увидеть через год на месте PHP новый Perl. А вам?