Решил взяться за изучение так сильно разрекламированного в последнее время языка "Go". На данный момент я его знаю весьма слабо, ничего сложнее "hello word" на нём на писал. Но мне просто хочется изложить свой первый "незамутнённый" взгляд на язык, а потом после более близкого знакомства сравнить ощущения. По причине плохого знания языка, в тексте ниже могут быть неточности, но по крайней мере, я старался по мере сил каждое утверждение проверять. Приступим.
Неожиданно было встретить некоторые элементы из Pascal, хотя википедия утверждает, что это бы Oberon-2. Как-по мне логичнее было бы взять недостающие конструкции из C++. В частности взяли способ объявления переменных, хотя взяли как-то странно, способов инициализации есть целых два, классический:
И упрощённый, когда можно отбросить "var" и заменить "=" на ":=":
А в каких-то местах авторы вообще не понятно откуда утащили синтаксис, вот так непривычно выглядит объявление map-ы вложенной в другой map:
OOP получилось каким-то куцым, наследование в чистом виде - убрали, пользуйтесь композицией. Конструкторов как таковых нет, деструкторы в каком-то виде представлены через малофункциональный "defer". Я не нашёл как сделать поле приватным, константным и т.п. В целом первое впечатление не очень, хотя нужно вникнуть поглубже.
Впечатления какие-то двойственные, с одной стороны видно что стремились сделать язык доступным армии C/C++ разработчиков, но зачем тогда Oberon и странное OOP? С другой видна работа по минимизации ключевых слов в языке, но вот на каналах они это опровергают добавляя новое ключевое слово "chan", да ещё и стрелочки специальные вводят "<-" и "->", хотя могли спокойно обойтись функциями. В общем, не видно какой-то общей концепции построения синтаксиса, не понятно почему он такой какой он есть, слишком много "а почему именно так?".
Ну да ладно, зато есть сортировка, правда что бы отсортировать даже массив из "int", мне пришлось написать 3 метода для реализации интерфейса сортировки, по другому не вышло. А вот поиск уже не осилили. Зато есть куча встроенных библиотек для работы с HTTP и около HTTP-шных вещей, что действительно приятно. С базами данных вроде как умеют из коробки работать. И на этом пожалуй всё…, впрочем смотрите сами.
Складывается впечатление, что писали язык для веба, вспоминая какая корпорация за ним стоит - логично.
За гранью добра и зла - заботливо перенесённый старый добрый "Printf", в котором оставили совершенно без внимания проверку параметров. И это не смотря на десятилетия борьбы с этим наследием в плюсах! Если я укажу в строке форматирования число, а передам строку - компилятор будет молчать как партизан, спасибо хоть в runtime не упадёт.
Но давайте разберёмся. В Rust мы не можем проигнорировать ошибку, программа не скомпилируется, а в Go нас даже не предупредят. В Rust передать ошибку выше по стеку можно одним макросом, а в Go - это три строчки. В Rust ошибки возвращаются в специальных типах, в Go на уровне договорённости это обычно второе возвращаемое значение. В Rust если мы взялись обрабатывать ошибку, компилятор заставит проверить все варианты, в Go - компилятору плевать, что мы проверим, а что нет.
Язык абсолютно не бьёт по рукам за необработанные ошибки. Мало того, концепция языка мешает это делать. Нужно на каждый вызов функции идти в документацию (а иногда в исходники), выковыривать все коды возврата и аккуратно их обрабатывать. Это какой же силой воли нужно обладать, что бы писать корректно? А что делать, если библиотека обновилась и появился новый код ошибки, как это обнаружить в уже написанном коде? Впрочем судя по github никто особо обработкой ошибок и не заморачивается - просто передают выше по стеку.
Безопасность
Описывается одной фразой: "а зато у нас тесты писать легко". Мне напомнил Python, в котором при компиляции стали проверять синтаксис и типы. По крайней мере на данном этапе, я не нашёл, как компилятор ещё помогает разработчику. Особенно после знакомства с Rust, для меня дико - что в современном языке большинство ошибок отлавливаются только в runtime.
Т.е. без шуток, мне предлагают указывать путь, содержащий имя системы контроля ревизий. Ну и конечно на диске нужно такую же иерархию создавать. И что самое странное - это похоже никого не парит. Вот например в статье на habrahabr в комментариях пишут: "Вся эта идея с папками нужна для легкой и непринужденной установки чужих проектов и публикации своих". Вот так вот: "легкой и непринужденной" блин.
Ещё вопрос: а как пользуясь стандартной инфраструктурой указать версию зависимого пакета? Там вообще возможно хоть как-то гарантировать, что у меня и у соседа приложение одинаково соберётся или что бы зафиксировать текущее положение мне свой GitHub со всеми зависимостями поднимать?
Ну или вот ещё из весёлого: допустим я указал все пакеты, которые я хочу использовать, логично было бы при компиляции пойти и скачать недостающие? Нет! Ты должен это сделать сам, причём судя по официальной документации ты будешь скачивать каждый пакет по отдельности. Как по мне, они должны были в первой строке документации, жирным шрифтом написать как автоматически скачать все пакеты, но я нашёл это только на стороннем сайте, вот это заклинание:
Я даже не понимаю, что оно делает, но вроде работает.
Идём дальше, из-за вышеописанных особенностей, мне хочется лишние пакеты удалять, т.к. они засоряют мою директорию с проектами. Итак, как удалить установленный пакет, вместе со всеми его бинарниками и зависимостями? Руками и только руками! На stackoverflow даже заботливо список директорий приводят, где стоит поискать.
Я даже не говорю про то, что мне хочется хоть какого-то рейтинга пакетов, хотя бы на основании статистики скачивания через "go get". Но похоже прийдётся выбирать библиотеки через бесконечные "lib1 vs lib2" в поиске.
Можно продолжать ещё долго, но нужно же немного позитива: в стандартной поставке есть утилита "gofmt", которая умеет форматировать исходники, адепты языка утверждают, что мол без такой, ни один язык не может считаться современным.
Синтаксис
Это первое с чем сталкиваешься знакомясь с языком. Местами всё очень похоже на "C", только порезанный. Хотя казалось бы куда его ещё и так язык минималистичный. Но смогли, например в Go стандартный "while" включён в "for", убраны константные параметры функций, исчез префиксный инкремент, switch больше не требует "break" и т.п. мелочи. Непривычно смотрятся заботливо перенесённые указатели и ссылки, это в языке со сборкой мусора то, а вот адресной арифметики нет.Неожиданно было встретить некоторые элементы из Pascal, хотя википедия утверждает, что это бы Oberon-2. Как-по мне логичнее было бы взять недостающие конструкции из C++. В частности взяли способ объявления переменных, хотя взяли как-то странно, способов инициализации есть целых два, классический:
var x = "Hello World"
x := "Hello World"
map[string]map[string]int
Впечатления какие-то двойственные, с одной стороны видно что стремились сделать язык доступным армии 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"
Ещё вопрос: а как пользуясь стандартной инфраструктурой указать версию зависимого пакета? Там вообще возможно хоть как-то гарантировать, что у меня и у соседа приложение одинаково соберётся или что бы зафиксировать текущее положение мне свой GitHub со всеми зависимостями поднимать?
Ну или вот ещё из весёлого: допустим я указал все пакеты, которые я хочу использовать, логично было бы при компиляции пойти и скачать недостающие? Нет! Ты должен это сделать сам, причём судя по официальной документации ты будешь скачивать каждый пакет по отдельности. Как по мне, они должны были в первой строке документации, жирным шрифтом написать как автоматически скачать все пакеты, но я нашёл это только на стороннем сайте, вот это заклинание:
go get ./...
Идём дальше, из-за вышеописанных особенностей, мне хочется лишние пакеты удалять, т.к. они засоряют мою директорию с проектами. Итак, как удалить установленный пакет, вместе со всеми его бинарниками и зависимостями? Руками и только руками! На stackoverflow даже заботливо список директорий приводят, где стоит поискать.
Я даже не говорю про то, что мне хочется хоть какого-то рейтинга пакетов, хотя бы на основании статистики скачивания через "go get". Но похоже прийдётся выбирать библиотеки через бесконечные "lib1 vs lib2" в поиске.
Можно продолжать ещё долго, но нужно же немного позитива: в стандартной поставке есть утилита "gofmt", которая умеет форматировать исходники, адепты языка утверждают, что мол без такой, ни один язык не может считаться современным.
Хочу добавить — формат строки Printf проверяет стандартная утилита go vet. Она ещё некоторые ошибки ловит, где-то в документации есть список.
ОтветитьУдалитьДа, действительно, вот документация: https://golang.org/cmd/vet/
УдалитьСтранно конечно, что при компиляции не запускается, но уже намного лучше, чем думалось в начале.