Эффект Монреаля: почему языкам программирования нужен Царь стилей

Kate

Administrator
Команда форума

▍ Проблема Scala​


Для наглядного представления проблемы возьмём Scala. Кстати, один из моих любимых языков. Вот только есть в нём одна большая загвоздка – отсутствие идиоматичности. Он слишком гибок.
Я могу написать один файл, класс Calculator, начав с простого стиля Java:

// Инструкции return, скобки и точки с запятыми
def getResult(): Double = {
return result;
}

def multiply(number: Double): Calculator = {
if (number == 0) {
println("Multiplication skipped: number is 0");
} else {
result = result * abs(number);
}
return this;
}

Теперь в том же классе того же файла я могу переключиться на псевдокод Python:

// Много пустого места, никаких return и точек с запятыми
def add(number: Double): Calculator =
result += abs(number)
this

def subtract(number: Double): Calculator =
result -= abs(number)
this

А теперь я могу вызвать всю эту конструкцию без скобок и точек в стиле предметно-ориентированного диалекта Ruby:

val calc = new Calculator add -5 subtract -3 multiply -2

Весь код.

Надеюсь, никто не пишет такой код. Вы выбираете подходящий диалект Scala и придерживаетесь его. Но по мере разрастания кода ситуация начинает напоминать Монреаль, где каждая часть города своеобразна. И если взять достаточно длинный отрезок времени, в вашем коде проявятся все существующие в этом языке причуды.

В конечном счёте какой-нибудь программист скопирует ваш код и перепишет в другом стиле. Возможно, он ему больше нравится, или это новый сервис, где принято всё делать иначе. А, может, это просто джуниор повторяет стиль библиотеки из документации. Здесь и начинается расхождение стилей.1

На Hacker News в каждой теме Scala есть комментарий от человека, который унаследовал базу кода и не смог в ней разобраться, отчасти из-за того, что она написана в чужом стиле.

▍ Проблема Монреаля в C++​


ms_ynmzzbbgheigjngsshmtsx40.jpeg

Любая крупная база кода становится «городом» с разными стилями

В C++20 было внесено много хороших идей, но значительная часть существующего кода предшествует этому стандарту. Поэтому возникает дрифт. Перед вами есть два пути: избегать использования нового стиля написания или получить базу кода, где стилей станет на один больше. Если пойти вторым путём, то в итоге вы столкнётесь с проблемой Монреаля. Работа с кодом «старого Монреаля» будет подобна работе с другим диалектом. Теперь вам потребуется знать несколько диалектов, а также понимать, когда и где каждый из них применять.

▍ Руководств по стилю недостаточно​


Руководства по стилю, особенно с применением автоматизации, могут существенно упростить задачу в случае крупной базы кода. Я считаю, что это прекрасно, когда языки позволяют экспериментировать. Возможно, мы пока не знаем, имеет ли смысл везде в Python использовать типы, или как часто следует применять в Go дженерики, или что-то ещё.

Но для конкретного проекта можно устанавливать правила. Предположим, «В этом Python нужны типы» или «Используй дженерики в Go там, где возможно». Мы устанавливаем стандарты для тестирования библиотек и создаём инструменты. И мы также пытаемся применить эти правила с инструментами везде, где можем. Но я считаю, что на уровне сообщества языка можно действовать более эффективно.

И здесь будет недостаточно руководств по стилю для конкретных баз кода.

▍ Царь стиля​


Когда в 2006 году вышел Scala 2.0, внутренние предметно-ориентированные диалекты были очень популярны, и на фоне лидирующего тогда Ruby on Rails он предлагал более текучий стиль написания. Но времена меняются, и сегодня этот стиль больше не является идиоматичным для Scala.

Но откуда вам это знать, если вы не вращаетесь в соответствующих кругах? Большие ORM*-фреймворки по-прежнему используют этот стиль в своих руководствах. Тонкости написания современного идиоматичного Scala таятся в умах лидеров сообщества. И это плохо.

*ORM (Object-Relational Mapping, объектно-реляционное отображение)

Нам нужен Царь стилей. Кто-то в сообществе языка, кто сможет сказать, что вот это идиоматичный Scala 2.1:

def ABSOrSeven(maybeNumber: Option[Int]): Int = {
if (maybeNumber.isDefined) Math.abs(maybeNumber.get)
else 7
}

Но в Scala 3.1 предпочтительнее такой вариант:

def ABSOrSeven(maybeNumber: Option[Int]): Int = {
maybeNumber.map(Math.abs).getOrElse(7)
}

Моя идея в том, чтобы каждый релиз языка поставлялся с руководством по стилю. Никто не обязан ему следовать. Компании и проекты могут идти своим путём, и стандарт может быть сильно разрозненным, но он должен существовать и быть где-то зафиксирован.

▍ Идиоматичный Python​


cx4lzjkv2upohy2i9pieovwexmg.png

Развивающийся стандарт может свести многомерное число диалектов в более-менее прямую линию

Python-разработчики любят свой питонический код, Дзен Пайтона и PEP8. Как раз о таком я и думаю, но в постепенно развивающемся виде и в большем масштабе. Я считаю, что разработчикам языков нужно не только их разрабатывать, но и руководить появляющимися в них стандартами программирования. Они должны общаться с нами и говорить: делай так, а не эдак. И это должен быть именно диалог, со спорами и инструментами, которые помогут нам следовать правилам.

И этот стандарт будет постепенно меняться, ведь так? Он будет развиваться параллельно с языком. Возможно, аннотации типов, когда они только появились в Python, считались экспериментальными. А когда все к ним привыкли и стали считать удачной идеей, Python должен занять твёрдую позицию и заявить, что для питонического кода они являются обязательными. По мере роста языка и разветвления его сообщества должна расширяться и область документации стилей.

Вот пример: Python должен выбрать свою линию в отношении пакетных менеджеров и виртуальных сред. Я считаю, что нужно предпочесть Poetry и project.toml, а другие набили себе татуировки requirements.txt-for-life. Но любое решение будет лучше, чем то, которое мы имеем сегодня.

Должен быть кто-то ответственный, кто встанет со словами: «Итак, слушайте все. Мы собираемся стандартизировать использование для пакетов Hatch. Если у кого-то есть вопросы к Hatch, выскажитесь. Мы это учтём. Но имейте в виду, начиная с Python 3.16 Hatch будет стандартом».

И это касается фреймворков для тестирования, стандартных библиотек и даже способов обработки конкурентности. Языковые сообщества любят экспериментировать и исследовать. Но после исследования нам нужно объединяться. И именно здесь должны подключаться создатели языка. Только они могут сделать это реальным.

▍ Экспрессивность​


Хорошо, а как всё это привязать к экспрессивности? Если ваш язык ориентирован на экспертов, то в нём наверняка уже есть много возможностей, и вы не боитесь добавлять новые. Проблема в том, что вы постепенно окажетесь в мире, где каждая база кода написана на собственном подмножестве языка. Поэтому против некоторых вещей нужно возражать. Естественно, необходимо оставить все старые возможности для совместимости, но давайте подталкивать всех к общему стандарту.

Если вы развиваете язык, значит, у вас есть мнение о том, как должен выглядеть его идеальный код. Скажите нам! Запишите. Поделитесь с сообществом. Например: «Использование макросов в C++20 не считается идиоматичным», «Использование if для проверки типов возвращаемого объекта в Kotlin 1.17 не идиоматично», «Не используйте в Scala явные инструкции return». И так далее.

В этом случае у нас в сознании всегда будет присутствовать целевой образ хорошего кода. И даже если этот образ изменится, любая адекватная база кода лишь находится в определённой точке этого пути к самому продуманному и великому стилю.

Иными словами, вы можете оказаться в мире, где весь идиоматичный код Java 20 такой же единообразный, как Go. Но для того, чтобы в этот мир попасть, потребуется однозначно решить, когда можно использовать Streams API, а когда нет. Я имею в виду, что вы можете никогда окончательно в него и не попасть, поскольку понятный однострочный обработчик потока у одного станет спагетти-кодом у другого. Но я считаю, что мы можем более единогласно сойтись на том, каким хотим видеть язык.

▍ Конец бутербродных войн


Всё это поднимает немало вопросов. Когда следует канонизировать популярную библиотеку? Какой объём руководств по стилю будет слишком большим и начнёт сдерживать развитие? Какую часть этих нововведений можно внедрить с помощью инструментов? У меня ответа нет. Нужно просто с чего-то начать, например с версии Python PEP8, и постепенно продвигаться.

Если что-либо является нормой для сообщества, записать это. Если сообщество спорит о том, как правильно есть бутерброд – маслом вниз или вверх – нужно просто подбросить монету, принять решение и двигаться дальше. Сообществу это пойдёт на пользу.

Покажи нам путь, о Царь стиля!

▍ Сноска​


1. Это лишь простой пример. Если бы мы были царями стиля, то да, правилом бы было «Не использовать явные return или точки с запятыми». А также «Не сопоставляйте с образцом (pattern matching), если можете использовать fold или map» и «Не используйте fold, если можете использовать getOrElse», и «Не выполняйте ручную рекурсию», и «Не используйте акторов без действительно веской причины», и «Не пишите кастомные операторы. Просто не пишите», и так далее. Многие возможности оказываются ценными только в конкретных обстоятельствах.

 
Сверху