tl;dr магия это плохо; глобальные состояние это магия > глобальные переменные в пакетах это плохо; функция init() не нужна.


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


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


Функции (включая методы) по сути являются единственным механизмом, который есть в Go, чтобы строить абстракции. Давайте посмотрим на следующее определение функции:


func NewObject(n int) (*Object, error)

По соглашению, мы ожидаем, что функции в форме NewXXX это конструкторы типов. Это ожидание подтверждается, когда мы видим, что функция возвращает указатель на Object и ошибку. Из этого мы можем вывести, что конструктор может не сработать, и в этом случае он вернёт ошибку, в которой объяснена причина. Также мы видим, что функция принимает на вход единственный целочисленный параметр, который, как мы полагаем, контролирует какой-то аспект или свойство объекта, который будет возвращён. Вероятно, есть какие-то допустимые значения n, которые, будучи нарушенными, приведут к ошибке. Но, поскольку, функция больше не принимает других параметров, мы ожидаем, что функция больше никаких побочных эффектов нет, кроме (вероятно) выделения памяти.


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


Теперь, давайте посмотрим на тело функции:


func NewObject(n int) (*Object, error) {
    row := dbconn.QueryRow("SELECT ... FROM ... WHERE ...")
    var id string
    if err := row.Scan(&id); err != nil {
        logger.Log("during row scan: %v", err)
        id = "default"
    }
    resource, err := pool.Request(n)
    if err != nil {
        return nil, err
    }
    return &Object{
        id:  id,
        res: resource,
    }, nil
}

Функция использует глобальный объект из пакета sql — database/sql.Conn, чтобы произвести запрос к какой-то непонятной базе данных; затем глобальный для пакета логгер, чтобы записать строку в непонятном формате в непонятно куда; и глобальный объект пула запросов, чтобы запросить какой-то ресурс. Все эти операции имеют побочные эффекты, которые абсолютно невидимы при чтении сигнатуры функции. У программиста, читающего её, нет способа предсказать ни одно из этих действий, кроме как чтения тела функции и заглядывания в определения глобальных переменных.


Давайте попробуем такой вариант сигнатуры:


func NewObject(db *sql.DB, pool *resource.Pool, n int, logger log.Logger) (*Object, error)

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


Если бы мы разрабатывали публичное API для этого пакета, мы бы могли пойти даже дальше:


// RowQueryer models part of a database/sql.DB.
type RowQueryer interface {
    QueryRow(string, ...interface{}) *sql.Row
}

// Requestor models the requesting side of a resource.Pool.
type Requestor interface {
    Request(n int) (*resource.Value, error)
}

func NewObject(q RowQueryer, r Requestor, logger log.Logger) (*Object, error) {
    // ...
}

Смоделировав каждый конкретный объект как интерфейс, содержащий только методы, которые нам нужны, мы позволили вызывающему коду легко переключаться между реализациями. Это уменьшает связанность между пакетами, и позволяет нам легко мокать (mock) конкретные зависимости в тестах. Тестирование же оригинальной версии кода, с конкретными глобальными переменными, включает утомительную и склонную к ошибкам подмену компонентов.


Если бы все наши конструкторы и функции принимали зависимости явно, нам бы не нужны были глобальные переменные вообще. Взамен, мы бы могли создавать все наши соединения с базами данных, логгеры и пулы ресурсов, в функции main, давая возможность будущим читателям кода очень четко понимать граф компонентов. И мы можем очень явно передавать все наши зависимости, убирая вредную для понимания магию глобальных переменных. Также, заметьте, что если у нас нет глобальных переменных, нам больше не нужна функция init, чья единственная функция это создавать или изменять глобальное состояние пакета. Мы можем затем посмотреть на все случаи использования функций int со справедливым подозрением: что, собственно, это код вообще делает? Если он не в функции main, зачем он?


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


--


И на этом моменте я могу сформулировать теорию современного Go. Отталкиваясь от слов Дейва Чини, я предлагаю следующие правила:


  • отсутствие глобальных переменных в пакетах
  • отсутствие функции init

Конечно, есть исключения. Но следуя этим правилам, остальные практики появляются естественным образом.

Поделиться с друзьями
-->

Комментарии (94)


  1. youROCK
    13.06.2017 13:37
    +4

    Честно говоря, не очень понимаю, каким образом человек от того, что «NewObject» идет в базу, человек опять пришел к идее того, что надо везде всё передавать явно :)? Функцию, которая достает Object из базы, надо просто назвать GetObject, и всё.

    Чтобы писать тесты и создавать моки, достаточно изолировать лишь небольшую часть зависимостей. Например, если функция скачивает что-то из интернета по HTTP, достаточно сделать конфигурируемым URL, куда она идет. После этого вы создаете тестовый HTTP-сервер и легко тестируете этот код.

    То же самое с методом NewObject — я всегда сам был сторонником чистых юнит-тестов (хотя бы из-за того, что они быстрые), но всё же, практика показывает, что намного лучше всё же не пытаться мокать базу, а разрешать коду работать с какой-нибудь временной базой — иначе «протестированный» код на самом деле может и не работать, и вы можете об этом узнать слишком поздно.


    1. divan0
      13.06.2017 14:40

      Честно говоря, не очень понимаю, каким образом человек от того, что «NewObject» идет в базу, человек опять пришел к идее того, что надо везде всё передавать явно :)? Функцию, которая достает Object из базы, надо просто назвать GetObject, и всё.
      Ну да. Если есть объект DB, у которого есть метод GetObject() — то тут никакой магии нет. В статье не описано, но как я понимаю — если мы говорим не о функции, а о методе объекта, то свойства объекта уже явно несут информацию о (возможном) поведении.

      В случае же с функцией конструктором — да, я с автором согласен :)


      Но в целом, база данных тут лишь как пример. Уберите базу и подставьте что угодно — сетевой вызов, обращение к другому пакету/объекту, коммуникация по каналам и тд. Идея в том, что глобальный стейт невидим читателю, без лишних (не всегда простых) телодвижений.


    1. index0h
      14.06.2017 13:37
      +1

      После этого вы создаете тестовый HTTP-сервер и легко тестируете этот код.

      Зачем писать функциональный тест, если для вашего примера unit будет хватать?


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

      Если тестируется именно взаимодействие с БД — вполне резонно, репозитории например. Если тестируется что-то использующее репозитории — базу дергать в абсолютном большинсве случае будет плохой идеей.


  1. poxvuibr
    13.06.2017 13:58
    +2

    А Dependency Injection в Go нет?


    1. eshimischi
      13.06.2017 14:09

      1. poxvuibr
        13.06.2017 14:37

        Насколько я понимаю по линку расскзывают что такое DI и как его реализовать на Go руками. Я видимо неправильно задал вопрос. Мне интересно есть ли в Go средства для автоматизации DI. Что-то эквивалентное Spring или Guice в java.


        1. RidgeA
          13.06.2017 17:02

          есть пакет https://godoc.org/github.com/facebookgo/inject


    1. divan0
      13.06.2017 14:35
      -5

      Магической — нет :)


      1. poxvuibr
        13.06.2017 14:47

        У меня создаётся впечатление, что вы поняли мой вопрос правильно :).
        Получается, что если я описываю в коде, например, дом, со всеми его компонентами включая дверные ручки, то нужно прямо из функции main передать созданные объекты типа "ручка" в то, что делает дом и потом там внутри дома передавать эти объекты в каждый конструктор. И всё это для того, чтобы можно было передать ручку в конструктор двери.


        1. divan0
          13.06.2017 15:00
          -1

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


          1. poxvuibr
            13.06.2017 15:39

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


            1. divan0
              13.06.2017 17:11
              -1

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


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


              type Knob interface {
                  OpenDoor() error
              }

              Тип описывает поведение и дверь работает с ручкой как с объектом, поведение которого она знает:


              type WoodenKnob struct {}
              func NewWoodenKnob() *WoodenKnob { return &WoodenKnob{} }
              func (*WoodenKnob) OpenDoor() error { return nil } // satisfies Knob interface
              ...
              knob := NewWoodenKnob() // returns static type WoodenKnob
              door := NewDoor(knob) // NewDoor expects interface Knob as a paramter

              и какая там ручка на самом деле, деревянная или хрустальная — это уже дереву по барабану, до тех пор пока она удовлетворяет интерфейсу (а это известно на этапе компиляции).


              1. poxvuibr
                13.06.2017 17:22

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


                1. divan0
                  13.06.2017 17:25
                  -2

                  Теперь у вас ответственность за знание о ручке переместилась из двери в какой-то другой класс, который двери делает.
                  С этого надо начинать — кто владеет информацией о том, где какая ручка?


                  1. poxvuibr
                    13.06.2017 17:27

                    С этого надо начинать — кто владеет информацией о том, где какая ручка?

                    Удобно такие вещи вообще в конфигурации задавать. Особенно это удобно для тестов.


                    1. divan0
                      13.06.2017 17:36
                      -1

                      Удобно такие вещи вообще в конфигурации задавать. Особенно это удобно для тестов.

                      Опять же — магия. Глядя на код, непонятно, что с кем работает.


                      Гораздо удобнее, взглянув на код теста увидеть что-то вроде:


                         knob := &MockedKnob{}
                         door := NewDoor(knob)
                         // run door tests

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


                      1. poxvuibr
                        13.06.2017 17:40

                        ИМХО удобно иметь конфигуратор в продакшне и моки в тестах. Я разделяю ваше мнение, что удобно, когда всё явно. Но такой подход часто порождает большое количество явных параметров, которые класс сам не использует, а просто прокидывает непонятно кому. Для этого прокидывания тоже надо писать явный код. И это тяжело.


                        1. divan0
                          13.06.2017 17:48
                          -4

                          Это действительно может быть проблемой, если писать код на Go в стиле класс-ориентированных языков. В Go очень редко можно увидеть проекты, в которых "иерархия" выходит за рамки 4-5 уровней вложенности. Embedding, интерфейсы, удобная CSP коммуникация позволяет строить сложные архитектуры без бесконечных уровней вложенности. Возможно я ещё что-то упускаю, но вот описанной вами беды, которую я регулярно видел в С++ проектах, в Go я не наблюдаю.


              1. poxvuibr
                13.06.2017 17:26

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

                Из не надуманных примеров у нас есть логгер, который теперь будет параметром любого класса вообще :). И логгером дело явно не кончится.


                1. divan0
                  13.06.2017 17:30
                  -1

                  Ну вот про логгер я чуть выше же писал.
                  Да, собственно выбор между двумя вариантами — использовать глобальный стейт или передавать каждому уровню абстракции. Некоторые используют context.Context как механизм передачи вниз по иерархии таких вещей как логгер, но их за это бьют по рукам.


                  1. poxvuibr
                    13.06.2017 17:37

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

                    Ещё есть вариант использовать конфигурируемую фабрику. Тоже глобальный стейт, конечно, но не засоряет код. В джаве с этим полегче — инфраструктура налажена.
                    Вообще на тему DI есть хорошее выступление, пусть и древнее. Возможно, вы видели, но я на всякий случай кину ссылку . Оттуда, кстати двери и ручки.


                    1. divan0
                      13.06.2017 17:41

                      Тоже глобальный стейт, конечно, но не засоряет код

                      А засоряет другое место (конфиг DI), магически меняя код. Всё то, что в Go любят :)


                      За ссылку спасибо.


                      1. poxvuibr
                        13.06.2017 18:10

                        Всегда пожалуйста. Вот ещё про глобальный стейт и синглтоны. Тоже в разрезе DI. Это выступление наверное даже более в резонансе со статьёй, чем то, что в предыдущей ссылке.


  1. telhin
    13.06.2017 14:16
    +4

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


    С другой стороны требовать применения правил, когда явных ограничений нет — бессмысленно.
    Заставлять программиста придерживаться строгой чистоты в коде как парадигмы можно сколько угодно, но без кнута в виде компилятора/линтера, которые будут контролировать программиста просто бессмысленно.


    Функционал контроля чистоты можно вывести в специальные директивы компилятора, в итоге язык выиграет в надежности, но потеряет в простоте, это выливается в вопрос "что важнее?".


    Я не спорю, что практики описанные в статье имеют свои преимущества, но применять повсеместно их конечно никто не будет.


    1. divan0
      13.06.2017 14:46
      -1

      но без кнута в виде компилятора/линтера, которые будут контролировать программиста просто бессмысленно.

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


      Вот взять, к примеру, тот же способ делать конструкторы (которые, вобщем-то не всегда нужны, и, которые по сути, просто функции, поэтому в Go и нет специальной магии для конструкторов) — имя функции начинается с New и возвращает указатель на создаваемый тип. Этот паттерн везде, он никак не энфорсится компилятором — программист волен писать CreateObject(), ConstructObject(), изобретать велосипеды вроде (Object) Construct() Object и тд, но, почему-то все используют именно подход с New. Возможно, потому что он просто проще и понятней, а может потому что, благодаря compatibility promise, туториалы по Go остаются актуальными на много лет, и это ускоряет распространение и укрепление best practices и вот таких негласных соглашений. Интересная тема, вобщем.


    1. youROCK
      13.06.2017 15:01

      но применять повсеместно их конечно никто не будет

      Повсеместно, может, и не будет, но в отдельных проектах — пожалуйста :). Посмотрите на juju, например: https://github.com/juju/juju/blob/01b24551ecdf20921cf620b844ef6c2948fcc9f8/apiserver/hostkeyreporter/shim.go


    1. SirEdvin
      13.06.2017 19:19
      -2

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

      Python вот как-то живет с pep8 и все норм.


  1. SirEdvin
    13.06.2017 19:21
    +1

    Самое главное и лучшее свойство Go это то, что он, по-сути, антимагический.

    Язык у которого нет комментариев (а только метаинформация) и который обладает такой ужасной фичей как "кодогенерация" не может быть антимагическим.


    Для примера, вот интеграция C в GO. Это магия кодогенерации и метаинформации о функциях.


    1. divan0
      13.06.2017 19:39

      Язык у которого нет комментариев

      А куда они делись? Сегодня ещё были, а, поскольку у языка promise of compatibility, не думаю, что комментарии убрали :)


      Для примера, вот интеграция C в GO

      cgo это не Go. Это грамотное решение, которое было нужно на первых этапах популяризации языка, когда нативных библиотек для всего на свете ещё не было.


      1. SirEdvin
        13.06.2017 20:03

        А куда они делись? Сегодня ещё были, а, поскольку у языка promise of compatibility, не думаю, что комментарии убрали :)

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


        cgo это не Go. Это грамотное решение, которое было нужно на первых этапах популяризации языка, когда нативных библиотек для всего на свете ещё не было.

        Это подход от людей, которые разрабатывают Go, который ясно показывает, что комментариев в этом языке нет, только метаинформация.


        В Python есть разница между этими двумя вещами, в Java для таких вещей есть аннотации, а в go просто нет комментариев.


        1. youROCK
          13.06.2017 21:16

          Да, в го решили объединить. Но на самом деле комменты отличаются от директив компилятора пробелом в начале :). Другой вопрос, что это решение, скажем так, немного нестандартное :)


          1. SirEdvin
            13.06.2017 22:02

            Точно? В том же cgo вот тут есть пробелы в начале:


            // #include <stdio.h>
            // #include <errno.h>
            import "C"

            А в примере по go generate уже нет


            //go:generate go tool yacc -o gopher.go -p parser gopher.y

            Иными словами, наличие проблема вряд ли спасает.


        1. divan0
          14.06.2017 02:11
          -2

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

          Каким это образом подключенная библиотека "приймет комментарии за инструкции".


          Небольшой ликбез про директивы компилятора в Go:


          • директивы компилятора доступны только компилятору и статическим анализаторам кода
          • директивы компилятора в 99.9% случаев используются внутренне в коде самого Go
          • директивы компилятора в Go имеют специальный и очень явный формат //go:something
          • этих директив три с половиной штуки
          • единственная публично известная и используемая директива это //go:generate


          1. SirEdvin
            14.06.2017 08:26
            +1

            У меня есть пример одной библиотеки, которая поставлялась авторами языка — это cgo, что бы сказать, что авторы языка поддерживают такой подход, а значит он имеет место быть.


            Все остальное, вроде "это уже не используется", "это не нужно" и прочее — демагогия. Прецентедент есть и от него уже не отмытся. Ведь когда кровавый энтерпрайз таки придет за Go, он будет использовать самые темные, но гибкие его места, как это произошло с Python и Java.


            И кстати, возможно, вы мне подскажите, как работаю такие структуры:


            `inject:""`
            `inject:"private"`
            `inject:"dev logger"`

            Это же не комментарии?


            1. divan0
              14.06.2017 11:44
              -1

              У меня есть пример одной библиотеки, которая поставлялась авторами языка — это cgo

              Было бы проще, если бы вы сначала изучили тему :)
              cgo это не библиотека, это изначально заложенная в язык — компилятор, тулинг, рантайм — интеграция с С.


              Прецентедент есть и от него уже не отмытся.

              Ну вам виднее.


              Это же не комментарии?

              Нет, это тэги структур :) Давайте так, вы сначала познакомитесь с темой разговора, а потом начнёте осуждать, а не наоборот.


              1. SirEdvin
                14.06.2017 17:11

                Нет, это тэги структур :) Давайте так, вы сначала познакомитесь с темой разговора, а потом начнёте осуждать, а не наоборот.

                А ничего, что я как раз это не приводил в пример?) Именно по той причине, что вы назвали.


  1. RidgeA
    13.06.2017 20:52

    SirEdvin

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


    Можете объяснить что вы имеете ввиду? Потому что согласно спецификации языка комментарии таки есть — https://golang.org/ref/spec#Comments

    P.S. Промахнулся…


    1. alhimik45
      13.06.2017 21:21
      +1

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


      1. RidgeA
        13.06.2017 21:35

        Спасибо, я так понял речь об этом https://golang.org/cmd/compile/#hdr-Compiler_Directives


        1. SirEdvin
          13.06.2017 22:00

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


          1. divan0
            14.06.2017 02:16
            -2

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


            1. SirEdvin
              14.06.2017 08:28
              -1

              На любой такой другой случай так же можно сказать "это был специальный случай". Говорить, что код на C/C++ нужен на заре языка — это как минимум немного приукрашивать. Все платформо ориентированные методы и вещи, требующие дополнительной производительности будут писатся на C/C++.


              Ну а так больше интерграции с компилируемыми языками я не нашел.


              1. Edison
                14.06.2017 11:44

                Все платформо ориентированные методы и вещи, требующие дополнительной производительности будут писатся на C/C++.

                не будут, уже все давно переписано на Go.


                1. SirEdvin
                  14.06.2017 17:12

                  не будут, уже все давно переписано на Go.

                  Вы хотели сказать, уже написаны все обертки над C/C++, как например, для systray?


                  1. divan0
                    14.06.2017 18:26
                    -1

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


                    Или вы предпочитаете cherry-picking, confirmation bias и прочие логически ошибки, чтобы формировать свое видение?


                    1. SirEdvin
                      14.06.2017 18:33

                      Окей, я вижу вы разбираетесь в теме.


                      Расскажите тогда, пожалуйста, как можно реализовывать платформо-ориентированые методы, такие как работа c winapi, systray и прочими плюшками без нативных c/c++ биндов?


                      Я вот знаю только один способ — это через c++, или через импорт библиотек, или через связку нативных функций, если таких библиотек нет, или они тебя не устраивают.


                      Я что-то упустил?


                      Например, вот вам либа для работы с mpi: https://github.com/JohannWeging/go-mpi


                      1. divan0
                        14.06.2017 19:27
                        -1

                        Расскажите тогда, пожалуйста, как можно реализовывать платформо-ориентированые методы, такие как работа c winapi, systray и прочими плюшками без нативных c/c++ биндов?

                        Как уже не раз говорилось, Go был создан для бекендов, серверов, клауда и все что около. Десктопный софт, в котором приходится плясать и выкручиваться вокруг фантазий и legacy-наследия целого зоопарка разработчиков — это, мягко говоря, не ниша Go. Не говоря уже о том, что десктопный софт уже давно не в теме, и интереса к этой нише нет. Тоесть что-то, конечно делают, и в embedded Go зашел, и UI интерфейсы пытаются пилить, но это не основная ниша Go, поэтому трейдофф в виде "использовать биндинг для вот той проприетарной никому уже почти не нужной технологии", обычно вполне себе работает.


                        1. SirEdvin
                          15.06.2017 10:48
                          +1

                          И разумеется, не один популярный проект, который повсеместно используется не использует cgo… oh wait, а как же docker?


                          Там куча примеров использование cgo.


                          1. divan0
                            15.06.2017 13:13

                            Вы пытаетесь сказать, что docker использует cgo, потому что на Go мало библиотеке? Хахаха. Docker использует cgo, потому что он настолько зависит от низкоуровневого взаимодействия с системой и ядром, что у вас уши на лоб полезут, когда узнаете обо всех вызовах, с которыми сталкиваются разработчики docker-а (у меня лезли, когда знакомый из Docker-а рассказывал).


                            Я не понимаю, ну блин, зачем набрасывать, не разбираясь в теме. Вы от этого что, удовольствие получаете? Вам платят за это?


                            1. SirEdvin
                              15.06.2017 13:47
                              +1

                              Небольшая шутка на тему

                              image


                              1. divan0
                                15.06.2017 14:01

                                Я не прав?

                                Сожалею.


                                Вы сделали в корне ложное утверждение "в Go нет комментариев", основанное на:


                                а) незнании Go
                                б) ложных предположениях ("никто не знает, как библиотека будет использовать комментарии", хахаха)
                                в) сильнейшем confirmation bias-е, который сквозит через все комментарии.


                                И даже после ликбезов по Go лично для вас, вы продолжаете хвататься за каждый шанс, чтобы хоть где-то попытаться набросить и хотя бы частичку вашего неверного утверждения "отвоевать".


                                Черт, на что я трачу свое время. Развлекайтесь дальше :)


                                1. SirEdvin
                                  15.06.2017 14:08

                                  а) Пруфы? Я не использовал знаний, которых у меня по go нет. Какие-то базовые есть, вот мне их и хватило.
                                  Какие же знания позволят мне разглядеть то, чего нет?
                                  б) Из опыта работы над проектом даже из 3 людей становится понятно, что довольно часто разработчики не знают всех библиотек, которые используется на проекте.
                                  в) Потому что я показываю частные случаи. Разумеется, никто не будет на постоянной основе заниматся такой фигней, но частных случаев хватает.


                                  И если вы не знаете, что такое комментарии, то я напомню:


                                  Коммента?рии — пояснения к исходному тексту программы, находящиеся непосредственно внутри комментируемого кода. Синтаксис комментариев определяется языком программирования. С точки зрения компилятора или интерпретатора, комментарии — часть текста программы, не влияющая на её семантику. Комментарии не оказывают никакого влияния на результат компиляции программы или её интерпретацию. Помимо исходных текстов программ, комментарии также применяются в языках разметки и языках описания.

                                  И вот самое главное:


                                  Комментарии не оказывают никакого влияния на результат компиляции программы или её интерпретацию

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


                                  1. Edison
                                    15.06.2017 14:15

                                    тот C код не будет комментарием только тогда, когда сразу же после этого блока будет идти `import «C»`
                                    Вот cgo:

                                    // #include <stdio.h>
                                    // #include <errno.h>
                                    import "C"
                                    


                                    Вот комментарий просто:
                                    // #include <stdio.h>
                                    // #include <errno.h>
                                    
                                    import "C"
                                    


                                    1. SirEdvin
                                      15.06.2017 14:20

                                      Окей, а тут значит я читал одним местом.
                                      Спасибо)


                                      1. Edison
                                        15.06.2017 14:27

                                        могу сделать вывод, что вы не читали совсем, потому что это написано в самом начале доки про cgo — https://golang.org/cmd/cgo/

                                        If the import of «C» is immediately preceded by a comment, that comment, called the preamble, is used as a header when compiling the C parts of the package.


                                        1. SirEdvin
                                          15.06.2017 14:29

                                          К сожалению, читал, но вот начиная с первого примера(


                                          В целом, мое замечение про остуствие комментариев это не снимает, но тот факт что я зашкварился остается :(


                                  1. divan0
                                    15.06.2017 14:19

                                    а) см. свои комментарии выше
                                    б) што?
                                    в) нет, я не про ваш cherry-picking bias, я про другой — confirmation bias. у вас еще пачка логических ошибок в комментариях, но эти два самые кричащие


                                    И если вы не знаете, что такое комментарии

                                    Да ну, правда?


                                    Комментарии не оказывают никакого влияния на результат компиляции программы или её интерпретацию. Как мы уже выяснили выше, "комментарии", которые задаются при помощи "//" не отвечают этому требованию.
                                    99.999999% комментарие в Go — это просто комментарии, они никак не влияют на результат компиляции. Специальные случаи. которые очень хорошо описаны, известны, очень ограничены в использовании, позволяют статическим анализаторам извлекать из них дополнительный профит. Это не меняет того факта, что все остальные комментарии остаются комментариями.

                                    Но раз в вашем бинарном мире это означает "в Go комментариев нет", то не буду лишать вас иллюзий. Других людей только не вводите в заблуждение.


                                    1. SirEdvin
                                      15.06.2017 14:24
                                      -2

                                      Но раз в вашем бинарном мире это означает "в Go комментариев нет", то не буду лишать вас иллюзий. Других людей только не вводите в заблуждение.

                                      Эм… что?


                                      Определение комментария четкое и не дает пространства для двуяких толкований.


                                      Комментарии должны никак не влиять на программу. Если они влияют, они не комментарии.


                                      Бинарный мир тут не причем, я просто следую определению. Нельзя быть наполовину беременным.


                                      1. divan0
                                        15.06.2017 14:26

                                        Нельзя быть наполовину беременным.

                                        Ок.


                                      1. kenik
                                        15.06.2017 14:30
                                        +2

                                        #!/bin/bash
                                        

                                        Это комментарий? Или тут тоже нет комментариев?


                  1. Edison
                    14.06.2017 20:10
                    +1

                    я имел ввиду стандартную библиотеку и рантайм — там чисто Го.
                    Вы выбрали специфическую область. Я не знаю про winapi и systray, но, как понимаю, винапи имеет Сишный интерфейс, потому это очевидно, что все реализации должны быть обертками надо Си. К тому же у Го свое место и это не десктопные виндовые приложения.

                    Если взять линуксовое апи, то Го не использует libc, у него своя реализация без C.


                    1. SirEdvin
                      15.06.2017 10:48
                      -1

                      Вы согласны с тем, что всякие клауд штуки, типо docker — это место для Go? Там тоже куча всякий вставок с c++/c.


                      1. Edison
                        15.06.2017 11:08

                        куча? во всем проекте вместе с вендорами С используется 47 раз.


                        1. SirEdvin
                          15.06.2017 11:12
                          -1

                          Выкинул вендоров, итого в docker cgo используется в таких частях системы, как (судил по названиям, простите, если ошибся):


                          • Обработка системных вызовов
                          • Работа с примонитрованием папок
                          • Абсолютно всей логики работы демона
                          • Работы с оперативной памятью.

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


                          1. Edison
                            15.06.2017 11:26
                            +1

                            и почти все используется только для в специфических местах:
                            1. для работы под солярисом и фрибсд
                            2. для работы с журналд.


                    1. SirEdvin
                      15.06.2017 13:47

                      Кстати, я не очень разбираюсь в теме, но вот это это же екстра библиотеки?


                      1. Edison
                        15.06.2017 16:51

                        это разрабатывается той же командой, что и язык и это часть проекта Go, иногда либы с golang.org/x/ включают в релиз языка, как случилось с context, http2.
                        Еще там много тулинга для Go, типа guru, golint


                1. ufm
                  14.06.2017 18:34

                  Нативную 0mq библиотеку не подскажите?
                  Или sqlite?


                  1. divan0
                    14.06.2017 18:58
                    -1

                    https://github.com/zeromq/gomq
                    https://github.com/iamacarpet/go-sqlite3-win64 (вызов к оригинальной .so/.dll без cgo)


                    Хотя с sqlite проблема в том, что sqlite по своей природе serverless, поэтому "реализация клиента" означает "переписать весь движок и поддерживать его в синхронизации с оригинальным", и учитывая объем кода (там 200 тысяч строк кода на C), и тот факт что а) для go есть пачка альтернатив embedded dbs вроде BoltDB (я использую в продакшене её, к примеру), б) cgo решение многие используют уже много лет в продакшене и никаких проблем не испытывают, сильного стимула переписывать и нет, увы.


                    1. ufm
                      14.06.2017 19:05
                      +2

                      Первое — неполная реализация.
                      Второе — не нативная, так как требует родной библиотеки.

                      Ситуация «Всё переписано на нативном го» не наступит никогда. Причём причину Вы сами написали.


                      1. divan0
                        14.06.2017 19:24
                        -2

                        Ситуация «Всё переписано на нативном го» не наступит никогда. Причём причину Вы сами написали.

                        Все верно. Автор выше под фразой "все уже переписано" подразумевал "всё, что востребовано и не имеет альтернатив". Вы, конечно, можете гнуть свою линию, но я варюсь в мире Go уже 4 года и компании, которые используют что-то с cgo — это единичные случаи, это настолько мизерный процент, что мне даже смешно, когда cgo пытаются представить таким вот монстром-стоппером :)


              1. divan0
                14.06.2017 11:50
                -2

                Все платформо ориентированные методы и вещи, требующие дополнительной производительности будут писатся на C/C++.

                Мне кажется вы не очень хорошо понимаете перформанс Go и плохо знакомы с экосистемой и проектами на Go, отсюда и делаете неверные выводы. Учитывая то, что с Go вы даже не знакомы, это неудивительно.


                Ну а так больше интерграции с компилируемыми языками я не нашел.

                Странно, вы же чуть выше уверяли, что "Прецентедент" был, и Go обречен.


                1. SirEdvin
                  14.06.2017 17:15

                  Мне кажется вы не очень хорошо понимаете перформанс Go и плохо знакомы с экосистемой и проектами на Go, отсюда и делаете неверные выводы. Учитывая то, что с Go вы даже не знакомы, это неудивительно.

                  Я отлично знаком с логикой работы программ, что бы сказать, что абсолютно все вещи, ориентированые на платформу или требующие использования особенностей платформы пишутся или с использованием C/C++, или с использованием оберток над ними.


                  То, что есть проекты, которые сделали это за вас — это не значит, что оно переписано на Go.


                  И так же, я отлично понимаю, что правильно написанная программа на C будет быстрее правильно написанной программы на Go, что бы вы не делали. Другое дело в скорости написания такой программы.


                  Странно, вы же чуть выше уверяли, что "Прецентедент" был, и Go обречен.

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


                  1. divan0
                    14.06.2017 18:34
                    -1

                    Я отлично знаком с логикой работы программ, что бы сказать, что абсолютно все вещи, ориентированые на платформу или требующие использования особенностей платформы пишутся или с использованием C/C++, или с использованием оберток над ними.

                    Ошибаетесь. В Go вещи, которые действительно требуют повышения производительности на порядок (например криптографических функций) обычно делает на ассемблере, потому что когда речь заходит о производительности того же порядка, что и С++, то это достигается в худшем случае с помощью стандартных приёмов оптимизации Go кода, вроде пулов памяти, оптимизации выравнивания структур.


                    и коммьюнити окажется лучше, чем я о нем думаю

                    Хаха. Я передам коммьюнити. Там всем очень важно ваше мнение. Как вас представить?


                    1. SirEdvin
                      14.06.2017 18:35

                      Ошибаетесь. В Go вещи, которые действительно требуют повышения производительности на порядок (например криптографических функций) обычно делает на ассемблере, потому что когда речь заходит о производительности того же порядка, что и С++, то это достигается в худшем случае с помощью стандартных приёмов оптимизации Go кода, вроде пулов памяти, оптимизации выравнивания структур.

                      А что насчет C? Ведь абстрации все равно чего-то да стоят.


                      1. divan0
                        14.06.2017 19:09
                        -2

                        Какие абстракции? Основные потери перформанса в сравнении с сырым С это а) выделение памяти б) работа GC, но эти потери, мягко говоря, не на порядок, тот же масштаб. Memory layout в Go очень близок к С, поэтому все основные операции и манипуляции (создание объектов, вызов функций/методов и тп) опять же, генерируют код на ассемблере очень близкий к аналогичному в С… Интерфейсы и коммуникация через каналы, безусловно, несут некоторый оверхед, но снова же, это наносекунды, а не разница на порядки, как в случае с питоном или нодой.


                        При этом, в Go ещё достаточно места для оптимизаций, и это один из главных акцентов в каждом релизе — вот SSA бекенд появился один релиз назад и только этим добавил +15% сырого перформанса, хотя весь его потенциал ещё не задействован.


                        Ну а правда в том, что 80% современного бекенд программирования не требует того перформанса, который даёт Go. Именно поэтому люди продолжают писать бекенды на более медленных решениях, и ничего, живут. Но там где нужен перформанс, и при этом отсутствие мозговыносительства С++ или Rust, Go это оптимальный выбор.


                        1. SirEdvin
                          15.06.2017 10:45
                          +2

                          там где нужен перформанс, и при этом отсутствие мозговыносительства С++ или Rust, Go это оптимальный выбор.

                          То есть у нас тут только выбор между мозговыносительством C++/Rust и Go. Понятно, спасибо.


                      1. Edison
                        14.06.2017 22:42
                        -1

                        А что насчет C? Ведь абстрации все равно чего-то да стоят.

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


                        1. SirEdvin
                          15.06.2017 10:44

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

                          Накладные расходы на всякие интерфейсы и прочее куда-то делись? Понятное дело, что это в целом капля в море, но они есть.


                          Нативный код не панацея.


                          1. Edison
                            15.06.2017 10:56

                            и чего же стоят интерфейсы в Go?


                            1. SirEdvin
                              15.06.2017 11:05

                              Думаю, тут нужно проводить нормальное исследование)


                              1. divan0
                                15.06.2017 13:09
                                +1

                                Так их миллион, и их легко провести самому — бенчмаркинг встроенный в Go. Как бы было проще, если бы вы сначала изучали вопрос, а потом набрасывали. Всем бы нервы и время сэкономили.


                                1. SirEdvin
                                  15.06.2017 13:48
                                  -1

                                  О да, ведь бенчмарк это такое релевантное исследование. И причем именно в том, сколько стоят интерфейсы.


                                  1. divan0
                                    15.06.2017 13:56
                                    +1

                                    Порелевантнее мнения анонимуса, который не знает, как они устроены, не видел цифр, но считает это достаточным, чтобы что-то доказывать и набрасывать :)


                                    1. SirEdvin
                                      15.06.2017 13:57

                                      Учитывая, что бенчмарки делаются непонятно как, непонятно чем и непонятно что меряют (и делаются, кстати, такими же анонимусами), то вы ошибаетесь.


                                      1. divan0
                                        15.06.2017 14:02

                                        Так это вам непонятно, потому что вы не потрудились узнать. Остальным понятно. Учите матчасть.


                            1. JekaMas
                              15.06.2017 11:11

                              Я гошник и люблю го, но интерфейсы стоят дорого, весьма дорого, поэтому с месяц стоит issue на оптимизацию itab


                              1. Edison
                                15.06.2017 11:42

                                да, дорого стоят. Но опять же — это удобства за которые нужно платить.
                                Можно и без них код писать.


                              1. divan0
                                15.06.2017 13:08

                                А что за оптимизация, поделитесь ссылкой :)


                                1. JekaMas
                                  15.06.2017 13:29
                                  +1

                                  Да, конечно. Там очень интересное обсуждение и код.


                                  1. divan0
                                    15.06.2017 13:45

                                    Круто, спасибо. Справедливости ради, там специфический кейс, у них 30000 автосгенерированных proto.Message интерфейсов.


  1. deadkrolik
    14.06.2017 12:24

    отсутствие глобальных переменных в пакетах

    Не совсем. Иногда это необходимо, например, при создании кастомных ошибок. В пакете могут быть объявлены:


    var ErrNotFound := errors.New("not found")
    var ErrInvalidRequest := errors.New("invalid request")

    И тогда вызывающий может легко понимать что за ошибка была возвращена и действовать в зависимости от типа ошибки.


    1. youROCK
      14.06.2017 13:09
      +1

      Там Dave Cheney всё предусмотрел и предлагает делать константы ошибок вместо переменных (основная претензия именно к возможности любого другого кода эти переменные случайно или специально модифицировать): https://dave.cheney.net/2016/04/07/constant-errors


      1. divan0
        14.06.2017 13:29
        +1

        Собственно, у него есть ответ на данную статью Питера :)
        https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables