Хабрахабр

[Перевод] За что я не люблю Go

На нынешней работе приходится использовать Go. Я хорошо познакомился с этим языком. Мне он не нравится, и меня озадачивает его популярность.

Эргономика разработки

К примеру, Роб Пайк неоднократно и открыто враждебно относился к любому обсуждению подсветки синтаксиса на Go Playground. Никогда не встречал языка, настолько открыто противостоящего удобству для разработчика. В ответ на разумно сформулированные вопросы пользователей его публичные ответы отсвечивали пренебрежением и неуважением:

К сожалению, это никак не повлияло на количество бессмысленных дискуссий о подсветке синтаксиса или, как я предпочитаю её называть, spitzensparken blinkelichtzen. Gofmt написан специально чтобы уменьшить количество бессмысленных дискуссий о форматировании кода, что отлично удалось.

И снова в ветке 2012 Go-Nuts:

В детстве меня учили арифметике на цветных палочках. Подсветка синтаксиса — для маленьких. Сейчас я вырос и использую чёрно-белые цифры.

Ясно, что из знакомых Роба никто не страдает от синестезии, дислексии или плохого зрения. Из-за его позиции официальный сайт и документация Go до сих пор остаются без подсветки синтаксиса.

В обсуждении типов union/sum пользователь ianlancetaylor отклонил запрос, конкретно определяющий преимущество эргономики, как слишком незначительный и не достойный внимания: Группа разработчиков Go не ограничивается Пайком, но остальные всячески поддерживают его отношение к эргономике.

Тогда мы пришли к мнению, что типы sum не особо расширяют интерфейсные типы. Это обсуждалось несколько раз в прошлом, в том числе до открытого релиза. Это довольно небольшое преимущество, чтобы менять язык. Если разобраться, в итоге всё сводится к тому, что компилятор проверяет, что вы заполнили все случаи переключения типов.

Такое отношение расходится с мнением о типах union в других языках. В 2000 году JWZ критиковал Java:

(Например, компилятор не имеет возможности выдать спасительное предупреждение, что «`enumeration value x', не обработано в switch»). Также я думаю, что для моделирования enum и :keywords используются довольно ламерские идиомы.

Команда Java приняла такую критику близко к сердцу, и теперь Java может выдать это предупреждение для операторов множественного выбора по типам перечисления. Другие языки — в том числе и современные языки, такие как Rust, Scala, Elixir и им подобные, а также собственный прямой предок Go, язык C — тоже выдают предупреждения, где это возможно. Очевидно, что такие предупреждения полезны, но для команды Go комфорт разработчика недостаточно важен и не заслуживает рассмотрения.
Нет, я не про интриги в списках рассылках и на встречах. Вопрос более глубокий и интересный.

Он воплощает определённый набор представлений, как должно быть написано и организовано программное обеспечение. Как и любой язык, Go является инструментом политики. В случае Go воплощается чрезвычайно жёсткая кастовая иерархия «опытных программистов» и «неквалифицированных программистов», навязанная самим языком.

Здесь нет универсальных дженериков, нельзя писать функции более высокого порядка, которые обобщают более чем один конкретный тип, и чрезвычайно строгие правила о наличии запятых, неиспользуемых символов и других недостатках, которые могут возникнуть в обычном коде. Со стороны неквалифицированных программистов язык запрещает функции, которые считаются «слишком продвинутыми». 4. Программисты Go живут в мире, который ещё более ограниченный, чем Java 1.

Реализация языка содержит родовые функции, которые нельзя использовать на практике, и с отношениями типов, которые язык просто не способен выразить. Опытным программистам доверяют эти функции — и они могут отдавать системы на таком коде коллегам по обе стороны барьера. Это мир, в котором живут разработчики программ на Go.

Не знаю как внутри Google, но за её пределами это негласное политическое разделение программистов на «благонадежных» и «неблагонадёжных» лежит в основе многих рассуждений о языке.

Пакетный менеджер go get разочаровывает своим отказом от ответственности. Границы пакетов — это место для коммуникации между разработчиками, а команда Go принципиально отказывается помочь.

Достаточно вспомнить катастрофическую историю попыток управления пакетами для библиотек C и посмотреть на Autotools — пример того, как долго может сохраняться столь бедственное положение. Я могу уважать позицию команды Go, которая заключается в том, что это не их проблема, но тут их действия невыгодно отличаются от других основных языков. С учётом этого весьма удивительно наблюдать, что команда разработчиков языка в 21 веке умывает руки в такой ситуации.

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

Опять же, ответ команды Go «не наша проблема» разочаровывает и расстраивает.

Стандартный подход Go к действиям, которые могут завершиться ошибкой, включает в себя возврат нескольких значений (не многокомпонентного объекта; в Go таких нет) с типом последнего значения error в виде интерфейса, где значение nil означает «ошибки нет».

Не существует обобщённого типа, представляющего результат потенциально ошибочной операции, над которым можно писать полезные объединяющие функции. Поскольку это негласное соглашение, оно не представлено в системе типов Go. Более того, он не всегда соблюдается: ничто, кроме здравого смысла, не мешает программисту вернуть error в каком-то ином виде, например, в середине последовательности возвращаемых значений или в начале — поэтому методы обработки ошибок тоже чреваты проблемами.

В Go невозможно составить потенциально ошибочные операции более лаконичным способом, чем что-то такое:

a, err := fallibleOperationA()
if err != nil { return nil, err
} b, err := fallibleOperationB(a)
if err != nil { return nil, err
} return b, nil

В других языках это можно сформулировать как

a = fallibleOperationA()
b = fallibleOperationB(a)
return b

в языках с исключениями или

return fallibleOperationA() .then(a => fallibleOperationB(a)) .result()

в языках с соответствующими абстракциями.

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

a, err := fallibleOperationA()
if err != nil { return nil, err
} if err := fallibleOperationB(a); err != nil { return nil, err
} c, err := fallibleOperationC(a)
if err != nil { return nil, err
} fallibleOperationD(a, c) return fallibleOperationE()

Да поможет вам Бог сделать вложение или что-то кроме передачи ошибки обратно на стек.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть