На протяжении последних нескольких недель мне довелось использовать Go в работе. Я впервые использовал Go на более-менее крупном и серьезном проекте. До этого я достаточно много читал про Go и практиковался на примерах и небольших программах при изучении возможностей Rust, но реальное программирование — это совсем другое дело.
Я подумал, что вам может быть интересно услышать о моих впечатлениях. Я постараюсь не зацикливаться на сравнениях с Rust, но так как это мой основной язык, их не избежать. Я должен заранее вас предупредить о сильном уклоне в сторону Rust, но я сделаю все возможное, чтобы быть объективным.
Общие впечатления
Программировать на Go приятно. В библиотеках было все, что мне было нужно, без слишком большого количества недоработок. Изучение тоже было приятным опытом — это хорошо продуманный и практичный язык. Как пример, узнав синтаксис, вы увидите, что многие идиомы из других языков переносятся на Go. Как только вы освоите часть Go, можно легко предсказать его возможности. Имея некоторый опыт других языков, я мог без проблем читать и понимать код Go без особой помощи гугла.
Я отметил намного меньше фрустрации и намного больше продуктивности, чем при использовании C/C++, Java, Python и т. д. Однако, Go все еще ощущается частью этого поколения языков. Он извлек из них уроки, и я думаю, что это, вероятно, лучший язык этого поколения; но он определенно часть этого поколения. Он скорее представляет инкрементное улучшение, нежели нечто принципиально новое (следует отметить, что это не оценочное суждение — инкрементность зачастую на пользу в мире разработки программного обеспечения). Хорошим примером этого является nil: такие языки как Rust и Swift избавились от null парадигмы, тем самым устранив целый класс ошибок. Go делает ее менее опасной: нет нулевых значений; разграничение nil и 0. Но основная идея все еще присутствует, как и распространенная рантайм ошибка разыменования нулевого указателя.
Легкость освоения
Go невероятно прост в освоении. Я знаю, что это растиражированный рекламный лозунг, но я был очень удивлен, насколько быстро я смог достичь уровня продуктивности. Благодаря документации, инструментам и самому языку буквально за два дня я начал писать содержательный, пригодный для коммитов, код.
Несколько факторов, в пользу обучаемости:
- Go небольшой. Многие языки стараются быть небольшими, Go же на самом деле является таковым. (В основном это хорошо, и я впечатлен дисциплиной, которая для этого потребовалась).
- Стандартная библиотека хороша (и опять же тоже небольшая). Находить и использовать библиотеки в экосистеме очень легко.
- В языке очень мало того, чего нет в других языках. Go наследует много битов из других устоявшихся языков, полирует их и аккуратно соединяет. Он старательно избегает новизны.
Рутинность кода
Код Go очень быстро становится повторяющимся. В нем отсутствует какой-либо механизм типа макросов или дженериков для сокращения повторений, (интерфейсы хороши для абстракций, но не так хорошо работают для уменьшения дублирования кода). У меня часто накапливается большое количество функций, идентичных за исключением типов.
Обработка ошибок также способствует повторению. Многие функции имеют больше шаблонов
if err != nil { return err }
, чем оригинального кода.Использование дженериков или макросов для сокращения рутинности кода иногда критикуют за компромисс с его читаемостью. Но в случае с Go, я бы с ними не согласился. Копировать и вставлять код легко и быстро, но чтение Go кода может приводить к фрустрации, потому что вы должны игнорировать большую его часть или выискивать тонкие различия.
То, что мне понравилось
- Время компиляции. Определенно быстро; определенно намного быстрее чем Rust. Но на самом деле не так быстро, как я ожидал (мне кажется, что оно на уровне C/C ++ или даже немного быстрее для средних и крупных проектов, а я ожидал чего-то практически мгновенного).
- Горутины и каналы. Облегченный синтаксис для запуска подпрограмм Go и использования каналов — это действительно хорошо. Такая небольшая деталь, делающая параллельное программирование намного приятнее, чем в других языках, действительно показывает силу синтаксиса.
- Интерфейсы. Они лишены изыска, но их легко понять и использовать, и они полезны во многих местах.
if ...; ... { }
синтаксис. Возможность ограничивать область видимости переменных телом if операторов — это хорошо. Это нечто сродни if let в Swift и Rust, но более общего назначения (Go не имеет паттерн матчинга как Swift и Rust, поэтому он не может использовать if let).- Тест и док комментарии просты в использовании.
- Инструмент Go приятен — все сразу в одном месте без необходимости подключения множества инструментов через командную строку
- В наличии сборщик мусора (GC)! Отсутствие необходимости заботиться об управлении памятью действительно делает программирование проще
- Varargs.
То, что мне не понравилось
Порядок значения не имеет.
- nil слайсы — nil, nil слайс и пустой слайс — все это разные вещи. Я более чем уверен, что вам нужны только двое из них, а не все три.
- Никаких первичных классов. Использование констант ощущается непривычно.
- Запрет на циклы импорта. Это действительно ограничивает полезность пакетов предназначенных для модуляризации проекта, поскольку это поощряет набивать большое количество файлов в один пакет (или множество небольших пакетов, что может быть так же плохо, если файлы, которые должны быть вместе, распределены в разные пакеты).
switch
может быть не исчерпывающимfor ... range
возвращает пару индекс/значение. Получить только индекс легко (просто проигнорируйте значение), но получение только значений требует явного указания. Для меня это шиворот-навыворот, так как в большинстве случаев мне нужно значение, а не индекс.- Синтаксис:
- Несоответствие между определениями и использованиями.
- Избирательность компилятора (требующего или запрещающего, например, висящие запятые); в основном это облегчается хорошим набором инструментов, но существует несколько случаев, когда это создает раздражающий дополнительный шаг.
- При использовании возвращаемых типов с несколькими значениями скобки требуются для типа, но не для return.
- Для объявления структуры требуется два ключевых слова (type и struct).
- Использование заглавных букв для обозначения переменных public или private. Это как венгерская нотация, только хуже.
- Неявные интерфейсы. Я знаю, что это так же в списке того, что мне нравится, но иногда это действительно раздражает, например, при необходимости найти все типы, которые реализуют интерфейс, или интерфейсы реализованные для данного типа.
- Вы не можете писать функции с получателем в другом пакете, поэтому, даже если интерфейсы типизированы неявно, они не могут быть реализованы для вышестоящих типов, что делает их гораздо менее полезными.
Я уже упоминал об отсутствии дженериков и макросов выше.
Согласованность
Как разработчик языка и программист, меня, вероятно, больше всего удивило то, что в Go часто встречается несоответствие между тем, что встроено, и тем, что доступно пользователям. Задача многих языков — развеять как можно больше магии и сделать доступными встроенные функции для пользователей. Перегрузка операторов — простой, но противоречивый пример. В Go таится очень много магии! И вы очень легко врезаетесь в стену неспособности делать то, что могут встроенные вещи.
Некоторые моменты, которые особенно выделяются:
- Есть хороший синтаксис для возврата нескольких значений и для каналов, но их нельзя использовать вместе, потому что нет типов кортежей.
- Существует оператор
for ... range
для итерации по массивам и слайсам, но вы не можете перебирать другие коллекции, потому что концепция итераторов отсутствует. - Такие функции, как
len
иappend
, являются глобальными, но невозможно сделать ваши собственные функции глобальными. Эти глобальные функции работают только со встроенными типами. Они также могут быть универсальными, даже если в Go нет дженериков! - Нет перегрузки операторов. Это особенно раздражает при использовании
==
, потому что это означает, что вы не можете использовать пользовательские типы в качестве ключей для map, если они не сопоставимы. Это свойство является производным от структуры типа и не может быть переопределено программистом.
Заключение
Go — простой, компактный и приятный язык. У него есть несколько острых углов, но главным образом он хорошо спроектирован. Он невероятно быстр в изучении и избегает любых функций, которые не известны на других языках.
По сравнению с Rust, Go — это совсем другой язык. Хотя оба они могут быть грубо описаны как системные языки или «замена» для C, они имеют разные цели и приложения, стили языкового дизайна и приоритеты. Сборка мусора — это действительно большая разница. Наличие GC в Go делает язык намного проще и меньше, и его легче понимать. Отсутствие GC в Rust делает его очень быстрым (особенно если вам нужна четкая задержка, а не просто высокая пропускная способность) и обеспечивает возможности и шаблоны программирования, которые невозможны в Go (по крайней мере, без ущерба для производительности).
Go — это компилируемый язык с хорошо реализованной средой выполнения. Он быстр. Rust также компилируемый, но имеет намного меньшую среду выполнения. Он очень быстр. Предполагая, что никаких других ограничений нет, я думаю, что выбор между использованием Go и Rust — это компромисс между гораздо более короткой кривой обучения и более простыми программами (что означает более быструю разработку) и, со стороны Rust, большой скоростью и более выразительной системой типов (что делает ваши программы более безопасными и ускоряет отладку и поиск ошибок).
На этом перевод подошел к концу, а мы приглашаем вас на бесплатное практическое онлайн-занятие, где вы узнаете, как за 60 минут создать полностью протестированный http-сервис с нуля без 3rd-party зависимостей.
ip1981
Лучше читать оригинал. Перевод никакой.