Написать пост

Типизированный Go: четыре преимущества языка

Как Go защищает от ошибок и повышает производительность и какое значение в разработке имеет улучшенная читаемость и документация языка.

Обложка поста Типизированный Go: четыре преимущества языка

Одно из делений языка программирования заключается в типизации. От того, типизирован язык или нет, зависит база, на которой строится код. К типизированным, кроме прочих, относится язык Go (Golang).

Лид курса по языку Go в Яндекс Лицее и лид backend community Практикума Виталий Левченко написал колонку о том, какие преимущества Go имеет перед другими языками и какие задачи это помогает решать на практике.

Где активно применяют Go

В реальности на Go пишут в первую очередь веб-сервисы и инфраструктурные утилиты. И если по веб-сервисам есть богатый выбор языков программирования: Java, Python, Ruby, РНР и другие, то в случае инфраструктурных сервисов альтернатив практически нет. С релиза Go, который был в 2013 году, практически все новые инфраструктурные утилиты open source писались на Go, включая Prometheus, Kubernetes, Docker и Terraform.

В этом плане важно помнить, что Go — типизированный язык, что даёт ему несколько преимуществ.

Улучшенная читаемость и понимание кода

Существенная часть разработки — это не писать код, а читать его и разбираться, как в него внести изменения, как довести до состояния, которое можно выкатывать в продакшен.

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

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

Например, сравните типичную функцию обработчика событий:

			def process(action, params):
		

И в Go:

			func process(action Action, params ProcessParams) (queuedEvent Event, err error)
		

Причём добавление типов не сильно меняет ситуацию:

			def process(action: Action, params: dict) -> Event:
		

Прямая документация

Допустим, в сервисе на Go есть строки из примера выше.

Из них мы понимаем, что в программу будут переданы переменные с явно заданными полями типов Action и ProcessParams, а возвращается переменная типа Event, может быть ошибка, которую важно обработать.

Наличие типов к переменным или параметрам, которые имеют внятное название, делает функцию, класс или структуру самодокументируемыми. И уже по сигнатуре понятно, какая есть вариативность того, что она умеет делать.

Скажем, в функцию передаётся params, extra или props (так в JavaScript называют передачу properties). Из документации языка ясно, какие данные они принимают к себе, какие операции с ними могут или не могут проводить, каким образом переменные при них обрабатываются.

Так, params — структура с несколькими десятками полей. Если этого нет, а просто идёт какая-то переменная, которая выводится как массив, то разработчику сложно понять, что она ожидает, как работает и прочее. Чтобы понять, как работает конкретный параметр и можно ли поправить его поведение, приходится реверс-инжинирить, разбираться в коде функции и во всех её вызовах.

Типизация от этого избавляет.

Безопасность и защита от ошибок

В масштабном проекте избежать ошибок при написании кода полностью невозможно, но даже отсутствие 1% этих ошибок значительно повышает качество разработки.

Во-первых, в Go за счёт типизации есть уверенность в том, что ты вызываешь и к чему это приведёт. В итоге процесс разработки ускоряется, он не требует перепроверки и не вызывает неожиданных ошибок в рантайме.

Во-вторых, существует проблема, связанная с неявным преобразованием типов данных, которые есть в языках типа PHP, JavaScript или Python, когда программа начинает вести себя неожиданным образом. Например, код

			if user.status:
		

выглядит как проверка статуса на null и некорректно срабатывает на нулевом значении статуса enum.

Особо неприятно, что это может быть ожидаемым поведением, а может — типичной ошибкой при разработке в потоке. И такая проблема системна, я вижу её раз за разом в большом количестве проектов, и строгая типизация от неё избавляет, потому что в ней нет неявного преобразования.

Такая же проблема встречается и в YAML, где неудачно написанная строка Yes внезапно интерпретируется как true, что тоже становится неявным преобразованием.

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

Данный этап мотивирует сразу подумать об архитектуре. Само это улучшает качество программы.

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

Поэтому обычно вырисовывается картина, что языки без типизации хороши для стартапа, который собирает MVP, чтобы проверить гипотезы и начать зарабатывать деньги. Многие криптовалютные проекты писались на JavaScript именно потому, что их можно быстро создать на коленке, не заботясь ни об архитектуре, ни о типах данных.

В момент же, когда появляются требования к качеству сервиса и ресурсы на него, проект переезжает на типизированный язык.

Улучшенная производительность

Строгая типизация позволяет компилятору или интерпретатору оптимизировать код и использовать более эффективные алгоритмы, чтобы работать с типами данных. И это может привести к улучшению производительности программы.

Любой программный код транслируется в машинный. Процессор при этом про высокоуровневые типы данных знать не хочет. Ему для работы необходимы байты и числа.

В случае с нетипизированным кодом хорошо, если рантайм-компилятор (JIT) способен оптимизировать его для процессора. Если же рантайм на это не способен, то на уровне машинного кода начинает происходить сложная обработка в зависимости от того, что у нас за тип данных пришёл на каждый нетипизированный параметр.

По итогу программа на свою работу тратит больше времени — и это может быть в десять раз дольше, чем при типизированном языке.

Проблему частично решает JIT-компилятор, но делает это довольно специфично — «смотрит» на статистику использования.

Допустим, в одну переменную приходил тип данных int, в другую — string. Компилятор анализирует это и решает скомпилировать код под те переменные, которые приходят статистически. И если в переменную приходят и int, и string, то он создаёт две копии, скомпилированные и под первый, и под второй тип.

За счёт этого решения JavaScript работает достаточно быстро и сопоставимо с Go, несмотря на то что один язык типизированный, а другой нет. Но это создаёт некий набор проблем в рантайм, потому что:

  • Оптимизация срабатывает не сразу.
  • Есть ограничения с типами, которые для каждого языка бывают специфичны. К примеру, JIT-компилятор может перестать работать, если видит в коде эксепшен.
  • В Python до сих пор не могут затащить JIT как стандарт. 

В Go этой проблемы нет: нет неявной типизации, а следовательно, не нужно прыгать с типами.

В конечном итоге для крупных сервисов с нетривиальными задачами Go оказывается безопаснее и производительнее, а работать с ним проще из-за простой документации и типизации.

Следите за новыми постами
Следите за новыми постами по любимым темам
4К открытий5К показов