Красота Go lang

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

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

Есть еще две особенности языка, которые делают его идеальным вариантом для разработки современных систем. Я на них остановлюсь подробнее в разделе статьи под названием «Сильные стороны».

Одна из них — первоклассная поддержка конкурентности (concurrency) (с помощью горутин (goroutines) и каналов, рассмотрено ниже). Конкурентность по своему определению позволяет эффективнее использовать всю доступную мощь CPU, даже если у процессора всего одно ядро. В Go на одной машине могут одновременно выполняться сотни тысяч горутин (легковесных потоков). Каналы и горутины крайне важны при построении распределенных систем, поскольку они абстрагируют механизмы, связанные с передачей сообщений в рамках концепции поставщика-потребителя (producer-consumer messaging paradigm).

Еще в Go мне очень нравятся интерфейсы. Интерфейсы позволяют создавать систему на основе слабосвязанных либо совершенно не связанных компонентов. Таким образом, ваш код может положиться на интерфейсный тип, особо не заботясь о том, кто и как этот интерфейс реализует. Контроллер предоставляет этому коду соответствующую интерфейсу (реализующую всего его функции) зависимость. Это позволяет создавать хорошую архитектуру для юнит-тестирования (с помощью внедрения зависимостей). Контроллер может внедрить (inject) имитацию реализации требуемого в коде интерфейса, что даст возможность протестировать правильность его работы.

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

В этой статье я планирую поговорить о следующих аспектах языка:

  • Введение.
  • Зачем понадобился Go.
  • Целевая аудитория.
  • Сильные стороны Go.
  • Слабые стороны Go.
  • Движемся к Go 2.
  • Философия Go.
  • С чего начать.
  • Кто использует Go.

Введение

Go — это язык с открытым исходным кодом, созданный в Google Робертом Гризмером (Robert Griesemer), Робом Пайком (Rob Pike) и Кеном Томпсоном (Ken Thompson). Открытость исходного кода в данном случае означает, что каждый может принять участие в развитие языка, внося предложения по разработке новых функций, исправляя ошибки и т. д. Код языка доступен на GitHub. Документацию об участии в разработке можно найти здесь.

Зачем понадобился Go

Авторы говорят, что основной причиной создания нового языка было желание решить возникающие в Google проблемы, связанные с разработкой программного обеспечения. Также они упоминают, что Go был разработан в качестве альтернативы C++.

Вот что говорит Роб Пайк о предназначении Go:

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

Проблемы Google, подтолкнувшие к созданию Go (взято с https://talks.golang.org/2012/splash.article):

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

Чтобы Go преуспел, он должен решить эти проблемы (взято с https://talks.golang.org/2012/splash.article):

  • Go должен работать на больших масштабах, для крупных команд разработчиков, для программ с большим количеством зависимостей.
  • Go должен быть знакомым, условно говоря, Си-подобным. Гуглу нужно, чтобы программисты могли быстро начать продуктивно работать с новым языком, поэтому он не должен быть слишком радикальным.
  • Go должен быть современным. Ему нужны такие возможности, как конкурентность, чтобы программисты могли эффективно использовать многоядерные машины. В нем должны быть встроенные библиотеки для работы с сетью и в качестве веб-сервера.

Целевая аудитория

Go — это язык для создания систем. Он замечательно подходит для облачных решений (веб-серверов, кэшей), микросервисов и распределенных систем (за счет поддержки конкурентности).

Сильные стороны

Статическая типизация

Go является языком со статической типизацией. Это значит, что уже на этапе компиляции необходимо объявить типы всех переменных, аргументов функций и возвращаемых значений. Такой подход может показаться неудобным, однако это на самом деле преимущество, поскольку многие ошибки будут найдены во время компиляции. Важность этого фактора растет вместе с размером команды, так как декларирование типов делает библиотеки и функции более читаемыми и лучше понимаемыми.

Скорость компиляции

Написанный на Go код компилируется очень быстро, так что больше не нужно ждать завершения компиляции ? На самом деле команда go run запускает программу так быстро, что сначала даже кажется, что компиляции не было вообще, как будто мы работаем с интерпретируемым языком.

Скорость исполнения

Код на Go компилируется напрямую в машинный код, который зависит от используемой ОС (Linux/Windows/Mac) и архитектуры CPU машины (x86, x86–64, ARM и т. д.), на которой производится компиляция. Поэтому Go-программы работают действительно очень быстро.

Переносимость

Поскольку программа компилируется напрямую в машинный код, бинарные файлы являются переносимыми в рамках одной операционной системы и архитектуры.

Такая возможность появляется за счет того, что бинарные файлы Go слинкованы статически, то есть все необходимые программе разделяемые библиотеки включены в бинарный файл на этапе компиляции. Динамически во время работы программы они не линкуются.
Это удобно при развертывании программ на большом количестве машин в дата-центре. Бинарник можно просто скопировать (например, с помощью scp) на любое количество машин, если, конечно, он был скомпилирован для ОС и архитектуры целевой системы. Вам не нужно заботиться об установленной версии Linux и управлять зависимостями. Программа просто запускается и работает ?

Конкурентность

В Go реализована высококлассная поддержка конкурентности, что является одним из важнейших преимуществ этого языка программирования. Механизмы реализации конкурентности Go основаны на работе Тони Хоара (Tony Hoare) ‘Communicating Sequential Processes’.

Среда исполнения Go позволяет выполнять сотни тысяч конкурентных горутин на одной машине. Горутина — это легковесный поток выполнения. Go умеет мультиплексировать горутины в потоках ОС. То есть несколько горутин могут конкурентно выполняться в одном потоке операционной системы. В среде исполнения Go предусмотрен специальный планировщик, в задачу которого входит управление выполнением горутин.

У такого подхода есть два преимущества:

  1. При инициализации горутина занимает в стеке лишь 4 KB. Это мизер по сравнению, например, с 1 мегабайтом, который обычно выделяется потоку операционной системы. Это число обретает значимость при необходимости конкурентного выполнения сотен тысяч различных горутин на одной машине. Если для этих целей использовать потоки ОС, оперативная память может очень быстро закончиться.
  2. Go мог пойти по тому же пути, что и другие языки, например Java, в которой используются потоки операционной системы. Однако в этом случае цена переключения контекста между потоками ОС гораздо выше по сравнению с ценой переключения контекста между различными горутинами.

Поскольку я в этой статье уже неоднократно упоминал «конкурентность», рекомендую посмотреть видео, в котором Роб Пайк говорит о том, что конкурентность и параллелизм — это разные вещи. В программировании под конкурентностью понимают набор независимо выполняющихся процессов, когда как параллелизм — это одновременное выполнение независимых и/или зависимых вычислений. На одном ядре параллелизм недоступен, поскольку оно может выполнять только одну операцию в единицу времени. Однако конкурентность в этом случае все же возможна. Планировщик операционной системы запускает различные процессы (на самом деле потоки, так как у каждого процесса есть как минимум основной поток) в различные кванты времени (time slices). Таким образом, в единицу времени процессор занят только одним потоком. Благодаря высокой скорости выполнения операций нам кажется, что одновременно выполняются несколько программ. Но по факту проходят все под одному.

Конкурентность позволяет иметь дело с несколькими вещами одновременно, а параллелизм — делать несколько вещей одновременно.

Интерфейсы

Интерфейсы позволяют создавать слабосвязанные системы (loosely coupled systems). В Go интерфейсный тип может быть определен в виде набора функций. И это все. Любой реализующий эти функции тип неявно реализует и интерфейс, то есть вам не нужно указывать, что ваш тип реализует определенный интерфейс. Это сделает компилятор во время компиляции.

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

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

Сборка мусора

В отличие от C, в Go не нужно освобождать динамически выделенную память и беспокоиться о висячих указателях (dangling pointers). О них позаботится сборщик мусора.

Отсутствие исключений

Обрабатывайте ошибки самостоятельно. Меня очень радует тот факт, что в Go, в отличие от многих других языков, нет стандартного механизма работы с исключениями. Разработчикам здесь приходится самостоятельно обрабатывать ошибки типа ‘couldn’t open file’ — возможности обернуть код в блок try catch нет. То есть программистов подталкивают к тому, чтобы задумываться, каким образом эти ошибки надо правильно обрабатывать.

Замечательный набор инструментов

Одна из самых приятных сторон Go — это его инструменты:

  • Gofmt автоматически форматирует код и расставляет отступы. Этим можно добиться того, чтобы код на Go выглядел одинаково у всех без исключения пишущих на этом языке разработчиков, значительно повышая таким образом читабельность кода.
  • Go run компилирует и выполняет код. Да, программы на Go нужно компилировать, но это происходит так быстро, что возникает ощущение работы с интерпретируемым языком.
  • Go get загружает библиотеку с GitHub и копирует ее в GoPath, после чего она может быть импортирована в проект.
  • Godoc парсит исходный код на Go, включая комментарии, и на выходе выдает документацию в простом текстовом или HTML-формате. Веб-интерфейс godoc позволяет увидеть вместе документацию и код, где, например, от документации по функции к ее реализации можно перейти в один клик.

Эти и другие инструменты можно найти здесь.

Замечательные встроенные библиотеки

В Go в есть следующие встроенные библиотеки, которые призваны облегчить жизнь современного разработчика:

  • net/http предоставляет реализации HTTP-клиента и сервера,
  • database/sql предназначена для взаимодействия с SQL-базами данных,
  • encoding/json обеспечивает первоклассную поддержку JSON,
  • html/templates — библиотека для работы с HTML-шаблонами,
  • io/ioutil — вспомогательные функции ввода/вывода.

Библиотеки и фреймворки для всевозможных сценариев использования можно найти здесь.

Слабости

Отсутствие дженериков

Дженерики позволяют создавать алгоритмы с использованием типов, которые будут указаны позже. Предположим, что вам надо написать функцию для сортировки целых чисел. Затем вам понадобилась функция для сортировки списка строк. В этот момент вы понимаете, что код этих функций будет практически одинаковым, но первую функцию вы использовать не можете, поскольку в качестве аргумента она принимает список целых чисел и список строк получать откажется. Ничего не поделаешь, придется дублировать код. Будь в Go дженерики, можно было бы написать функцию сортировки списка элементов типа T и вызывать одну и ту же функцию для целых чисел, строк и любых других типов, для которых существует функция упорядочивания (ordering function). То есть компилятор должен уметь сравнивать значения этого типа.

В Go существует возможность сделать что-то похожее на дженерик с помощью пустого интерфейса (interface {}). Но это решение не лишено недостатков.

Дженерики — тема весьма противоречивая. Есть программисты, которые молятся на них, когда как другие не хотят включать дженерики в язык, поскольку они представляют собой компромисс между временем компиляции и временем выполнения программы.

Создатели Go говорят, что они открыты для диалога о реализации механизма дженериков в Go. Но дело не только в дженериках. Они могут быть реализованы в языке только в том случае, если будут хорошо взаимодействовать с другими его компонентами. Давайте дождемся Go 2 и посмотрим, будет ли в нем какое-либо решение этой проблемы.

Отсутствие управлениями зависимостями

«Обещание Go 1» (Go 1 promise) позволяет быть уверенным в том, что сам язык Go и его библиотеки не будут менять свои API в течение времени жизни Go 1. Ваш код должен компилироваться и в Go 1.5, и в Go 1.9. Большинство сторонних библиотек следуют этому обещанию. Поскольку вы загружаете сторонние библиотеки с GitHub с помощью go get, выполнив go get github.com/vendor/library, вам остается надеяться, что в новейшем коде в ветке master нет изменений в API. Для редких сторонних проектов это не так уж и плохо, так как большинство библиотек слово держат, но для production вариант не самый лучший.

В идеале нужен какой-то способ версионирования зависимостей, при котором можно было бы указать номер версии сторонней библиотеки в файле зависимостей. В этом случае можно будет не беспокоиться об изменении API, поскольку это не затронет используемую вами версию. Позже можно будет изучить отличия и принять решение о переходе на новую версию, сделать соответствующие изменения в вашем коде, если они нужны, и указать новый номер версии в файле зависимостей.

В Go проводится официальный эксперимент под названием dep, который, надеюсь, решит эту проблему. Возможно, это случится уже в Go 2 ?

Движемся к Go 2

Мне очень нравится опенсорсный подход к созданию языка, который мы видим на примере Go. Если вы хотите, чтобы в Go 2 была реализована нужная вам функциональность, оформите документ следующего содержания:

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

Авторы рассмотрят предложение и вынесут свой вердикт. Обсуждения этих вопросов проходят публично в рассылках и системах отслеживания ошибок (issue trackers).

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

Философия Go

Хочу привести здесь наиболее запомнившиеся мне цитаты из презентации Роба Пайка под названием Simplicity is Complicated.

  • По традиции языки программирования идут по пути наращивания функциональности.Таким образом они постоянно раздувают и усложняют свои компиляторы и спецификации. Если так будет продолжаться и дальше, в будущем все языки станут практически одинаковыми. Хороший пример — добавление ООП в JavaScript. Многие вещи намеренно не были включены в Go его авторами. Была реализована только та функциональность, относительно который существовало единство мнений авторов языка, те возможности, которые действительно увеличивали полезность Go.
  • Элементы функциональности напоминают ортогональные векторы в пространстве решений. Важна способность выбрать подходящие для вашего случая векторы. Выбранные векторы должны без лишних усилий взаимодействовать друг с другом, то есть все элементы функциональности языка должны состыковываться предсказуемым образом. Полный набор элементов функциональности языка покрывает пространство решений целиком. Реализация всевозможных фич с учетом необходимости их беспроблемного взаимодействия приносит в языки дополнительную сложность. Однако язык абстрагирует сложность, предоставляя простые и легкие для понимания интерфейсы. Таким образом, простота — это искусство скрытия сложности ?
  • Важность читабельности очень часто недооценивается.Читабельность критична. Возможно, в дизайне языка это самая главная вещь, поскольку необходимость и цена поддержки программного обеспечения также очень важны. Слишком большое количество фич вредит читабельности.

Читабельность также означает надежность. Если сложность языка высока, необходимо больше знаний и усилий для чтения кода и работы с ним, в том числе для отладки и внесения исправлений. Это также означает, что новичкам придется потратить больше времени, чтобы довести свое понимание языка до такого уровня, когда они смогут начать вносить вклад в развитие кодовой базы.

Взято с https://blog.digitalocean.com/get-your-development-team-started-with-go/

С чего начать

Дистрибутив Go и инструкцию по установке можно найти здесь.

Вот официальное руководство по началу работы. Также рекомендую Go в примерах.

Если хочется книгу, The Go Programming Language — отличный вариант. Она написана в том же духе, что и легендарная C Programming Languageза авторством Алана Донована (Alan A. A. Donovan) и Брайана Кернигана (Brian W. Kernighan).

При желании общаться с участниками сообщества можете присоединиться к Gophers Slack Channel.

Кто использует Go

Многие компании начали инвестировать в Go. Вот несколько наиболее известных: