Стефан Нильссон преподает информатику в Королевском технологическом институте Стокгольма и очень много пишет о языке Go. Предлагаем вам перевод его статьи Why Go? — Key advantages you may have overlooked, где он рассказывает о главных плюсах языка. Статья ориентирована на читателей, уже знакомых с основами программирования, в том числе на Java и/или Python.
Выбрать язык программирования непросто. Отдельные преимущества могут поначалу очаровывать, а выявление недостатков требует времени и опыта.
Как профессор информатики и разработчик, много лет использующий Go и Java, я хочу поделиться мыслями и объяснить, почему я ставлю Go выше, чем Java или Python. И почему с ним мне гораздо проще писать хороший код.
Go — мой основной язык с 2012 года. До этого, с 1998 года, я использовал Джаву, а еще раньше — Си. Python нужен мне главным образом в преподавательской работе.
Заниматься программированием я начал в далеком 1978 году. Тогда я писал для калькулятора TI-57, с его программной памятью на 50 шагов и 8 регистрами.
Минимализм
Go — минималистичный язык, и это (по большей части) очень хорошо.
Официальная спецификация Go умещается всего на 50 страницах, легко читается и содержит много примеров. Думаю, опытный программист может освоить язык по одной этой спецификации.
«Ядро» Go составляют простые ортогональные конструкции, которые можно сочетать сравнительно малым числом способов. Это упрощает изучение языка, чтение и написание кода.
Когда вы добавляете в язык новую возможность, он не просто усложняется — его сложность возрастает в разы. Потому что возможности могут пересекаться по самым разным сценариям. И это серьезная проблема, ведь сложность языка касается всех разработчиков, а не только тех, кто пишет спецификацию или делает компилятор.
Некоторые из ключевых особенностей Go:
- Полноценные встроенные фреймворки для тестирования и профилирования программ компактны и просты в изучении. Скорее всего, вам не понадобится ни один из многочисленных аддонов от сторонних разработчиков.
- Можно отлаживать и профилировать оптимизированный бинарник, который уже запущен в production на HTTP-сервере.
- Документация к языку Go генерируется автоматически и содержит примеры, которые можно запускать прямо на странице справки [для этого под каждым листингом есть кнопка Run — прим. пер.]. Опять же, интерфейсы минималистичны и не требуют долгого изучения.
- Go — язык с сильной статической типизацией и явным приведением типов, но его синтаксис на удивление необременителен. Все благодаря нетипизированным числовым константам и определению типа по присвоенному значению. Как результат, в работе с типами Go безопаснее, чем Java (где есть неявное приведение). При этом его код по легкости чтения ближе к Питону, где есть нетипизированные переменные.
- Программы на языке Go состоят из пакетов, что позволяет понятно делить код и легко управлять зависимостями. Механизм пакетов — пожалуй, в числе наиболее удачно реализованных в языке. И самых недооцененных.
- Структурно типизированные интерфейсы обеспечивают динамический полиморфизм за счет динамической диспетчеризации.
- Конкурентность — неотъемлемая часть Go, для нужд которой в языке есть горутины, каналы и оператор select. Чем конкурентность отличается от параллелизма, можно посмотреть здесь.
Небольшие примеры кода, описание базовых типов данных, методов и управляющих структур вы также найдете в статье «Go vs. Java: 15 главных отличий».
Задел на будущее
В Go нет ряда возможностей, обычных для других современных языков.
Вот универсальный ответ проектировщиков языка на вопрос «Почему в Go нет такой-то функции?».
Каждый язык предлагает новые возможности и не содержит чьих-то любимых. Go был создан с прицелом на удобство программирования, быструю компиляцию, возможность влиять на одну концепцию, не затрагивая другой, и необходимость поддерживать такие вещи, как параллелизм и сбор мусора. Ваша любимая «фишечка» могла сюда не вписаться, потому что увеличивает время компиляции или снижает ясность синтаксиса. Или потому, что она усложнила бы всю модель языка [принципы построения программ — прим. пер.].
Для добавления любой новой возможности нужны веские основания — острая необходимость, которую обосновали бы разработчики реальных проектов.
Вот наиболее вероятные кандидаты на добавление в Go 2:
- Управление пакетами с помощью модулей, поддержка которых уже была частично реализована в Go 1.11.
- Обобщенное программирование — концептуальная схема, реализация которой в Go уже запланирована во второй версии языка. Пока вместо обобщенного кода (дженериков) можно использовать альтернативы и обходные пути, расписанные вот здесь.
- Новая система обработки ошибок — пока тоже на стадии проекта — может заменить нынешние упрощенные механизмы.
Сейчас на рассмотрении более мелкие усовершенствования, которые помогут организовать и опробовать разработку Go с упором на инициативу сообщества.
Тем не менее вряд ли стоит ждать от Go 2 таких нововведений, как опциональные параметры, значения параметров по умолчанию и перегрузка методов.
Сравнение с Java
Спецификация языка Java® сейчас насчитывает 750 страниц. Сложность изучения связана в первую очередь с перегруженностью «фишками» [явление известно как feature creep — прим.пер.].
Вот вам несколько примеров. Внутренние классы внезапно появились в Java в 1997 году. На обновление спецификации ушло больше года, и она увеличилась в объеме почти вдвое. Это высокая цена для функции, появление которой было не критично.
Дженерики в Java, использующие стирание типов (type erasure), делают код яснее и позволяют выполнять дополнительные тесты во время исполнения программы. Но когда нужно выйти за рамки простейших примеров, работа с дженериками усложняется. Вы не можете создавать массивы дженериков, а шаблоны параметров (type wildcards) с нижней и верхней границей довольно сложны. Слово «дженерик» фигурирует в спецификации 280 раз. Лично я не уверен, стоила ли эта штука усилий, затраченных на ее реализацию.
Перечисления (enum) появились в Java в 2004 году как специальный класс, который работает с группой констант. Это, конечно, неплохо, но практически все возможности перечислений можно реализовать с помощью обычных классов. Термин enum упомянут в спецификации 241 раз.
Прозрачность кода
Блок обработки данных суперкомпьютера ILLIAC IV
Если вы не можете читать и понимать свой код, ваш проект обречен.
- Вы должны всегда четко знать, что именно делает ваш код.
- Иногда нужно оценить количество ресурсов (времени и памяти), необходимых для выполнения вашего кода.
Создатели Go постарались сделать так, чтобы обе эти потребности было легко удовлетворить.
Синтаксис языка создан с расчетом на прозрачность и поддерживает только один стандарт оформления кода, который можно автоматически применить утилитой fmt.
Другой пример: Go не скомпилирует программу, если она требует каких-то пакетов (через import), но в реальности не использует их в коде. Такой подход повышает ясность кода, а в долгосрочной перспективе — его производительность.
Подозреваю, что создатели Go нарочно усложнили некоторые вещи. Например, надо очень постараться, чтобы поймать исключение (панику). Причем, чтобы переступить через типобезопасность, вам придется пометить свой код ключевым словом unsafe.
Сравнение с Python
В Python фрагмент кода del a[i] удаляет элементы с индексом i из списка a. Этот код, конечно, вполне читаем, но не прозрачен: легко упустить из вида, что временная сложность алгоритма представлена как O(n), где n — число элементов списка.
У Go нет такой полезной функции. Это не так удобно, зато более прозрачно. Если вы копируете элемент списка, вам нужно явно указать это в коде. Смотрите пример кода: 2 способа удалить элемент из среза в Go. Но можно и проще — с помощью аppend.
Сравнение с Java
Прозрачность кода — проблема не надуманная. Вот пара примеров того, как правила инициализации пакетов и порядка выполнения кода в Go упрощают поддержку и доработку проекта:
- Циклические зависимости могут вести к нежелательным последствиям. В отличие от кода на Java, Go-программа с зацикленной инициализацией не скомпилируется.
- Программа на Go завершает работу только из функции main. Java-приложение закрывается после завершения всех пользовательских потоков, не являющихся демонами.
Это значит, что для понимания особенностей в работе Java-приложения нужно изучить большие фрагменты его кода. Это может быть и вовсе невозможно, если вы используете сторонние библиотеки.
Совместимость
Язык, который подвержен внезапным изменениям или утрачивает поддержку, может погубить ваш проект.
Для первой версии Go были кратко и сжато сформулированы гарантии совместимости для языкового «ядра» и стандартных библиотек: программы на Go, которые работают сегодня, должны работать и с будущими версиями Go 1. До сих пор обратная совместимость соблюдается безукоризненно.
Go — это проект с открытым кодом и BSD-подобной лицензией, которая разрешает коммерческое использование, внесение изменений, распространение и личное пользование.
Права интеллектуальной собственности принадлежат авторам Go и тем из нас, кто внес вклад в развитие проекта. Кроме того, Google предоставляет пользователям патент на интеллектуальную собственность, распространяемую корпорацией в рамках проекта Go.
Сравнение с Python
Если вы Python-разработчик, вам приходилось мучаться с различиями между Python 2.7.x and Python 3.x. И хотя есть веские основания выбирать Python 3, если вы используете библиотеки, доступные только для 2.7, у вас может не быть выбора.
Сравнение с Java
У языка Java очень хороший опыт обратной совместимости и подробное руководство по совместимости для JDK 8. Плюс, Java долгое время был в свободном доступе для разработчиков.
К сожалению, на горизонте сгущаются тучи. Причиной тому — судебное разбирательство между Oracle America и Google о сущности компьютерного кода, авторском праве и новой модели лицензирования Java от Oracle.
Производительность
Снаружи Go неброский, но под капотом у него — прекрасно отлаженный движок.
Бессмысленно обсуждать производительность вне контекста. Такие параметры программы, как время исполнения и потребление памяти, сильно зависят от алгоритмов, структур данных, входных данных, мастерства программиста, операционной системы и «железа».
Тем не менее заметно влиять на производительность могут язык, среда исполнения и стандартные библиотеки. Все это касается высокоуровневых задач и архитектурных решений. Чтобы глубже вникнуть в реализацию компилятора и его производительность, читайте FAQ по Go.
Прежде всего, Go — компилируемый язык. Готовое к запуску Go-приложение обычно выглядит как один исполняемый файл без отдельных динамических библиотек или виртуальных машин, которые можно было бы напрямую разворачивать.
Объем и скорость генерации машинного кода зависят от целевой архитектуры. Генерация кода Go довольно хорошо проработана и поддерживает все основные ОС (Linux, macOS, Windows) и архитектуры (Intel x86/x86-64, ARM64, WebAssembly, ARM и др.). Поэтому от go-приложений можно ждать производительности на уровне C++ или Java. Выигрыш относительно интерпретируемого кода на Python может быть огромным.
В Go есть сборщик мусора, что предотвращает утечки памяти. Причем задержка в работе сборщика минимальна. На деле вы можете даже не замечать, что «поток-мусорщик» запущен.
Практически все стандартные библиотеки исполнены очень качественно: их код оптимизирован по эффективным алгоритмам. К примеру, регулярные выражения в Go работают настолько хорошо, что время исполнения напрямую зависит от объема ввода. К сожалению, с Java и Python все иначе.
Скорость сборки в абсолютных величинах сейчас достаточно хороша. Что еще важнее, Go спроектирован так, чтобы упрощать компиляцию и анализ зависимостей. Это позволяет создавать легко масштабируемые решения для растущих проектов.
Пройти обучение