Типизированный Go: четыре преимущества языка
Как Go защищает от ошибок и повышает производительность и какое значение в разработке имеет улучшенная читаемость и документация языка.
4К открытий6К показов
Одно из делений языка программирования заключается в типизации. От того, типизирован язык или нет, зависит база, на которой строится код. К типизированным, кроме прочих, относится язык Go (Golang).
Лид курса по языку Go в Яндекс Лицее и лид backend community Практикума Виталий Левченко написал колонку о том, какие преимущества Go имеет перед другими языками и какие задачи это помогает решать на практике.
Где активно применяют Go
В реальности на Go пишут в первую очередь веб-сервисы и инфраструктурные утилиты. И если по веб-сервисам есть богатый выбор языков программирования: Java, Python, Ruby, РНР и другие, то в случае инфраструктурных сервисов альтернатив практически нет. С релиза Go, который был в 2013 году, практически все новые инфраструктурные утилиты open source писались на Go, включая Prometheus, Kubernetes, Docker и Terraform.
В этом плане важно помнить, что Go — типизированный язык, что даёт ему несколько преимуществ.
Улучшенная читаемость и понимание кода
Существенная часть разработки — это не писать код, а читать его и разбираться, как в него внести изменения, как довести до состояния, которое можно выкатывать в продакшен.
Типизация помогает эффективнее работать с этим кодом, позволяя лучше понимать, как на самом деле функционирует код. Go преподают даже для старшеклассников — такой курс есть в том же Яндекс Лицее.
Приведу пример типизации — в Go она требует от разработчика чётко объявить тип данных. Так разработчики понимают, какие данные ожидаются и какие операции можно с ними выполнить. Это ясно даже по сигнатуре функций.
Например, сравните типичную функцию обработчика событий:
И в Go:
Причём добавление типов не сильно меняет ситуацию:
Прямая документация
Допустим, в сервисе на Go есть строки из примера выше.
Из них мы понимаем, что в программу будут переданы переменные с явно заданными полями типов Action и ProcessParams, а возвращается переменная типа Event, может быть ошибка, которую важно обработать.
Наличие типов к переменным или параметрам, которые имеют внятное название, делает функцию, класс или структуру самодокументируемыми. И уже по сигнатуре понятно, какая есть вариативность того, что она умеет делать.
Скажем, в функцию передаётся params, extra или props (так в JavaScript называют передачу properties). Из документации языка ясно, какие данные они принимают к себе, какие операции с ними могут или не могут проводить, каким образом переменные при них обрабатываются.
Так, params — структура с несколькими десятками полей. Если этого нет, а просто идёт какая-то переменная, которая выводится как массив, то разработчику сложно понять, что она ожидает, как работает и прочее. Чтобы понять, как работает конкретный параметр и можно ли поправить его поведение, приходится реверс-инжинирить, разбираться в коде функции и во всех её вызовах.
Типизация от этого избавляет.
Безопасность и защита от ошибок
В масштабном проекте избежать ошибок при написании кода полностью невозможно, но даже отсутствие 1% этих ошибок значительно повышает качество разработки.
Во-первых, в Go за счёт типизации есть уверенность в том, что ты вызываешь и к чему это приведёт. В итоге процесс разработки ускоряется, он не требует перепроверки и не вызывает неожиданных ошибок в рантайме.
Во-вторых, существует проблема, связанная с неявным преобразованием типов данных, которые есть в языках типа PHP, JavaScript или Python, когда программа начинает вести себя неожиданным образом. Например, код
выглядит как проверка статуса на null и некорректно срабатывает на нулевом значении статуса enum.
Особо неприятно, что это может быть ожидаемым поведением, а может — типичной ошибкой при разработке в потоке. И такая проблема системна, я вижу её раз за разом в большом количестве проектов, и строгая типизация от неё избавляет, потому что в ней нет неявного преобразования.
Такая же проблема встречается и в YAML, где неудачно написанная строка Yes
внезапно интерпретируется как true
, что тоже становится неявным преобразованием.
В-третьих, типизация мотивирует разработчика подумать над сигнатурами и типами, которые передаются. То есть нужно указать тип, и потому приходится подумать, какой тип необходим, а не просто указать переменную.
Данный этап мотивирует сразу подумать об архитектуре. Само это улучшает качество программы.
В итоге мы имеем ситуацию, что со строгой типизацией нужно написать чуть больше кода, но на практике это экономит время при чтении, перепроверке и всём остальном.
Поэтому обычно вырисовывается картина, что языки без типизации хороши для стартапа, который собирает MVP, чтобы проверить гипотезы и начать зарабатывать деньги. Многие криптовалютные проекты писались на JavaScript именно потому, что их можно быстро создать на коленке, не заботясь ни об архитектуре, ни о типах данных.
В момент же, когда появляются требования к качеству сервиса и ресурсы на него, проект переезжает на типизированный язык.
Улучшенная производительность
Строгая типизация позволяет компилятору или интерпретатору оптимизировать код и использовать более эффективные алгоритмы, чтобы работать с типами данных. И это может привести к улучшению производительности программы.
Любой программный код транслируется в машинный. Процессор при этом про высокоуровневые типы данных знать не хочет. Ему для работы необходимы байты и числа.
В случае с нетипизированным кодом хорошо, если рантайм-компилятор (JIT) способен оптимизировать его для процессора. Если же рантайм на это не способен, то на уровне машинного кода начинает происходить сложная обработка в зависимости от того, что у нас за тип данных пришёл на каждый нетипизированный параметр.
По итогу программа на свою работу тратит больше времени — и это может быть в десять раз дольше, чем при типизированном языке.
Проблему частично решает JIT-компилятор, но делает это довольно специфично — «смотрит» на статистику использования.
Допустим, в одну переменную приходил тип данных int, в другую — string. Компилятор анализирует это и решает скомпилировать код под те переменные, которые приходят статистически. И если в переменную приходят и int, и string, то он создаёт две копии, скомпилированные и под первый, и под второй тип.
За счёт этого решения JavaScript работает достаточно быстро и сопоставимо с Go, несмотря на то что один язык типизированный, а другой нет. Но это создаёт некий набор проблем в рантайм, потому что:
- Оптимизация срабатывает не сразу.
- Есть ограничения с типами, которые для каждого языка бывают специфичны. К примеру, JIT-компилятор может перестать работать, если видит в коде эксепшен.
- В Python до сих пор не могут затащить JIT как стандарт.
В Go этой проблемы нет: нет неявной типизации, а следовательно, не нужно прыгать с типами.
В конечном итоге для крупных сервисов с нетривиальными задачами Go оказывается безопаснее и производительнее, а работать с ним проще из-за простой документации и типизации.
4К открытий6К показов