Переход на новый язык — это всегда большой шаг. Особенно, если этим языком владеет только один член команды. В начале этого года мы поменяли основной язык программирования в Stream — с Python на Go. В этой статье я приведу 9 причин почему — и 3 минуса, выявленных в процессе.
Причина 1. Производительность
Go — быстрый. Очень быстрый! По скорости его можно сравнить с Java или C++. В нашем сценарии использования Go обычно в 40 раз быстрее Python. Вот небольшая игра-бенчмарк, в которой сравниваются Go и Python.
Причина 2. Скорость языка имеет значение
Во многих приложениях язык программирования является лишь связующим звеном между самим приложением и базой данных. Скоростью самого языка обычно можно пренебречь. Однако наше приложение обеспечивает работу каналов и чат-платформы для 700 компаний и более 500 млн конечных пользователей. Несколько лет мы оптимизировали Cassandra, PostgreSQL, Redis и другие компоненты и, в конце концов, достигли пределов возможностей используемого языка. Python — прекрасный язык, но его скорости недостаточно для таких сценариев использования, как сериализация/десериализация, ранжирование и агрегирование. У нас часто возникали проблемы со скоростью: Cassandra извлекала данные за 1 мс, а Python тратил еще 10 мс, чтобы преобразовать их в объекты.
Причина 3. Продуктивность разработчиков и ограничение креативности
Взгляните на этот небольшой фрагмент кода на Go из руководства по началу работы с Go. (Это отличное руководство и хорошая отправная точка для знакомства с этим языком.)
Если вы новичок в Go, то вас вряд ли что-то удивит при чтении этого небольшого фрагмента кода. В нем показано несколько присвоений, структуры данных, указатели, форматирование и встроенная библиотека HTTP.
Когда я только начал программировать, я старался использовать более продвинутые возможности Python. Python позволяет подойти к написанию кода творчески. Например, вы можете:
использовать метаклассы для самостоятельной регистрации классов при инициализации кода;
заменять True на False и наоборот;
добавлять функции в список встроенных функций;
создавать перегрузку операторов с помощью магических методов;
использовать функции в качестве свойств через декоратор @property.
С этими функциями интересно поэкспериментировать, но они зачастую усложняют понимание чужого кода (и со мной согласится большинство программистов). Go заставляет следовать основам. Это позволяет читать чужой код и сразу понимать, как что работает. Примечание. Разумеется, простота сильно зависит от вашего сценария использования. Если вы хотите создать базовый интерфейс CRUD API, я бы порекомендовал связку Django + DRF или Rails.
Причина 4. Параллелизм и каналы
Go пытается оставаться простым. Он не вводит множество новых концепций. Главное — создание понятного и невероятно быстрого языка, с которым легко работать. Единственная инновация в нем — это гоурутины и каналы. (Если быть на 100% точным, то концепция CSP появилась в 1977 году, поэтому данное нововведение — это скорее новый подход к хорошо забытому старому.)
Гоурутины (смотри тут) — это упрощенный подход Go к многопоточности. Создание гоурутин обходится очень дешево, а сами они занимают всего несколько килобайт дополнительной памяти. Из-за столь низких запросов их можно запускать сотнями или даже тысячами одновременно.
Для взаимодействия между гоурутинами можно использовать каналы. Все сложности берет на себя среда выполнения Go. Реализация многопоточности с ними позволяет легко использовать все доступные ядра ЦП и обрабатывать параллельный ввод-вывод данных без усложнения разработки. По сравнению с языками Python или Java запуск функции с помощью гоурутины требует минимального шаблонного кода. Вам нужно всего лишь добавить к вызову функции ключевое слово «go»:
С используемой в Go реализацией многопоточности очень легко работать. Это довольно интересный подход, учитывая, что в том же Node разработчику приходится внимательно следить за обработкой асинхронного кода.
Другим важным аспектом многопоточности в Go является детектор гонки (Go Race Detector). Он позволяет легко выявлять наличие гонки в асинхронном коде.
Вот несколько полезных ресурсов для знакомства с Go и каналами:
http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
https://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html
Goroutines vs Green threads (Сравнение гоурутин и «зеленых» потоков)
Причина 5. Быстрая компиляция
На текущий момент компиляция нашего самого крупного микросервиса на Go занимает 4 секунды. Быстрая компиляция — главное преимущество Go перед медлительными Java и C++. Мне нравится сражаться на мечах, но еще приятнее, когда к завершению компиляции я ещё помню, что должен делать код:
Причина 6. Возможность собрать команду
Во-первых, начнем с очевидного: разработчиков на Go куда меньше, чем разработчиков на более старых языках типа C++ и Java. Согласно данным StackOverflow, 38% разработчиков знает Java, 19,3% — C++ и только 4,6% — Go. Данные GitHub показывают аналогичную тенденцию: язык Go встречается чаще, чем языки типа Erlang, Scala и Elixir, но не так популярен, как Java и C++. К счастью, Go легок и прост в изучении. Он предлагает только базовые необходимые функции и ничего более.
Среди новшеств — вводятся оператор defer и встроенное управление многопоточностью с помощью гоурутин и каналов. Для сторонников пуризма: Go не был первым языком, реализовавшим эти концепции, но именно в нем они приобрели популярность.
Благодаря простоте языка любой новоприбывший разработчик на Python, Elixir, C++, Scala или Java сможет влиться в процессы уже через месяц. Получается, собрать команду разработчиков на Go гораздо проще, чем на других языках. А это важное преимущество, если вы набираете сотрудников в таких конкурентных экосистемах, как Боудер и Амстердам.
Причина 7. Сильная экосистема
В нашей команде 20 человек, и для нас экосистема очень важна. Вы просто не сможете создавать добавленную стоимость для своих клиентов, если вам придется заново изобретать каждую маленькую функцию. В Go есть отличная поддержка инструментов, которыми мы пользуемся. Надежные библиотеки уже доступны для Redis, RabbitMQ, PostgreSQL, разбора шаблонов, планирования задач, разбора выражений и RocksDB.
Безусловно, экосистема Go уступает Java, Python или Node, но она с большим отрывом выигрывает у языков поновее, типа Rust или Elixir. Это крепкая экосистема с готовыми высококачественными пакетами под многие базовые задачи.
Причина 8. Gofmt, принудительное форматирование кода
Начнем с того, что такое Gofmt. И нет, это не ругательство. Gofmt — это потрясающая утилита командной строки, встроенная в компилятор Go и предназначенная для форматирования кода. По функциональности она очень похожа на autopep8 в Python. И что бы ни показывали в «Кремниевой долине», большинству из нас не очень интересно спорить о табуляциях и пробелах. Главное, чтобы форматирование было единообразным, но каким оно будет конкретно, особо не важно. Gofmt избавляет от подобных споров, предлагая один официальный способ форматирования кода.
Причина 9. gRPC и буферы протокола
У Go первоклассная поддержка буферов протоколов и gRPC. Они прекрасно работают в тандеме при создании микросервисов, взаимодействующих через RPC. Вам нужно только написать манифест, где будут установлены допустимые вызовы RPC и принимаемые ими аргументы. Затем из этого манифеста автоматически генерируется серверный и клиентский код.
Получившийся код будет быстрым и простым в использовании, а еще он практически не нагрузит сеть. Один и тот же манифест позволяет сгенерировать клиентский код на самых разных языках, даже на C++, Java, Python и Ruby. Так что больше никаких неоднозначных конечных точек REST для внутреннего трафика, для которых каждый раз приходится писать практически один и тот же клиентский и серверный код.
Недостаток 1. Отсутствие фреймворков
У Ruby есть Rails, у Python — Django, у PHP — Laravel. А вот у Go нет ни одного ведущего фреймворка. По этой теме в сообществе постоянно ведутся жаркие дебаты. Многие считают, что никакие фреймворки языку в принципе не нужны. Я частично соглашусь, в некоторых сценариях они не потребуются. Но если кто-то захочет создать простой интерфейс CRUD API, ему будет гораздо проще с Django/DJRF, Rails, Laravel или Phoenix.
Дополнение. Комментаторы указали несколько проектов, которые предлагают фреймворк для Go — Revel, Iris, Echo, Macaron и Buffalo. Мы для своих сценариев предпочли не использовать фреймворк. Однако для многих новых проектов, нацеленных запилить простой интерфейс CRUD API, отсутствие ведущего фреймворка станет серьезным недостатком.
Недостаток 2. Обработка ошибок
При возникновении ошибки Go просто возвращает эту ошибку функции и ожидает, что ее обработает вызывающий код — либо вернет ее выше, в стек вызывающей программы. Такой подход вполне работоспособен, но можно легко упустить из виду, что что-то пошло не так, и не выдать пользователям соответствующее сообщение об ошибке.
Пакет errors решает эту проблему, позволяя добавлять контекст и выполнять трассировку стека до ваших ошибок. Другая проблема заключается в том, что можно случайно забыть обработать ошибку. Да, в этом случае помогут инструменты статического анализа типа errcheck и megacheck. Эти обходные решения прекрасно работают, но все же прибегать к ним как-то неправильно. Хочется, чтобы язык поддерживал надлежащую обработку ошибок.
Недостаток 3. Управление пакетами
Управление пакетами в Go оставляет желать лучшего. По умолчанию в нем нельзя указывать конкретную версию зависимости и создавать воспроизводимые сборки. У Python, Node и Ruby с этим получше. Впрочем, при использовании подходящих инструментов можно наловчиться управлять пакетами и в Go. Для указания и закрепления версий подходит Dep. Кроме того, мы участвовали в разработке VirtualGo — он упрощает работу над несколькими проектами параллельно.
Дополнение. С момента публикации статьи механизм управления пакетами в Go поменялся в лучшую сторону. Эффективным решением стали модули Go. Единственной проблемой, с которой я столкнулся — они ломают некоторые инструменты статического анализа, такие как errcheck. Вот руководство по использованию Go и модулей Go.
Сравниваем Python и Go
Мы провели интересный эксперимент, взяв функционал нашего ранжирования каналов на Python и переписав его на Go. Взгляните на этот пример метода ранжирования:
Для его поддержки код как на Python, так и на Go должен выполнить следующее:
Разобрать выражение для оценки. В данном случае мы хотим превратить строку «simple_gauss(time)*popularity» в функцию, которая на входе принимает активность, а на выходе возвращает оценку (score).
Создать частично вычисляемые функции на основе конфигурации JSON. Например, нам нужно, чтобы функция simple_gauss вызывала decay_gauss со шкалой (scale) в 5 дней, смещением (offset) в 1 день и коэффициентом затухания (decay) 0,3.
Разобрать конфигурацию по умолчанию (defaults), чтобы у вас была возможность перейти в резервный режим работы, если какое-либо поле в активности окажется не определено.
Использовать функцию из шага 1 для оценки всех активностей на канале.
Разработка кода ранжирования на Python заняла примерно 3 дня. Сюда входит написание кода, модульных тестов и документации. Затем мы потратили примерно 2 недели на оптимизацию кода. Одна из оптимизаций состояла в переводе выражения оценки (simple_gauss(time)*popularity) в абстрактное синтаксическое дерево. Мы также реализовали логику кэширования, которая предварительно вычисляла оценку для определенного времени в будущем.
Написание этого же кода на Go заняло примерно 4 дня. Дополнительные оптимизации не потребовались. Хотя поначалу разработка на Python шла быстрее, с Go было гораздо меньше возни. Плюсом с Go мы получили более высокую скорость работы кода — в 40 раз быстрее, чем высокооптимизированный код на Python.
Это лишь один из примеров буста производительности, который мы получили при переходе на Go. Ну и, конечно же, сравнение несопоставимого:
код ранжирования был моим первым проектом на Go;
на Go мы писали уже после того, как написали код на Python, поэтому я лучше понимал сценарий использования;
библиотека Go для разбора выражений была исключительно высокого качества.
Может, у вас было по-другому. Стоит признаться, что какие-то другие компоненты системы на Go разрабатывались гораздо дольше, чем на Python. В целом разработка на Go требует чуть больше усилий, но зато на оптимизацию уходит гораздо меньше времени.
Дополнение. С момента публикации статьи разница в производительности Python и Go возросла. Go стал ещё быстрее, а Python — нет.
Сравнение Elixir и Go. Серебряный призер
Еще мы рассматривали Elixir. Он построен на виртуальной машине Erlang. Один наш коллега с ним долго работал, и мы решили его проверить. В наших сценариях использования мы заметили, что исходная скорость Go намного выше. И Go, и Elixir отлично справляются с тысячами параллельных запросов, но если оценивать скорость отдельных запросов, то Go гораздо быстрее.
Другой причиной стала экосистема. Для Go уже были готовы проверенные библиотеки для нужных нам компонентов, тогда как библиотеки Elixir было невозможно использовать в рабочей среде без предварительной подготовки. Кроме того, найти или обучить разработчиков на Elixir сложнее. В общем, наши взоры вновь устремились на Go. Но если вы захотите его опробовать, обратите внимание на потрясающий фреймворк Phoenix — он определенно заслуживает внимания.
Заключение
Go — высокопроизводительный язык с отличной поддержкой многопоточности. Он практически такой же быстрый, как C++ и Java. Хотя разработка на Go занимает больше времени, чем разработка на Python или Ruby, вы сэкономите массу времени на оптимизации кода.
У нас небольшая команда разработчиков поддерживает работу каналов и чата для более чем 500 млн конечных пользователей. Благодаря сочетанию отличной экосистемы, простоты адаптации новых разработчиков, высокой производительности, хорошей поддержки многопоточности и продуктивной среды разработки язык Go стал отличным вариантом. Мы по-прежнему используем Python в Stream для информационного табло, сайта и машинного обучения для персонализированных каналов, и прощаться с ним не планируем. Но теперь весь высокопроизводительный код будет писаться на Go. Наш новый Chat API также полностью написан на Go. Если вы хотите узнать больше о языке Go, ознакомьтесь со статьями из списка ниже.
Остаётесь верны Python? Напишите нам — мы подберём для вас что-нибудь подходящее. Сейчас нам очень нужны Python-разработчики для работы удалённо или с релокацией. Если вы ищете разработчиков, тоже пишите. :)
Дополнительные материалы по переходу на Golang
Изучение Go
Читайте также: