[Перевод] Почему String Templates не будет в Java 23?

От переводчика

Неожиданный поворот в поддержке String Templates в JDK 23. Команда Java решила отказаться от функциональности, которая есть в большинстве современных языках программирования. Почему так произошло? Кажется, из-за слишком большой гибкости, которую заложили на ранних этапах разработки, а также, нежелания просто сделать «синтаксический сахар» для строковой интерполяции. А чего же хотели разработчики на самом деле? Нам кажется, что все-таки — последнего. Сообщество Spring АйО представляет перевод почтовой переписки Гэвина Бирмана и Брайана Гоеца, в которой решилась судьба String Templates.

Гэвин Бирман gavin.bierman at oracle.com

Пт 5 апреля 14:01:54 UTC 2024 

Спасибо за подробную обратную связь на письмо Брайана. Я думаю, будет справедливо отметить, что все еще присутствует широкий диапазон мнений о том, какую в точности форму должна принять эта функциональность.

Пришло время для нас решить, будем ли мы предоставлять эту функциональность в JDK 23. Учитывая, что есть поддержка изменений дизайна API, но отсутствует единство мнений в отношении того, как этот дизайн должен выглядеть, разумными представляются следующие действия: (а) НЕ поставлять текущий дизайн как превью функциональность в JDK 23, (б) потратить достаточное количество времени на продолжение процесса дизайна. Мы все согласны, что наш любимый язык заслуживает того, чтобы потратить столько времени, сколько нужно, на оттачивание нашего дизайна до совершенства! Превью функциональности предназначены как раз для этого — для опробования зрелых дизайнов, прежде чем мы подтвердим их как окончательные. Иногда мы можем изменить своё мнение.
 
Таким образом, для дальнейшей ясности: в JDK 23 не будет String Templates, даже с --enable-preview. 
 
Для тех из вас, кто экспериментирует со String Templates в JDK 22 — пожалуйста продолжайте, и поделитесь своим опытом с нами. Это лучшая форма обратной связи! (Нам не нужны напоминания о том, что делают другие языки — мы уже провели подробное исследование. Но мы ничего не знаем о вашем приложении; попробуйте и, возможно, обнаружите что-то новое. Экспериментируйте и присылайте нам обратную связь — хорошую или плохую.)
 
Спасибо,  
Гэвин 
 

Брайан Гоец brian.goetz at oracle.com

Пт 8 марта 18:35:43 UTC 2024

Время проверить, как обстоят дела со String Templates. Мы прошли два раунда превью и получили некоторую обратную связь.
 
Напоминаю, что основная цель сбора обратной связи — это узнать о дизайне или реализации то, чего мы еще не знаем. Это могут быть отчеты о багах, отчеты об индивидуальном опыте использования, ревью кода, тщательный анализ, новаторские альтернативы и т.д. А лучшая обратная связь обычно поступает от тех, кто использует функциональность «в состоянии злости» — пытаясь реально написать код с ее помощью. («Некоторые люди предпочли бы другой синтаксис» или «некоторые люди предпочли бы, чтобы мы сфокусировались только на интерполяции строки» — все это точно попадает в категорию «вещей, которые мы уже знаем») 
 
В процессе использования этой функциональности в проекте `jextract` мы узнали о многих вещах, неизвестных нам прежде, и они убедили нас поменять наш подход к реализации этой функциональности. В частности, роль процессоров «превышает» предлагаемую ими ценность и, после дальнейшего исследования, мы теперь верим в то, что существует возможность достижения целей данной функциональности без использования явной абстракции «процессора»!  Это очень существенный шаг вперед.
 
Из JEP 459:  

  • Упростить написание программ на Java, облегчив процесс выражения строк, которые включают значения, вычисленные во время выполнения. 

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

  • Улучшить безопасность программ на Java, которые составляют строки из предоставленных пользователем значений и передают их в другие системы (например, составление запросов к базам данных) посредством поддержки валидации и трансформации как темплейта, так и значений, включенных внутрь его выражений. 

  • Сохранить гибкость, позволив библиотекам Java задавать синтаксис форматирования, использованный в строковых темплейтах.

  • Упростить использование API, которые принимают строки, написанные на языках, отличных от Java (например, SQL, XML и JSON). 

  • Разрешить создание нестроковых значений, вычисленных из текстовых литералов и вложенных выражений, не проходя при этом через промежуточное строковое представление.

Не является целями :

  • Не является целью вводить синтаксический сахар для оператора конкатенации строк в Java (+)^ поскольку это позволило бы обходить валидацию, которая входит в список целей. 

  • Не является целью отказываться от поддержки или полностью удалять классы StringBuilder и StringBuffer, которые традиционно использовались для комплексных или программных композиций строк.

Еще одна вещь, которая не изменилась — это наши взгляды на синтаксис для встраивания выражений. В то время как многие люди высказались в духе, «почему бы не сделать то, что делают Kotlin/Scala», этот вопрос был более чем полностью изучен на стадии первоначального дизайна. (По факту, в то время как разногласия по синтаксису зачастую абсолютно субъективны, в данном случае все было довольно ясно — $-синтаксис объективно хуже, и был бы вдвойне плохо, если бы его внедрили в существующий язык, в котором уже существуют строковые литералы. Всё это было рассмотрено ранее, так что я не буду пересказывать это здесь).
 
Теперь давайте поговорим о том, что с нашей точки зрения должно измениться: роль процессоров и тип StringTemplate.
 
Процессоры изначально представлялись как абстракция преобразования темплейтов к их финальной форме (будь это строка или что-то другое). Однако, Java уже содержит устоявшиеся средства абстракции поведения: методы. (По факту, применение процессора может рассматриваться просто как новый синтакс для вызова метода). Наш опыт использования данной функциональности привлек внимание к следующему вопросу: когда мы конвертируем SQL запрос как темплейт в форму, требуемую базой данных (такую как PreparedStatement), почему мы должны писать:  
 
DB.”… template …” 
 
если мы можем использовать стандартную библиотеку Java:  
 
Query q = Query.of("…template…”) ? 
 
В самом деле, одна из худших вещей, связанных с наличием процессоров в языке, состоит в том, что разработчики API поставлены в трудную ситуацию: они не знают, написать ли им процессор или обычный API и зачастую вынуждены делать выбор до того, как последствия этого выбора станут окончательно понятны. (Вдобавок к этому, процессоры поднимают аналогичные вопросы и на стороне использования). Но реальная критика здесь связана с тем, что захват и обработка темплейта объединены вместе, тогда как они должны быть отдельными, последовательно вызываемыми функциями. 
 
Это замотивировало нас пересмотреть некоторые причины, по которым процессоры были так важны на начальном этапе проектирования. И оказалось, что на этот выбор повлияли — возможно, чрезмерным образом — ранние экспериментами с реализацией. (Одной из фоновых целей дизайна было позволить дорогостоящим операциям наподобие String::format стать (намного) дешевле. Без глубокого погружения в производительность,  String::format может быть более, чем на порядок, хуже, чем эквивалентная операция конкатенации, и это в свою очередь иногда мотивирует разработчиков использовать худшие языковые конструкции для форматирования. Процессор FMT привел эту стоимость обратно к показателям, сравнимым с эквивалентной конкатенацией). Эти ранние эксперименты сформировали предубеждение, что необходимо знать процессор в момент захвата темплейта, но после перепроверки мы осознали, что есть и другие методы достижения желаемой производительности, не требующие знания о процессорах в момент захвата темплейта. Это, в свою очередь, позволило нам заново посмотреть на ту точку в процессе дизайна, через которую мы прошли ранее, когда строковые темплейты были лишь «новым видом литерала», а работа, выполняемая процессорами, могла вместо этого быть выполнена обычными API. 
 
В этот момент родился более простой дизайн и реализация, которые отвечали целям по семантике, правильности и производительности: литералы темплейтов ("Hello \{name}”) — это просто литеральная форма StringTemplate:  

StringTemplate st = "Hello \{name}”;

String и StringTemplate остаются несвязанными типами. (Мы исследовали несколько способов преобразования их один в другой, но они создавали больше проблем, чем решали). Обработка строковых темплейтов, включая интерполяцию, осуществляется обычными API, которые имеют дело со StringTemplate, в чем им помогают некоторые умные трюки реализации, гарантирующие хорошую производительность. 
 
Для API, где интерполяция известна как безопасная в данном домене, например PrintWriter, API могут делать выбор от имени домена, предоставляя перегрузку, чтобы воплотить этот дизайн:

void println(String) { … }

void println(StringTemplate) { 
    // … interpolate and delegate to println(String) … 
}

Результат состоит в том, что для API с безопасной интерполяцией, вроде println, мы можем использовать темплейт напрямую, не жертвуя безопасностью:  

System.out.println("Hello \{name}”);

В этом примере строковый темплейт является объектом класса StringTemplate, а не String (отсутствует неявная интерполяция), и выбирается перегрузка println для StringTemplate, которая в свою очередь выбирает, как обрабатывать шаблон. Так мы остаемся верны принципу, что интерполяция достаточно опасна, чтобы быть явным выбором в коде, но мы все еще позволяем библиотекам делать такой выбор, если этого этого хотят.

Аналогично, процессор FMT заменяется перегрузкой String::format, которая интерпретирует шаблоны с встроенными спецификаторами формата (например, «%d»):

String format(String formatString, Object… parameters) { 
    // … same as today … 
}

String format(StringTemplate template) {
    // … equivalent of FMT …
} 

И пользователи могут вызывать это так:

String s = String.format("Hello %12s\{name}”);

Здесь API String::format выбрал интерпретировать строковые шаблоны согласно правилам, ранее указанным в процессоре FMT (не обычная интерполяция), но этот выбор заложен в семантике библиотеки, поэтому на месте использования не требуется дальнейшего явного выбора. Пользователь уже выбрал передать его в String::format; и этого достаточно для выбора обработки.
 
Там, где API не осуществляют выбора относительно того, что означает развертывание темплейта, пользователи продолжают иметь возможность явно обрабатывать их перед передачей, используя API, которые это делают (например, String::format или обычная интерполяция).
 
Результат всего этого следующий:  

  • Необходимость использования «лишнего кода» на месте вызова (ранее это было имя процессора; теперь это статические или экземплярные методы для обработки шаблона) полностью исчезает при работе с библиотеками, которые уже поддерживают темплейты.

  • Даже с библиотеками, которые требуют использования «лишнего кода» на месте вызова, это не становится более навязчивым, чем раньше, и может быть уменьшено со временем по мере адаптации API.

  • StringTemplate — это просто еще один тип, который API могут поддерживать, если хотят. Процессор «DB» становится обычным factory методом, который принимает строковый темплейт или обычный builder API. 

  • API теперь имеют больше контроля над временем обработки и назначением темплейта, так как мы больше не требуем выбирать процессор на этапе создания темплейта.

  • Становится проще абстрагировать обработку темплейта (например, комбинировать или манипулировать темплейтами именно как темплейтами, до их обработки). 

  • Интерполяция остается неявным выбором, но библиотеки, знакомые с ST, могут делать выбор от имени пользователя. 

  • Влияние особенностей языка и интерфейса API становится намного меньше, что хорошо. Core JDK APIs (например, println, format, exception constructors) обновляются для возможности работы со строковыми темплейтами.

Остаётся ответить на вопрос, который, вероятно, сейчас задаст себе каждый: «как же тогда делать интерполяцию?». Ответ: «через обычные библиотечные методы». Это может быть статический метод (String.join(StringTemplate)) или instance метод (template.join()), который должен быть реализован (но давайте, пожалуйста, отложим это на потом). 
 
Это набросок направления, поэтому не стесняйтесь задавать вопросы или оставлять комментарии. Мы обсудим детали по мере продвижения.

6fb526a4b88832cd9ea7dab4a317bc86.png

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Ждем всех, присоединяйтесь

Habrahabr.ru прочитано 2667 раз