понедельник, 14 декабря 2015 г.

Первые впечатления от Go

Решил взяться за изучение так сильно разрекламированного в последнее время языка "Go". На данный момент я его знаю весьма слабо, ничего сложнее "hello word" на нём на писал. Но мне просто хочется изложить свой первый "незамутнённый" взгляд на язык, а потом после более близкого знакомства сравнить ощущения. По причине плохого знания языка, в тексте ниже могут быть неточности, но по крайней мере, я старался по мере сил каждое утверждение проверять. Приступим.


Синтаксис

Это первое с чем сталкиваешься знакомясь с языком. Местами всё очень похоже на "C", только порезанный. Хотя казалось бы куда его ещё и так язык минималистичный. Но смогли, например в Go стандартный "while" включён в "for", убраны константные параметры функций, исчез префиксный инкремент, switch больше не требует "break" и т.п. мелочи. Непривычно смотрятся заботливо перенесённые указатели и ссылки, это в языке со сборкой мусора то, а вот адресной арифметики нет.

Неожиданно было встретить некоторые элементы из Pascal, хотя википедия утверждает, что это бы Oberon-2. Как-по мне логичнее было бы взять недостающие конструкции из C++. В частности взяли способ объявления переменных, хотя взяли как-то странно, способов инициализации есть целых два, классический:
var x = "Hello World"
И упрощённый, когда можно отбросить "var" и заменить "=" на ":=":
x := "Hello World"
А в каких-то местах авторы вообще не понятно откуда утащили синтаксис, вот так непривычно выглядит объявление map-ы вложенной в другой map:
map[string]map[string]int
OOP получилось каким-то куцым, наследование в чистом виде - убрали, пользуйтесь композицией. Конструкторов как таковых нет, деструкторы в каком-то виде представлены через малофункциональный "defer". Я не нашёл как сделать поле приватным, константным и т.п. В целом первое впечатление не очень, хотя нужно вникнуть поглубже.

Впечатления какие-то двойственные, с одной стороны видно что стремились сделать язык доступным армии C/C++ разработчиков, но зачем тогда Oberon и странное OOP? С другой видна работа по минимизации ключевых слов в языке, но вот на каналах они это опровергают добавляя новое ключевое слово "chan", да ещё и стрелочки специальные вводят "<-" и "->", хотя могли спокойно обойтись функциями. В общем, не видно какой-то общей концепции построения синтаксиса, не понятно почему он такой какой он есть, слишком много "а почему именно так?".

Стандартная библиотека 

Сразу после того, как я узнал, что в языке нет шаблонов (template), я пошёл смотреть какие же существуют стандартные контейнеры. Набор и способ использования в примерах, скажем мягко - удивил. Я ожидал многообразия stl с примерно похожей концепцией манипулирования данными, но видно никому не нужно.

Ну да ладно, зато есть сортировка, правда что бы отсортировать даже массив из "int", мне пришлось написать 3 метода для реализации интерфейса сортировки, по другому не вышло. А вот поиск уже не осилили. Зато есть куча встроенных библиотек для работы с HTTP и около HTTP-шных вещей, что действительно приятно. С базами данных вроде как умеют из коробки работать. И на этом пожалуй всё…, впрочем смотрите сами.

Складывается впечатление, что писали язык для веба, вспоминая какая корпорация за ним стоит - логично.

Строгость компилятора 

Одна из первых ошибок что мне выдал компилятор - неиспользуемый "import", это был даже не warning, а именно ошибка компиляции. А вот на неиспользуемые параметры функции, никто внимания не обратил, а неиспользуемая переменная в теле функции - опять ошибка компиляции, неиспользуемая функция - снова без внимания и т.д. О том что бы проверить все ли мы варианты в switch перебрали, обработано ли возвращаемое значение функции и т.п. маргинальщине, даже речи нет. Опять не видно общей идеи, как будто сделали только то, что успели.

За гранью добра и зла - заботливо перенесённый старый добрый "Printf", в котором оставили совершенно без внимания проверку параметров. И это не смотря на десятилетия борьбы с этим наследием в плюсах! Если я укажу в строке форматирования число, а передам строку - компилятор будет молчать как партизан, спасибо хоть в runtime не упадёт.

 

Обработка ошибок 

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

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

Язык абсолютно не бьёт по рукам за необработанные ошибки. Мало того, концепция языка мешает это делать. Нужно на каждый вызов функции идти в документацию (а иногда в исходники), выковыривать все коды возврата и аккуратно их обрабатывать. Это какой же силой воли нужно обладать, что бы писать корректно? А что делать, если библиотека обновилась и появился новый код ошибки, как это обнаружить в уже написанном коде? Впрочем судя по github никто особо обработкой ошибок и не заморачивается - просто передают выше по стеку.

 

Безопасность

Описывается одной фразой: "а зато у нас тесты писать легко". Мне напомнил Python, в котором при компиляции стали проверять синтаксис и типы. По крайней мере на данном этапе, я не нашёл, как компилятор ещё помогает разработчику. Особенно после знакомства с Rust, для меня дико - что в современном языке большинство ошибок отлавливаются только в runtime.

Горутины

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

Инфраструктура 

Сложно писать без мата. Я просто не понимаю как можно было такое сделать в языке созданном в 21 веке в одной из крупнейших компаний мира. Знаете как канонически импортировать модуль из этого же проекта, лежащий в соседней директории? Я не поверил сначала, полез смотреть исходники популярных проектов, действительно все делают как-то вот так:
import "github.com/ReanGD/go-test/src/module1"
Т.е. без шуток, мне предлагают указывать путь, содержащий имя системы контроля ревизий. Ну и конечно на диске нужно такую же иерархию создавать. И что самое странное - это похоже никого не парит. Вот например в статье на habrahabr в комментариях пишут: "Вся эта идея с папками нужна для легкой и непринужденной установки чужих проектов и публикации своих". Вот так вот: "легкой и непринужденной" блин.

Ещё вопрос: а как пользуясь стандартной инфраструктурой указать версию зависимого пакета? Там вообще возможно хоть как-то гарантировать, что у меня и у соседа приложение одинаково соберётся или что бы зафиксировать текущее положение мне свой GitHub со всеми зависимостями поднимать?

Ну или вот ещё из весёлого: допустим я указал все пакеты, которые я хочу использовать, логично было бы при компиляции пойти и скачать недостающие? Нет! Ты должен это сделать сам, причём судя по официальной документации ты будешь скачивать каждый пакет по отдельности. Как по мне, они должны были в первой строке документации, жирным шрифтом написать как автоматически скачать все пакеты, но я нашёл это только на стороннем сайте, вот это заклинание:
go get ./...
Я даже не понимаю, что оно делает, но вроде работает.

Идём дальше, из-за вышеописанных особенностей, мне хочется лишние пакеты удалять, т.к. они засоряют мою директорию с проектами. Итак, как удалить установленный пакет, вместе со всеми его бинарниками и зависимостями? Руками и только руками! На stackoverflow даже заботливо список директорий приводят, где стоит поискать.

Я даже не говорю про то, что мне хочется хоть какого-то рейтинга пакетов, хотя бы на основании статистики скачивания через "go get". Но похоже прийдётся выбирать библиотеки через бесконечные "lib1 vs lib2" в поиске.
Можно продолжать ещё долго, но нужно же немного позитива: в стандартной поставке есть утилита "gofmt", которая умеет форматировать исходники, адепты языка утверждают, что мол без такой, ни один язык не может считаться современным.

Заключение 

Я не понимаю почему вокруг языка так много разговоров. Я вижу у него только один плюс - простота. Это наверное хороший язык для большой компании, где все библиотеки внутренние, весь код проходит review, а квалификация разработчиков выше среднего. Но зачем тогда его так рекламировать на каждом углу? Возможно нужно попробовать написать на нём проект и впечатление изменится на противоположное…

2 комментария:

  1. Хочу добавить — формат строки Printf проверяет стандартная утилита go vet. Она ещё некоторые ошибки ловит, где-то в документации есть список.

    ОтветитьУдалить
    Ответы
    1. Да, действительно, вот документация: https://golang.org/cmd/vet/
      Странно конечно, что при компиляции не запускается, но уже намного лучше, чем думалось в начале.

      Удалить