Философия языка Go¶
«Не чужд ему был и некоторый скептицизм. В одной из его лабораторий висел огромный плакат: "Нужны ли мы нам?"».
А. и Б. Стругацкие, «Понедельник начинается в субботу».
Порой во введении к пособию по изучению какого-либо языка программирования (особенно, если язык достаточно сложен, например, если это C++ или Haskell), автор пытается воодушевить и мотивировать читателя рассуждениями о «философии» данного языка или даже о его «особом пути».
На мой же взгляд, каждый язык программирования в меру своих сил решает одну и ту же задачу — оптимизацию суммы компонентов «скорость выполнения + размер занимаемой памяти + когнитивная нагрузка на разработчика» применительно к различающимся конкретным обстоятельствам. Например, связка C/C++ и ассемблерных вставок (проекты на чистом ассемблере сейчас встречаются редко) позволяет максимизировать скорость, минимизировать размер, но зачастую трудна для сопровождения, модификации и передачи информации о проекте новым членам команды. У Python'а относительно пологая кривая обучения (по крайней мере, на начальном участке), но скорость и размер необходимой памяти пока оставляют желать лучшего. У каждого языка из верхней двадцатки TIOBE Index или PYPL Index есть свои чёткие преимущества, свои «фишки», позволяющие ему сохранять паритет в конкурентной борьбе.
Всё в мире динамично: с одной стороны, C++ активно овладевает нововведениями, позволяющими отойти от «сырых» указателей и минимизировать борьбу с утечками памяти; с другой стороны, разработчики Python целенаправленно работают над повышением быстродействия языка. Завтра равновесие может сместиться в ту или иную сторону, но, на мой взгляд — прямо здесь и прямо сейчас язык Go максимально близок к оптимуму по соотношению «скорость, размер, сложность».
Какой бы язык вы не считали своим основным, я бы рекомендовал вам присмотреться к Go, «пощупать» его, написать пару консольных утилит. Возможно, новый инструмент в вашей обойме облегчит работу с задачами, ранее дававшимися нелегко, и даст больше возможностей для маневрирования способами решения бизнес-задач, а не попытками протолкнуть алгоритм через слои синтаксического сахара или многочасовую пошаговую отладку.
Моими рабочими языками программирования в разные годы были ассемблер, C, C++, C# и Python. Естественно, перед освоением Go я заинтересовался его сравнением с моими предыдущими языками. Ниже я привёл несколько цитат с Reddit'а, показавшихся мне весьма уместными, из топиков, посвященных обсуждению «Go vs какой-нибудь другой язык программирования», которые, возможно, помогут вам определиться с местом Go в вашем личном инструментарии. На мой взгляд, это гораздо полезнее, чем невдумчивое акцентирование сильных сторон Go.
Обычно я рекомендую следующее:
Вы занимаетесь AI/ML? Берите C, C++, Python, R или, в крайнем случае, MATLAB.
Пишете фронтэнд? Берите React, VueJS, TypeScript.
Десктопное приложение для Windows? Однозначно C#.
Вы пишете модуль для ядра Linux? Rust.Всё остальное? Выбирайте Go! Ну или Lua, в зависимости от специфики задачи.
Go более многословный, но всё равно процесс разработки оказывается более легким и простым.
C#, когда мне приходится возвращаться к разработке на нём, просто высасывает из меня все соки. Слишком уж много абстракций и «магии». Я, как и многие, предпочитаю однозначно и явно читаемый код, даже если он немного более многословен, по сравнению с, возможно, более кратким, но абстрактным кодом.
Кроме того, хотя это и не свойственно языкам, .NET как экосистема и культура склоняется к гипертрофированной абстракции — такие паттерны, как IOC и DI, являются нормой практически для любого приложения и механизмом обеспечения изолированного тестирования компонентов.
Go больше соответствует «философии Unix», помогая создавать одноцелевые сущности, которые компонуются для создания более сложных проектов. Это облегчает процесс разработки, позволяет избежать ненужной сложности и лучше читается, но при этом масштабируется так же хорошо, а то и лучше, поскольку масштабируемость кода — это, по большой части, читаемость.
Кроме того, в .NET есть множество способов сделать одно и то же, и множество функций, которые являются незначительными вариациями одного и того же; например, классы, записи, структуры, типы ссылок и типы значений — там, где в Go есть типичные стандартные типы — абсолютно все из которых являются типами значений. В нем также есть типы-указатели, которые позволяют передавать указатели на любой из типов значений. И хотя в Go вам нужно разобраться с указателями, понять это не сложнее, чем byref/val и т. д. в .NET.
Аналогично с параллелизмом: в .NET у вас есть native threads, threadpool, task parallel library и async/await. В Go у вас есть стандартные функции и ключевое слово (go), которое делает функцию параллельной, если вы вставите его перед вызовом — вот и всё.
Циклы — в Go у вас есть for, и только. В .NET есть for, while, foreach, do, parallel.for, IEnumerable.ForEach и так далее.
Что касается fields, то в Go у вас есть просто fields. В .NET у вас есть fields, inline properties, lambda properties, и getter/setter properties.
Я мог бы продолжить, но уверен, что вы поняли мою мысль. Подведем итог: я люблю Go. Я нахожу .NET гигантской солянкой из различных идей, подходов и паттернов — хотя они иногда добавляют немного синтаксического сахара по отдельности, в целом они существенно увеличивают когнитивную нагрузку при разработке.
О, позвольте мне высказаться. Ниже упорядоченный список моих претензий.
C# — это огромный язык. С дюжиной способов сделать практически любую операцию, с постоянным беспричинным потоком новых возможностей каждый год. Это превращает работу с C# в сплошную боль, потому что на всё, что вы гуглите, находится 3-4 разных ответа, которые зачастую не работают с последней версией .NET, или, наоборот, работают только в последней версии .NET.
С этим связано то, что модель наследования в C# очень сложна и почти всегда приводит к слишком сложному для понимания коду. Слишком много ключевых слов для определения поведения класса, и чтобы уследить за всем этим, требуется слишком много когнитивных затрат.
Использование statements — это отвратительно. Серьезно, я не знаю, кому пришла в голову эта идея. Тот факт, что я могу заглянуть в файл и не понять, откуда берутся многие переменные верхнего уровня или методы, меня постоянно расстраивает. Я знаю, что есть способы принудительного использования пространств имен с помощью операторов using, но это встречается довольно редко и, скорее всего, будет неожиданностью в любой командной среде. Кроме того, неявные using в C# 10 до сих пор вызывают у меня душевную боль.
Явные интерфейсы. Это скорее моё личное предпочтение, но мне кажется, что на C# очень сложно писать действительно гибкий код, потому что приходится использовать явные интерфейсы, а не неявные, как в Go. Когда я хочу использовать несколько интерфейсов в C#, мне всегда приходится искать свой уникальный подход и пытаться придумать аккуратный способ реализации.
Я ненавижу Visual Studio. Это на 100 % личное предпочтение, но я ненавижу Visual Studio. Эта IDE медленная, неуклюжая, с плохим пользовательским интерфейсом, и как человек, использующий CLI для большинства вещей, я просто не могу сделать то, что мне нужно, не нажав 10 кнопок в Visual Studio. Я бы предпочел использовать VS Code, но интеграция с .NET до сих пор очень неудачна, я считаю ее практически непригодной для использования и на моем Mac, и на моем рабочем ноутбуке с Windows. А если вы не используете Visual Studio, то CLI-ориентированный инструментарий оставляет желать лучшего, особенно если вы делаете что-то связанное с .NET Framework. Я также думаю, что отладчик в VS позволяет разработчикам лениться и не реализовывать толковое логгирование для своего приложения, так что оно будет периодически падать в продакшене без вывода какой-либо полезной информации. В процессе разработки они просто полагаются на отладчик для решения всех проблем (по крайней мере, таков мой опыт с моей командой).
В целом, я не согласен с идеей, что для использования языка программирования, по сути, требуется собственный редактор. Меня это очень раздражает. Это была главная причина, по которой я выбрал Go для своих не основных клиентских проектов.
Рантайм .NET раздражает. Я много работал на Python и Javascript, и одна из главных вещей, которая мне не нравилась — это управление развертыванием. Конечно, развёртывание .NET в большинстве случаев проще, чем Python или JS, но все равно это бельмо на глазу — убедиться, что .NET правильно установлен и имеет нужную версию, которая совместима с версией на машине разработчика. Возможность создать статичный двоичный файл, который можно запустить практически в любом месте — вот что реально меняет правила игры. Это так освобождает, что я чувствую, что каждый программист должен изучать язык, который может компилироваться в статичные бинарники.
Плохая обработка ошибок по умолчанию. Это не специфично для C#, скорее общий паттерн. Исключения почти никогда не обрабатываются должным образом. Мне реально нравится, что в go ошибки очень хорошо видны в коде, и если вы хотите проигнорировать ошибку, то обязаны сделать это явно.
Async/await — это самая настоящая ловушка C#. В коде, с которым я работаю, я редко вижу корректную реализацию async/await. Я считаю, что C# не очень хорошо справляется с управлением параллелизмом в явном виде. 90% async-кода, не связанного с веб, который я вижу, просто блокируется в другом потоке, что, наверное, хорошо, но никто из тех, кого я спрашиваю, на самом деле не думает, что это то, что он делает.
Слишком много «магии». Магия — это, конечно, дурацкое слово в программировании, но я не знаю, как еще это назвать. Каждый раз, когда я запускаю новый проект для ASP.NET, я начинаю путаться в том, как я должен все делать. Здесь так много встроенных настроек, что очень трудно двигаться вперед. Приходится просто читать документацию и искать все способы использования фреймворка для инъекции зависимостей и прочего. Каждый раз, когда я хочу выйти за пределы протоптанного пути, я чувствую сопротивление, и я никогда не могу толком понять, как я заставил эту штуку работать.
Некоторые причины, по которым мне очень нравится Go:
очень быстрое время компиляции;
принудительные пространства имен пакетов;
единый бинарный дистрибутив;
встроенный веб-сервер производственного уровня;
простой параллелизм;
самое простое тестирование, которое только можно представить;
встроенные инструменты оценки производительности;
встроенное стандартное форматирование;
достаточно простой язык — синтаксис можно выучить за неделю или две;
очень мало «магии» или неявного поведения — если язык что-то делает, то только потому, что я сказал ему это сделать;
мое общее ощущение при написании кода — язык поощряет вас быть простым и понятным, а не умничать;
семантика указателей помогает избегать побочных эффектов.Я нисколько не жалею о том, что изучил Go. На работе мне довелось написать 2-3 CLI-инструмента и несколько небольших веб-проектов на Go, и мои коллеги всегда удивлялись тому, как быстро я смог выдать рабочий код и как быстро работает приложение. Это отличный инструмент, который нужно иметь в виду, даже если это не ваш основной язык.
После изучения и использования Go я бы больше никогда не рассматривал Python для проектов любого типа; и совершенно определенно он подходит для консольных приложений и для бэкенд API и микросервисов.
TypeScript — для веб-приложений, Python — для численного анализа и ML. Go для высокопроизводительных приложений, экономящих память, и для микросервисов. Выбирайте правильный инструмент.
Если ваш основной сценарий работы — микросервисы, которые могут брать большую часть данных из кэша, то используйте Python; вы сможете двигаться быстро, не переживая, что производительность станет узким местом.
Если же ваши приложения сложны и транзакционны по своей природе, выбирайте Golang.
Если ваше приложение используют большое количество пользователей и вы фиксируете большое количество транзакций, выбирайте Golang, так как он позволит вам сэкономить на счетах за облако и при этом даст прогнозируемый коэффициент отказоустойчивости.
Python и, в меньшей степени, Golang таят в себе множество сюрпризов, которых можно избежать, хорошенько обложив проект тестами.
Я, по правде сказать, большой поклонник языка Go. Я говорю это как человек, который начал с написания игр на языке ассемблера (потому что в то время особого выбора не было) примерно 35-40 лет назад. Когда я закончил колледж и начал профессиональную карьеру, я перешел на C, затем на C++, потом на Java, за которой быстро последовал C#. Затем я перешел на руководящую должность. Именно тогда я открыл для себя Go и полюбил его за возможности, которые он дает при разработке бэкенд-сервисов. Golang — это, можно сказать, язык C, который вынес уроки из всех остальных языков, которые я когда-либо использовал.
Если вы любите решать проблемы алгоритмически, то Go для вас. Если вам нравится параллелизм, то Go для вас. Если вам нравится создавать быстрые бэкенд-сервисы, то Go для вас.
До перехода на Go я много писал на Python, сейчас же я Python почти не использую. Для производственного кода я никогда не выберу язык с динамической типизацией, это грозит слишком большим количеством возможных ошибок. Некоторые считают, что Python сильно выигрывает от аннотаций типов, но если вы собираетесь аннотировать типы, вы должны получить от этих аннотаций кроме предупреждающих сообщений еще и выигрыш в производительности, чего Python дать не может. Упаковка в Python тоже оставляет желать лучшего, в то время как проект Go компилируется в один бинарник. Go быстрее, безопаснее и проще в развертывании.
Я буду немало удивлен, если Python когда-либо будет смещен с пьедестала в качестве языка для задач типа «сделай это быстро!». Это не значит, что у Python нет более полезных приложений — но Python превосходит любой другой язык по соотношению пользы к количеству строк кода.
Но вот что выбрать для распределенных систем, API, микросервисов? Просто привыкайте к Go. Язык Go изначально был спроектирован для решения подобных задач, и спроектирован хорошо; и хотя вы не сможете быстро штамповать код, как это помогает делает Python, но в долгосрочной перспективе Go-код будет оставаться стабильно управляемым, в то время как Python с ростом сложности может начать спотыкаться и усложнять отладку, модификацию и расширение.
И Python, и Go на данный момент являются двумя шикарными языками программирования, поэтому любой из них будет хорошим выбором.
Если вы будете интересоваться популярностью языков программирования в сабреддите Python, ответом будет, конечно же — «Нужно больше Python'а!», а в сабреддите Go — «Даёшь больше Go!».
Мы же проектируем сложные, нагруженные системы и лично я заинтересован в том, чтобы члены моей команды хорошо владели более, чем одним языком программирования; и что еще более важно — более, чем одной экосистемой и более, чем одним, подходом к разработке.
Я считаю себя программистом-полиглотом, и каждый раз, когда у меня появляется возможность профессионально использовать другой язык, я чувствую, что это укрепляет мои основные навыки.
Все языки программирования что-то дают, просто некоторые из них лучше подходят для разных задач в зависимости от квалификации команды, технических задач, бюджета, времени и т. д. и т. п.
Мой вам совет — уделите Go немного времени.
На работе я использую Go (чаще) и Python (реже). Всего за свою карьеру я использовал около десяти языков программирования, и твердо убежден, что работа с разными языками улучшает вашу квалификацию. Это же, естественно, расширяет ваши возможности при поиске работы: лучше кодер — больше выбор.
Мой совет — обязательно изучайте Go, но не забывайте Python и используйте их оба. У них разные области оптимального приложения. Сильной стороной Python, например, является скорость разработки приложений, нацеленных на machine learning.
И вообще, не ищите беспристрастных ответов на вопрос «стоит ли мне переходить на X» в сабреддите, посвященном X.