Go глазами java программиста
Она для программистов на других языках, которым инетересно, стоит ли тратить время на go.
Чем отличается go, например, от java и чем может быть полезен.
Принципы go
Когда-то можно было просто взять и создать новый язык программирования.
Сейчас язык имеет шанс занять какое-то место только если у него есть четкие принципы, которым следуют его создатели. Другими словами — собственное лицо.
Принципы go — это простота и продуктивность.
Принцип простоты звучит так:
Если без чего-то можно обойтись, без этого нужно обойтись.
Принцип продуктивности:
Самое ценное — это время, затрачиваемое разработчиком. Его надо минимизировать всеми доступными способами.
Проиллюстрирую принцип простоты:
В go очень мало языковых конструкций. Например, только один цикл. Go можно выучить за два вечера.
В go отказались от динамической загрузки библиотек — результат компиляции один большой исполняемый файл
В go нет warning-ов при компиляции. Любая некорректность или «многословие» — это ошибка компиляции.
В go встроен автоформат кода на уровне самого языка. Есть только один каноничный вид кода на go
Теперь несколько примеров продуктивности:
Код на go на 20–30 процентов короче аналогичного на яве (там просто не лишних слов, например, нет точки с запятой в конце каждого предложения, нет круглых скобок в операторах условия или цикла etc)
В go очень быстрый компилятор (несколько секунд на компиляцию большого проекта)
Продуктивности служит не только сам язык, но и стандартные инструменты с ним поставляемые.
Инструменты повышения производительности
Профилировщики
начну с небольшого отступления о том как я, программист java, пришел к использованию go.
Я делал игровой проект — многопользовательский космический шутер.
Изначально я написал серверную часть на java. Она работала.
Но очень быстро все уперлось в производительность.
На одном сервере можно было запустить не более 300 клиентов.
Это слишком мало, чтобы игра стала рентабельной.
Что я мог сделать с этим, как программист java?
На самом деле немногое:
1) Используя метод пристального взгляда искать в коде неэффективные места, пробовать их исправлять и, после каждого изменения, вновь запускать нагрузочные тесты. Этот способ очевидно неэффективен.
2) Погрузиться в изучение альтернативных серверных библиотек, пробовать разные варианты. Это тоже не дает никаких гарантий — возможно проблемы в моём собственном коде, или вообще в самом языке java.
3) Наконец, я мог купить один или несколько платных профилировщиков и попробовать получить какую-то информацию от них. Но и тут есть проблемы. Профилировщики, которые я видел, требуют, чтобы их запускали на той же машине, что и сервер. Если использовать локальный сервер, стоящий у нас в офисе, я не мог создать нужной нагрузки, поскольку у нас в офисе нет нужного количества свободных машин, чтобы запустить несколько тысяч клиентов. Если же использовать внешний сервер, то требовалась довольно сложная конфигурация, чтобы запустить там профилировщик. Наконец, выбор профилировщика сам по себе является нетривиальной задачей.
В go эта проблема решена очень удобно.
Профилировщик там является частью языка.
Включить его можно на любом сервере, а результатом его работы является файл, который можно скачать на свою машину и не спеша изучить при помощи очень удобных инструментов.
Эти инструменты покажут какие именно строчки кода тратят более всего памяти и процессорных циклов.
Их использование дает возможность быстро и эффективно найти и исправить все проблемные места. (Которые, кстати, почти всегда оказываются не там, где ожидаешь их найти)
В моем проекте сервер, переписанный на go изначально «тянул» только 20 клиентов (много хуже, чем на яве). После работы с профилировщиками эта цифра выросла до 2500.
Race detector
Другая головная боль всех писателей многопоточных приложений — race conditions.
Это такие трудноуловимые баги, которые возникают только если звезды сошлись определенным образом. То есть, если потоки запустились в одном порядке, баг есть, если в другом — нет. А порядок непредсказуем.
В go для решения этой проблемы есть стандартный инструмент — race detector. Если его включить, программа на go будет писать в лог обо всех небезопасных обращениях к общей памяти из разных потоков.
Используя race detector можно быстро, целенаправленно найти и устранить все проблемные места.
Интересные конструкции
Я не имею целью научить программировать на go и не стану разбирать все его конструкции. Хочу остановиться только на трех, наиболее интересных. Все они описывают парадигмы, которых в яве в чистом виде нет. И эти парадигмы очень полезны.
Это интерфейсы, горутины и каналы.
Интерфейсы
Интерфейс в go похож на интерфейс в яве или c#. Это — набор сигнатур методов, но без реализаций.
Основная разница в том, что go не требует объявлять, что какая-то сущность имплементирует какой-то интерфейс.
Достаточно, чтобы у сущности просто были все нужные методы.
Что это даёт? Decoupling.
Вы можете взять чужую библиотеку. Найти там сущность с каким-то набором методов, создать у себя интерфейс с тем же набором и использовать эту сущность, как этот интерфейс. Вам не надо изменять чужой код, не надо завязывать свой код на чужие сущности и не надо писать адаптеров, которые являются классическим boilerplate кодом. Еще одна иллюстрация принципа продуктивности.
Горутины
Сначала для многозадачных приложений в языках программирования были придуманы процессы. Они были тяжеловесными, имели собственное адресное пространство и быстро «жрали» ресурсы системы. Потом появились треды (или нитки). Они были гораздо легче и работали в одном адресном пространстве. Если процессов обычно запускали единицы или десятки, то тредов можно было иметь уже сотни.
Однако, при неаккуратном использовании и они могли отнять все ресурсы системы. Каждый тред всё-таки занимал какие-то ресурсы, даже если был заблокирован.
Чтобы ограничить число тредов, начали использовать тред пулы.
Горутины можно мыслить как задачи, выполняемые одним общим большим тред пулом. В Go горутины крайне дешевы. Вы можете запустить миллионы горутин без проблем для производительности. Единственное требование — горутины должны быть «маленькие». То есть горутина должна быстро сделать свою работу и либо выйти, либо заблокироваться (что с точки зрения планировщика горутин одно и тоже)
Каналы
Каналы являются расово правильным средством коммуникаций между горутинами.
В go есть важное правило:
Don’t communicate by shared state. Share state by communication.
Его смысл — не пользуйтесь переменными, к которым имеет доступ более одной горутины.
Вместо этого заставьте горутины пересылать данные друг другу.
Эта пересылка данных как раз и делается через каналы.
Канал в go это очередь (буфер) в один конец которого можно писать, а из другого читать.
При этом, горутина блокируется при попытке писать в переполненный канал или читать из пустого канала.
Но самое интересное — go имеет конструкцию для одновременного чтения из нескольких каналов.
Горутина может перечислить несколько каналов, на которых она будет сидеть и ждать появления сообщения в одном из них. Получив это сообщение, горутина разблокируется, обрабатывает его и (обычно) снова ждет следующего сообщения.
Так, например, в чат сервере горутина может ждать как сообщение от пользователя так и сигнала о выходе из чата. Одновременно.
Управление зависимостями
Такие инструменты управления зависимостями, как maven или gradle сегодня существуют для всех серьезных языков.
В go пошли дальше и сделали поддержку управления зависимостями на уровне самого языка.
При импорте пакета (конструкцией, аналогичной import в яве) можно указать как локальное имя, так и адрес пакета в любой современной системе контроля версий (например, в git).
Например, «github.com/gorilla/websocket»
Go самостоятельно скачает нужный пакет и включит его в ваш проект. Если какой-то пакет уже был ранее скачан, то он просто будет использован. Вы также можете попросить go обновить все пакеты до их последних версий.
Здесь, впрочем есть один неприятный момент — go всегда скачивает последнюю версию пакета. Если над проектом работает несколько человек, это может привести к различным версиям пакетов у разных разработчиков.
Для решения этой проблемы используются внешние инструменты — менеджеры пакетов.
Один из лучших на сегодняшний день — glide.
В основе glide — два действия:
1) Найти все зависимости проекта, и записать их в файл
2) Cкачать эти зависимости
При этом файл можно редактировать вручную, указывая, при необходимости, другие версии пакетов (отличные от последней).
В качестве идентификатора версии в GIT используется идентификатор коммита (в других системах контроля версий используются специфичные для них идентификаторы)
Слабые стороны go
Любой принцип имеет не только сильные стороны, но и слабые.
В go, на мой взгляд, есть одна плохая и одна ужасная вещь, которые проистекают из принципа простоты.
Плохая — это отсутствие generic-ов. Ужасная — обработка ошибок путем проверки возвращаемого функцией кода.
Без generic-ов приходится отказываться от строгой типизации.
А обработка ошибок приводит к большому количеству однотипного кода. Который, к тому же, можно вовсе забыть написать и оставить ошибку необработанной.
Кроме этого, следуя принципу продуктивности, было решено ввести в go garbage collector. Само по себе это неплохо. Но важно понимать, что этот самый коллектор будет запускаться каждые несколько секунд и заметно притормаживать систему. С этим, впрочем, можно успешно бороться.
Выводы
Стоит ли ява программисту полностью переходить на go? Нет. Во всяком случае, пока. Ява в среднем быстрее. В ней больше полезных библиотек и наработанных способов создания приложений.
Стоит ли использовать go для решения определенных задач? Однозначно, стоит.
Какие задачи лучше всего решать на go? Создание многопоточных высоконагруженных серверных решений, в которых потоки много коммуницируют между собой.
Какой язык лучше учить новичку — go или яву? Однозначного ответа нет, но со временем аргументов в пользу go будет все больше.
Возраст языка играет роль. Более старый язык обрастает «историческими» вещами. Что-то было добавлено давно, теперь никому не нужно, но выбросить нельзя, поскольку нарушится обратная совместимость.
Какие-то конструкции языка или стандартных библиотек, ранее актуальные, сейчас выглядят неуместно, кажется, логичнее делать эти невостребованные вещи вне стандарта языка.
Для каких-то стандартных проблем накопилось множество альтернативных решений разного качества. Для новичка всё это — большое количество бесполезных, но необходимых знаний в стиле: «Здесь нужно быть особенно осторожным. Здесь у нас ЛУЖА»
Go создавался гораздо позже и на сегодня в нем присутствует все, что востребовано сейчас и ничего лишнего.