Написав несколько проектов на Go, я оглянулся назад. Посмотрел на язык, на его плюсы и минусы. В этой статье хотелось бы поговорить о том, за что критикуют Go. Конечно же, речь пойдет об отсутствии ООП как такового, перегрузки методов и функций, обобщенного программирования и исключений. Действительно ли это доставляет столько проблем? Или это проблема подхода разработки? Я поделюсь своим опытом решения этих вопросов.

Проблематика


Я стал программировать на Go после Java и PHP. И сейчас расскажу почему.

Java классная штука. У нее приятный синтаксис. Многие крупные проекты используют его в бою. Все было бы круто, если не JVM. Для того, чтобы развернуть серьёзное приложение на Java, вам понадобится тачка с большим количеством оперативной памяти. И это совершенно не годится для стартапов(в моем случае).

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

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

Go в этом плане не исключение. Выдавая неплохую производительность и экономно расходуя ресурсы, язык требует от разработчика особого подхода. Если подходить к разработке приложений на Go, как на Java, то тогда действительно возникнут трудности. Мы сталкиваемся с отсутствием таких привычных вещей, как ООП, перегрузка методов и функций, обобщенное программирование, а также исключений.

Когда нет привычных решений, на смену им приходят новые, но не менее эффективные: гибкая система типов, интерфейсы, разделение данных и поведения. Сейчас я продемонстрирую все на примерах.

Перегрузка методов и функций


В Go нет перегрузки методов и функций. Предлагается просто давать разные имена методам и функциям.

func SearchInts(a []int, x int) bool
func SearchStrings(a []string, x string) bool

Иной подход — это использование интерфейсов. Для примера создадим интерфейс и функцию для поиска:

type Slice interface {
    Len() int
    Get(int) interface{}
}

Search(slice Slice, x interface{}) bool

Теперь достаточно создать два типа:

type Ints []int
type Strings []string

И реализовать интерфейс в каждом из типов. После этого можно использовать поиск и по строкам и по числам:

var strings Strings = []string{"one", "two", "three"}
fmt.Println(Search(strings, "one")) // true
fmt.Println(Search(strings, "four")) // false

var ints Ints = []int{0, 1, 2, 3, 4, 5}
fmt.Println(Search(ints, 0)) // true
fmt.Println(Search(ints, 10)) // false

ООП


В Go нет того ООП, к которому мы так привыкли. ООП в Go это по сути встраивание типов с возможностью перегрузки методов родителя методами потомка. Пример:

// родитель
type A struct {}

// может быть переопределен в потомке
func (a *A) CallFirst() {
    fmt.Println("A CallFirst")
}
// потомок
type B struct {
    A
}

// переопределяем метод в потомке
func (b *B) CallFirst() {
    fmt.Println("B CallFirst")
}

a := new(A)
a.CallFirst() // "A CallFirst"
b := new(B)
b.CallFirst() // "B CallFirst"

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

// метод со сложной логикой
func (a *A) CallSecond() {
    fmt.Println(a.GetName(), a.GetMessage())
}

// может быть переопределен в потомке
func (a *A) GetName() string {
    return "A"
}

// может быть переопределен в потомке
func (a *A) GetMessage() string {
    return "CallSecond"
}

// переопределяем метод в потомке
func (b *B) GetName() string {
    return "B"
}

a.CallSecond() // “A CallSecond”
b.CallSecond() // “A CallSecond”, а нужно “B CallSecond”

Я выбрал для себя такое решение — создаем и реализуем интерфейс для родителя и потомка. При вызове сложного метода передаем ссылку на интерфейс и в родителе и в потомке:

// создаем интерфейс
type SuperType interface {
    GetName() string
    GetMessage() string
    CallSecond()
}

// метод со сложной логикой
func (a *A) сallSecond(s SuperType) {
    fmt.Println(s.GetName(), s.GetMessage())
}

// реализуем метод интерфейса в родителе
func (a *A) CallSecond() {
    a.callSecond(a)
}

// реализуем метод в потомке
func (b *B) CallSecond() {
    b.callSecond(b)
}

a.CallSecond() // “A CallSecond”
b.CallSecond() // “B CallSecond”

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

// создадим еще одного потомка
type C struct {
    A
}

func (c *C) GetName() string {
    return "C"
}

func (c *C) CallSecond() {
    c.callSecond(c)
}

// функция, которая должна работать с A, B и C
func DoSomething(a *A) {
    a.CallSecond()
}

DoSomething(a) 
DoSomething(b) // ошибка, не тот тип
DoSomething(c) // ошибка, не тот тип

Переделаем функцию DoSomething так, что бы она принимала интерфейс:

// функция, которая должна работать с A, B и C
func DoSomething(s SuperType) {
    s.CallSecond()
}

DoSomething(a) // “A CallSecond”
DoSomething(b) // “B CallSecond”
DoSomething(c) // “C CallSecond”

Таким образом мы отделяем данные от поведения, что является хорошей практикой.

Обобщенное программирование


В Go все таки есть обобщенное программирование и это interface{}. Опять же это непривычно, т.к. нет синтаксического сахара, как в Java.

ArrayList<String> list = new ArrayList<>();
String str = list.get(0);

Что же мы получаем в Go?

type List []interface{}

list := List{"one", "two", "three"}
str := list[0].(string)

На мой взгляд разница не велика! Если же использовать интерфейсы, то можно избежать явного приведения типов. Приведу пример:

// создаем интерсейсы
type Getter interface {
    GetId() int
}

type Setter interface {
    SetId(int)
}

// обобщаем интерфейсы
type Identifier interface {
    Getter
    Setter
}

// создаем новый список
type List []Identifier

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

// реализует Identifier
type User struct {
    id int
}

// реализует Identifier
type Post struct {
    id int
}

func assign(setter Setter, i int)
func print(getter Getter)

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

list := List{new(User), new(User), new(Post), new(Post), new(Post)}
for i, item := range list {
    assign(item, i)
    print(item)
}

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


Блок try/catch отсутствует в языке, вместо этого методы и функции должны возвращать ошибку. Кто то считает это недостатком языка, кто то нет. Есть люди, которые принципиально не используют try/catch, т.к. это медленный блок. Как правило хватает стандартной обработки ошибок:

func Print(str string) error

if Print(str) == nil {
    // делаем что то еще
} else {
    //обработка ошибки
}

Если же нужна сложная обработка ошибок, как в блоке try/catch, то можно воспользоваться следующим приемом:

switch err := Print(str); err.(type) {
case *MissingError:
    // обработка ошибки
case *WrongError:
    // обработка ошибки
default:
    // делаем что то еще
}

Таким образом с помощью конструкции switch можно свести в минимуму отсутствие try/catch.

Подводя итоги


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

P.S.: полный код примеров доступен здесь.

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


  1. lair
    27.04.2016 13:21
    +20

    В Go все таки есть обобщенное программирование и это interface{} Опять же это непривычно, т.к. нет синтаксического сахара, как в Java.

    Эм, вы правда не знаете, что обобщенное программирование — это не синтаксический сахар, а interface{} — это не обобщенное программирование?


    1. andrewnester
      27.04.2016 14:03
      +3

      наверное неправильное понимание, что такое interface{} — одна из самых больших ошибок начинающих «гоферов»

      оставлю тут ссылочку


      1. asolomonoff
        27.04.2016 15:30
        -4

        в одном из своих докладов Роб Пайк говорил, что в Go есть обобщенное программирование и это interface{}


        1. Sirikid
          28.04.2016 00:47
          +2

          Роб тролль и не особо скрывается


    1. asolomonoff
      27.04.2016 15:28

      слушал курс по java от mail.ru, в нем говорится, что:

      ArrayList<String> list = new ArrayList<>();
      

      конструкция для программиста, но для JVM это:

      ArrayList<Object> list = new ArrayList<>();
      

      так что ничем иным, как синтаксическим сахаром это назвать нельзя


      1. lair
        27.04.2016 15:33
        +4

        То, что в Java это так, совершенно не означает, что в других языках это так же. В частности, в каком-нибудь банальном С# List<int> — это именно List<int>, и никаких кастов (и никакого боксинга/анбоксинга, связанного с кастами) в рантайме не происходит.


        1. asolomonoff
          27.04.2016 15:46

          согласен, но я и не говорил же про другие языки


          1. lair
            27.04.2016 15:49
            +9

            Вы говорили, что в Go есть обобщенное программирование — на основании того, что оно реализовано "как в Java, но без синтаксического сахара". Проблема в том, что то, что осталось (а именно — касты всего и вся к/из interface{}, он же object, он же void *) — это не обобщенное программирование.


            1. asolomonoff
              27.04.2016 16:10
              -4

              Про interface{} — это слова создателей языка, не мои) Должно быть, я не точно выразился( Основная мысль была такова, что если мы объявляем списки или карты, которые используют интерфейс(Identifier, не interface{}), то в такие структуры данных мы можем положить любой тип, реализующий этот интерфейс. Кроме того методы и функции смогут работать с такими структурами данных без кастов


              1. lair
                27.04.2016 16:17
                +9

                Про interface{} — это слова создателей языка, не мои

                Пока что в факе про язык написано, что дженериков в нем нет. Так что я не уверен, что вы правильно понимаете "слова создателей языка".


                Основная мысль была такова, что если мы объявляем списки или карты, которые используют интерфейс(Identifier, не interface{}), то в такие структуры данных мы можем положить любой тип, реализующий этот интерфейс

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


                1. asolomonoff
                  27.04.2016 16:59
                  -2

                  Так что я не уверен, что вы правильно понимаете «слова создателей языка»

                  статья ребят из яндекса. В заключении статьи написано, про дженерики. Говорю же, не я это придумал)))

                  Мне кажется, вы упускаете из виду тот факт

                  Полностью с вами согласен — списки и словари дженерики. Я говорю про другое:
                  template <typename T> bool eq(T x, T y)
                  

                  в Go
                  type Equaler interface {
                      IsEq(Equaler) bool
                  }
                  
                  func Eq(x, y Equaler) bool
                  
                  type IntEqualer int
                  type StringEqualer string
                  // и т.д.
                  


                  1. lair
                    27.04.2016 17:05
                    +2

                    статья ребят из яндекса. В заключении статьи написано, что дженерики.

                    Правда? Цитата:


                    То, чего не хватает в первую очередь мне — это generics. [...] Действительно, какой-то generic code можно писать с использованием интерфейсов, но в части случаев этого не хватает.

                    Я говорю про другое


                    Угу. Попробуйте с помощью этого "другого" реализовать банальную монаду Maybe (она же Option[T]). Естественно, для всех T. Или, оставаясь в пределах вашего примера, описать следующее: нам нужен метод Sort<T>(T[] data, Comparer<T> comparer), который принимает на вход массив (ура, они уже дженеричные) и компаратор (который бы работал с тем же типом, которого массив).


                    1. asolomonoff
                      27.04.2016 17:35

                      Sort(T[] data, Comparer comparer)

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


                      1. lair
                        27.04.2016 17:44
                        +3

                        я думаю, что такой же гибкости в go не добиться, увы

                        Гибкость тут ни при чем. Это просто наглядная демонстрация, зачем бывают нужны дженерики помимо навязших всем в зубах контейнеров.


                        Но все же некую обобщенную реализацию можно написать.

                        Без кастов? Я бы с любопытством посмотрел на это.


                        1. asolomonoff
                          27.04.2016 17:53

                          Это просто наглядная демонстрация, зачем бывают нужны дженерики


                          Я вас прекрасно понял! Я пытался на простом примере с Equaler показать, что это возможно в принципе. Т.е. в функции и методы структур и сами структуры в go могут работать с интерфейсами, а не с конкретными типами данных.

                          Без кастов? Я бы с любопытством посмотрел на это.


                          Нужна пища для размышления…


                          1. lair
                            27.04.2016 17:58
                            +1

                            Я вас прекрасно понял! Я пытался на простом примере с Equaler показать, что это возможно в принципе. Т.е. в функции и методы структур и сами структуры в go могут работать с интерфейсами, а не с конкретными типами данных.

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


                            1. asolomonoff
                              27.04.2016 18:14
                              +2

                              да, вы правы! Я вкладывал иной смыл в термин обобщенный код.


                              1. lair
                                27.04.2016 18:15
                                +2

                                А люди, которые говорят, что в Go нет дженериков, имеют в виду вполне конкретный смысл. Поэтому когда вы говорите, что обобщенное программирование есть, но подменяете смысл — это, скажем так, некрасиво.


                                1. asolomonoff
                                  27.04.2016 18:22
                                  +2

                                  это, скажем так, некрасиво.

                                  пожалуй, соглашусь) что неправильно использовал термины( эх…


      1. Gorthauer87
        27.04.2016 15:49
        +1

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


      1. codemax
        27.04.2016 16:19
        +1

        Компилятор следит за типами в дженериках и не даст сделать глупостей. Или хотя бы предупредит об опасности. Как бы уже нехилый бонус.


        1. asolomonoff
          27.04.2016 16:24
          -6

          согласен, в Go тоже следит. Если тщательно проектировать проект, то использование interface{} можно свести к минимуму, тогда большинство глупостей отпадут на этапе компиляции.


  1. solver
    27.04.2016 13:26
    +12

    В начале дается причина

    >Все было бы круто, если не JVM. Для того, чтобы развернуть серьёзное приложение на Java, вам понадобится тачка с большим количеством оперативной памяти. И это совершенно не годится для спартапов.

    А потом рассказывается, как можно написать кучу кода, для замены JVM.
    А где собственно подтверждение причины?
    Где доказательства, что памяти действительно надо во столько раз больше, что дешевле перейти на Go?
    Почему писать больше кода в Go дешевле, чем добавить памяти на сервак (это не учитывая, что доказательство большего потребления памяти вами не приведено)?


    1. asolomonoff
      27.04.2016 15:36
      -5

      первое, JVM не подходит мне лично, т.к. я не хочу платить за дорогой хостинг
      второе, достаточно запустить любой сервер приложений Java и посмотреть количество потребляемой памяти. Недавно мы тестили Go и Java(spring + jetty). Go скушал 37мб, против 678мб у Java, да производительность показал в два раза выше. Мы списали это на spring.


      1. solver
        27.04.2016 16:25
        +8

        >первое, JVM не подходит мне лично, т.к. я не хочу платить за дорогой хостинг
        Вот так и надо было писать в статье «Лично я не смог хоть немного выучить Java и сделал приложение которое потребляло много ресурсов», а не громкие завления в духе «JVM жрет много ресурсов и точка»
        Ну и опять же, Давно под Go есть шаред хостинги по 1$?
        Ибо у меня несколько проектов на Java и Scala крутится на 5$ инстансах DO.
        При чем ДО не самый дешевый, аналогичные инстансы можно найти сильно дешевле.

        >второе, достаточно запустить любой сервер приложений Java и посмотреть количество потребляемой памяти
        А зачем его запускать-то? Ну давайте напишем приложение на Go, которое отожрет много памяти и будем его в пример приводить… Да, кстати, а есть они вообще под Go эти самые сервера приложений? А то вы сравниваеете навороченный програмный комплекс с простым web приложением, что как бы косвенно говорит о вашей квалификации как разработчика и о достоверности ваших выводов.

        >Недавно мы тестили Go и Java(spring + jetty)…
        А недавно мы написали кривое приложение на Go и оно съело все ресурсы… и что с того?
        Это лично вы не умеете писать на Java, а не Java потребляет много ресурсов.
        Опять пример не в пользу вашей квалификации и сделанных выводов.

        Чтобы писать статьи с такими громкими заявлениями, надо для начала хорошо разбираться в вопросе.
        Если уж беретесь что-то сравнивать в таком ключе, то потрудитесь запастись реальными примерами и корректными сравнениями, а не домыслами вытекающими из вашей слабой квалификации.
        Для начала, далеко не всем нужны сервера приложений. Если вы на гласфише пытались делать публичный веб проект, то вы должны были четко понимать для чего вы это делаете. А так по вашим каментам все выглдяит так, буд-то вы по хреновым хеловордам из инета пытались на Java поднять проект и он у вас тормозил.
        Не разобравшись начали писать на Go. К сожалению, такими «саксес стори» пестрит весь интернет…

        Я не противник Go, и не адвокат Java.
        Просто надо корректно сравнивать технологии, а не выдавать собственную некомпетентность за плюсы и минусы технлогий. Если это ваш личный опыт, то не делайте обобщений, так и пишите «мы не смогли», «мы не умели», «мы не знали как, поэтому взяли первое попавшееся решение в интенете» и т.д.

        Тогда ваша статья будет иметь правильную тональность.
        Не «JVM говно, а Go молодец»,
        а «Начинающему разработчику, проще начать с Go, т.к. язык молодой и все решения свежие в гугле проще находятся. А Java уже 20 лет и при поиске надо фильтровать старые не эффективные решения от новых более эффективных»

        P.S. Ну и да, ждем пруфов в виде одинаковых проектов, в которых Go есть памяти в 18 раз меньше чем Java.
        Чтобы можно было понять, на сколько адекватно все здесь вами написанное.


        1. asolomonoff
          27.04.2016 17:22
          -4

          я не смог хоть немного выучить Java

          А так по вашим каментам все выглдяит так, буд-то вы по хреновым хеловордам из инета пытались на Java поднять проект и он у вас тормозил.

          взял офф доки java ee, по ним делал. Проект не тормозил, просто много кушал. Я не противник java, просто мне не подошло.

          А недавно мы написали кривое приложение на Go

          писал не я, а коллега java программист, не в его интересах было писать криво.

          Не «JVM говно, а Go молодец»,

          я этого и не говорил, и вообще статья о проблемах go, а не jvm))

          А Java уже 20 лет

          круто! Но это не означает, что это панацея для всех проектов

          Ну и да, ждем пруфов в виде одинаковых проектов, в которых Go есть памяти в 18 раз меньше чем Java

          легко, достаточно написать сервер на go и и на spring, но повторюсь статья не об этом))) извините, если задел за живое)


  1. Moxa
    27.04.2016 13:51
    +5

    у меня есть веб-приложение, написанное на жаве, жрет 24 мегабайта оперативы. Что я делаю не так?


    1. j_wayne
      27.04.2016 15:02
      +1

      Видимо, ограничиваете heap и т.п.) По умолчанию этого мало кто делает, оттуда и сказки.
      Немного оффтоп, но я так и не смог нормально ограничить heap руби программе, в отличие от явы. Может, я конечно плохо старался, но я думал, такие вещи должны быть очевидными.
      А в плане потребления памяти — как правило, любая VM будет есть сколько дадут, т.к. это позволяет реже собирать мусор. Руби тоже жрет пока может, если объекты интенсивно создаешь.


    1. asolomonoff
      27.04.2016 15:39

      значит вы не используете энтерпрайз решения, тот же glassfish на старте потребляет 500мб + ~200 на приложение


    1. cy-ernado
      27.04.2016 16:35
      -2

      У меня есть сервер, на которым запущены debian, веб-приложение на Go (с использованием fasthttp) и nginx.
      Веб-приложение кушает 3640 килобайт.

      image


      1. asolomonoff
        27.04.2016 17:25

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


  1. dim_s
    27.04.2016 14:25
    +11

    Я не хочу обидеть тех, кто программирует на Go, но пожалуй я лучше заплачу дополнительно за железо и заплачу меньше, чем буду тратить время (т.е. деньги) на то, чтобы реализовать в Go то, что есть в JVM из коробки. Java подходит для стартапов как любой другой язык и потребление памяти это сейчас не такая большая проблема как написание хорошего и оптимизированного кода.


  1. kekekeks
    27.04.2016 14:44
    +9

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

    Что за бред. У хецнера 60 евро в месяц за Xeon® E3-1275 v5 и 64 гигабайта DDR4 оперативки.


    1. solver
      27.04.2016 15:05
      +9

      Написано же «не годится для спартапов».
      Выходит, по мнению автора, 60 евро в месяц это просто неподъемная сумма для стартапа.


      1. asolomonoff
        27.04.2016 15:45
        -3

        да, для меня было много)))


    1. asolomonoff
      27.04.2016 15:44
      -1

      я писал проект — несколько вебсервисов + фронт, который с ними работает. Приложение потребляло от 2гб оперативки. Я писал один, мне было накладно. Согласен, что когда у тебя есть бюджет и команда, то ничего страшного.


      1. kekekeks
        27.04.2016 16:08
        +3

        Ну вот на таком сервере ваших приложений можно запустить 32 штуки. Итого чуть меньше двух евро в месяц за приложение. Что у вас за сервис такой, что два евро в месяц найти негде?


        1. asolomonoff
          27.04.2016 16:15
          -6

          java ee + restfull + клиент на java
          это было давно, когда только выходила восьмая java


          1. kekekeks
            28.04.2016 01:08
            +2

            Т. е. вы сравниваете джаву "когда-то давно" и го сейчас, я правильно понимаю?


    1. UA3MQJ
      28.04.2016 15:28
      +1

      И это еще не предел. У меня на chicagovps серверы, один за 12, второй за 15 долларов в год. Тоже VM, правда от Erlang. Всё работает.


  1. ertaquo
    27.04.2016 14:47
    +1

    Мне в Go не нравится еще то, что в нем нет возможности задать значение аргументов по умолчанию, типа

    func Foo(bar int = 5)
    Это можно обойти через динамические аргументы (args ...int), но дико неудобно.


    1. asolomonoff
      27.04.2016 15:48

      согласен, тоже не нравится, не упомянул в статье


  1. sovetnik
    27.04.2016 15:48
    -3

    не годится для СПАРТАпов [х]


    1. asolomonoff
      27.04.2016 16:12

      спасибо, поправил!


  1. madmanul
    27.04.2016 15:48
    +1

    Согласен, многие стартаперы ошибочно считают что у них больше времени чем денег, а еще наивно полагают что им прям со старта придется столкнуться с высокими нагрузками. И еще — хотелось бы видеть сравнение go с его главным конкурентом по потреблению ресурсов — nodejs.


    1. asolomonoff
      27.04.2016 15:52

      могу сказать, что мы в компании проводили тесты java(Netty), nodejs, go(нативная реализация), scala, erlang. Основными конкурентами по производительности были java и go, чуть чуть позади erlang, потом scala, а только потом nodejs. Выложу статью про это дело попозже


    1. cy-ernado
      27.04.2016 16:40

      сравнение go с его главным конкурентом по потреблению ресурсов — nodejs.

      Судя по опыту миграции некоторых компаний с nodejs на go (Digg, VK), нода конкурентом не является.


  1. defaultvoice
    27.04.2016 15:52
    -1

    >В свою очередь в PHP при каждом запросе приложение инициализируется заново
    Я, конечно, не пхпшник, но разве php-fpm не решает эту проблему? Обычный CGI вроде бы давно никто толком не использует, разве что для простеньких скриптов.


    1. asolomonoff
      27.04.2016 15:58
      +1

      php-fpm загрузит в память только скомпиленные модули php. Ваше же приложение будет инициализироваться при каждом запросе. Отчасти эту проблему решает phalcon — это фреймворк для php на плюсах. Но опять же ваш код будет инициализироваться при каждом запросе.


      1. andrewnester
        27.04.2016 16:48

        есть php-pm для таких случаев, но это совсем другая история уже https://github.com/php-pm/php-pm


        1. asolomonoff
          27.04.2016 17:59

          спасибо, посмотрю


  1. Source
    27.04.2016 15:58
    +3

    А зачем бороться с тем, что Go ругают? Многим не нравятся языки, которые вводят искусственные ограничения. И это вполне нормально, особенно с учётом того, что есть отличные альтернативы, например: Elixir и Nim.


    1. asolomonoff
      27.04.2016 16:02

      согласен, с тем не всем может понравится язык. Это нормально. Я говорю с статье о том, с чем сталкивается любой Go программист. И как это можно решить)


  1. Ryppka
    27.04.2016 15:59

    И почему считается, что если есть классы и позднее связывание — то ООП. А если нет — то не-ООП?


    1. Source
      27.04.2016 16:10
      +1

      Потому что мало кто помнит, что такое ООП :-)


  1. Mesmer
    27.04.2016 16:12
    -7

    А где Катя, я вас спрашиваю!


    1. asolomonoff
      27.04.2016 16:13
      -3

      ой, это не я) сам жду)))


      1. Mesmer
        27.04.2016 16:15
        -3

        Вообще-то сериал про Катю закончился Леной (если не ошибаюсь), поищите.
        Но как-то скучно без неё (Кати) на хабре стало. ))


        1. asolomonoff
          27.04.2016 16:16
          -5

          зачет!!! спасибо, поищу)


        1. Source
          27.04.2016 18:52
          -1

          Надо вводить традицию: хочешь писать про Go на Хабре — сначала заведи Катю )))


          1. Mesmer
            27.04.2016 19:15
            -2

            Судя по минусам, которыми нас щедро одаривают, у людей психологическая травма от Кати.


            1. asolomonoff
              27.04.2016 19:21
              -2

              вообще, очень забавно народ минусует, некоторые комменты вполне безобидные) впрочем, я ожидал похожую реакцию) а что касается статей про Катю, то интересная была подача!


              1. zelenin
                27.04.2016 21:35
                -4

                то, что для вас безобидно, т.к. в комменте нет ни оскорблений, ни разжигания нацистских настроений, для меня вполне опасно. С неуместного в технической статье (и техническом обсуждении статьи) коммента начался тред на 8 комментов из 60. Удивительно, что вам как автору статьи интересно в комментах «хихи-хаха» на отвлеченную тему.


                1. asolomonoff
                  27.04.2016 23:14

                  я тоже читал цикл этих статей, не хотел игнорировать человека. Да и в любой момент может быть минута лирики, не вижу ничего такого


                1. Source
                  28.04.2016 00:40

                  Можно feature-request в TM отправить на сворачивание тредов или самому на JS аддончик запилить.
                  А пока из консоли браузера выполните

                  $("#comment_8873074").hide();

                  дабы не видеть это безобразие xD


  1. zartdinov
    28.04.2016 00:41

    Частичку перфекциониста во мне всегда тревожат эти func, nil, fmt ..., когда всякие interface, package, struct написаны нормально.
    Особенно когда часто переключаешься с js и обратно.


    1. Sirikid
      28.04.2016 00:55

      Это просто привычка, struct тоже сокращение а вот nil нет.


  1. Sirikid
    28.04.2016 01:02
    +2

    Зачем, мистер Андерсон, зачем вы пишете ещё одну статью про Go?


    1. asolomonoff
      28.04.2016 01:18
      +1

      зачем пишут новые статьи о других языках? может потому, что возникают проблемы и их решение, и этим хочется поделиться… разве нет?


      1. Sirikid
        28.04.2016 01:29
        -1

        > Конечно же, речь пойдет об отсутствии ООП как такового, перегрузки методов и функций, обобщенного программирования и исключений.
        Давно уже все это обсудили и здесь и на других площадках.


  1. UA3MQJ
    28.04.2016 16:56
    +6

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

    В общем, мои скромные изыскания в части сравнения реализации процессов и способов обмена данными между ними, выявили следующее:

    1. Обмен сообщениями.
    В Go для обмена сообщениями используются каналы. Канал явно существует, как понятие. Он передается в функцию (горутину), а та уже с ним работает.
    Что понравилось:
    — вроде бы можно одно сообщение из канала принять двумя корутинами
    Что не понравилось:
    — необходимость самого существования такого понятия, как канал (в ерланг это не надо)
    — при отправке сообщения в канал, отправляющий процесс «зависает» пока принимающий не примет данные. Для чего такое может быть нужно, мне сложно понять. В итоге, может выстроиться цепочка из процессов от соединения с пользователем, до записи на диск, и вся она «зависнет» на работе с файловой системой. Но в принципе, на сколько мне сумели объяснить, в таких случаях сообщения «принимают и складывают на стек», читай принимают и складывают в память процесса. Чтобы устранить зависания, в канале может быть включен буфер ФИФО, это от части решает проблему с зависанием.

    В Erlang для обмена сообщениями между процессами, не используется понятие канала. Нужно знать PID процесса и отправить ему сообщение. Отправляющий процесс не зависает при отправке сообщения. Он его просто отправляет и работает себе дальше. Отправленное сообщение упадет в почтовый ящик процесса, в очередь, и тот его считает тогда, когда ему будет нужно. Но процесс читающий сообщение из почтового ящика «зависнет», если там нет сообщения. Но это я считаю нормально (нет данных для дальнейшей работы — ложимся спать). К тому же есть возможность установить таймаут и после него выполнять работу дальше. Если же нужно отправить сообщение синхронно, с подтверждением приема от удаленного процесса, то мы его отправляем, потом читаем входящие — в этот момент мы «зависнем», пока не придет ответ. То есть мы можем выбирать режим (почти аналогия буфера каналов).

    Вопрос того, что в Erlang процесс обмена сообщениями внутри ноды или кластера происходит прозрачно, тут не обсуждаем. Приложения Go в кластер не объединяются. Там своя тема под названием микросервисы и то, как они меж собой связаны уже решает разработчик, но пусть так. Из положения выходят с помощью grpc или как-то иначе. Но для меня до сих пор остается загадкой, как множество процессов одного приложения, например tcp соединений с клиентами, должны обмениваться с процессом, например, базы данных? Сколько будет каналов, как их столько держать и обрабатывать? В Erlang даже вот чисто концептуально все это получается менее нагроможденным и простым. Процесс клиент просто отправит сообщение серверу, в сообщении будет PID, куда слать ответ. Сервер процесс не хранит всю связность с процессами — клиентами. Очередность будет соблюдена благодаря тому, что сообщения в почтовом ящике процесса выстраиваются в очередь. В то же время, есть возможность выбрать сообщения в другом порядке.

    2. Зеленые процессы. В Erlang всё понятно — это виртуальная машина, она выполняет байткод, каждый процесс выполняется на заданное количество редукций, разрешенных для него, потом, что бы он не делал (за исключением атомарных операций), он будет прерван и выполняться будет уже другой процесс. Распределяет работу шедулер. Из коробки ноду Erlang можно считать целой ОС мягкого реального времени.

    Я грешным делом сначала подумал и возрадовался, что в Go для реализации работы горутин используется что-то типа встроенной виртуальной машины. Это было бы шикарно: виртуальная машина в приложении. Но в процессе общения в Go'шном канале (ребятам, кстати, спасибо), выяснилось, что никакой виртуальной машины нет. Но как же работают горутины в параллельном режиме? Как осуществляется шедулинг? Оказалось, что если создать горутину, которая в бесконечном цикле прибавляет единицу и никуда это не передает, то она «зависнет» насовсем. И если запустить Go приложение в один процесс (а не по умолчанию 6 — 8), то приложение зависнет насовсем с момента запуска горутины и то, что будет после этого, никогда не выполнится! Я даже сделал ошибочный вывод о том, что на одноядерном процессоре это все в принципе может перестать работать. Но потом вспомнилось про системные threads и что они существуют как бы параллельно даже на одном ядре. Так что, приложение Go вполне себе честно виснет, будучи запущенным в один процесс, если горутина зациклена. Открытием было то, что передача управления на другие горутины происходит при вызове функций других модулей (просто, или, к примеру, вывода на экран). То есть, ничего общего с равномерным распределением ресурсов тут получается, что и нет. Каждый процесс может неопределенное время монопольно использовать один thread, пока не отпустит. В условиях того, что thread'ов выделяется несколько, то это бросается в глаза как бы не сразу. Все это напоминает многозадачность Win95, когда задачи сами решали, когда отдать управление ОС. На сколько мне известно, решение о том, какую горутину выполнять дальше, решает рандом. И вообще это еще в процессе разработки, потому что этот алгоритм показал плохие результаты. Другие статьи по Go вполне ожидаемо сообщают о том, что не всегда целесообразно создавать много параллельных горутин.

    3. Производительность. Перед тем, как сделать простой веб сервер на Go и на Erlang и сравнить их по производительности, я взял задачу, которая гарантировано удобнее для Go. Это счастливые билетики. Тупо в лоб 6 вложенных циклов от 0 до 9 и миллион итераций сравнения. Естественно, Go обогнал Erlang больше, чем на порядок. Потом я скомпилировал модуль Erlang с применением HiPE и расчет на Erlang оказался в 4 раза медленнее расчета на Go.

    На этом я и остановился. Ощутимой прибавки производительности можно и добиться, но другие минусы расстроили. А целого ряда возможностей Erlang'а нет и скорее всего никогда не будет в Go. Поэтому Erlang и дальше для меня остается более предпочтительным в качестве платформы для разработки серверных приложений. Серьезные проблемы типа дедлоков в нем решаются вообще иной организацией построения приложений и иным подходом в решении задач. А Go это такой же язык, как и сотня других. Простой синтаксис и компиляция в двоичный код спасают скоростью своей работы. Но на этом пока и все. Реализация зеленых процессов и обмен сообщений между ними произвели впечатление каких-то сырых костылей, скрученных изолентой. Еще в процессе обсуждения в канале нашлась еще одна задача, которая тяжело решается на Go из за его принципиально статической природы: это прием и разбор json если он не имеет строго определенной структуры (мап, а в нем мап, а в мапе тоже мап, а может и нет). Так что можно предположить, что с парсингом xml, html там могут быть похожие проблемы. Так же есть информация, что http сервер на Go оптимальнее всего работает в один процесс. Тогда зачем все эти зеленые процессы и каналы?

    Ну а в связи с тем, что статья про Go, я закончу на позитивной ноте. Go мне понравился тем, что я будучи совсем ничего не знающим про Go даже могу что-то понять из исходного кода. Концепт там такой, что кода больше, но зато всем понятно. Так вот — действительно общий смысл понятно. В том же Erlang многие вещи будут очень непонятны. На Go вполне удобно делать какие-то консольные утилиты (каких то wxWidgets пока не прикручено, чаще поднимают локальный вебсервер и заходят на него из браузера, но работы вроде бы идут). Полученный exe файл получается размером меньше 10 мегабайт и рядом не требуется укладывать допол


    1. Source
      28.04.2016 19:29
      +2

      Каждый второй пишет какую-нибудь библиотеку для какой-нибудь задачи (csv, orm etc), которые находятся в разных состояниях готовности.
      Как по мне, это жирный минус экосистемы Go. У меня есть опыт написания микросервисов на Go, но даже в рамках микросервиса этот буриданов выбор из кучи библиотек, которые делают одно и то же, начинает раздражать. Причём большинство из них напрочь лишены версионности и приходится линковать их к проекту по ревизии в master-ветке o_O
      Кстати, для управления зависимостями в Go тоже 100500 альтернатив и ни одной, которая по удобству была бы близка к bundler или hex.
      Для себя решил отказом от Go в пользу Elixir. Да, по памяти Go экономнее, но не критично. А по производительности для веб-сервера они одинаковы с точностью до погрешности. При этом код на Elixir получается проще, лаконичнее и понятнее. Хоть и требует, чтобы программист был в курсе функциональной парадигмы и потратил недельку на знакомство с OTP.


    1. GeckoGreen
      29.04.2016 11:39
      +1

      1. При отправке сообщения отправляющая рутина зависает только в случае небуферизированного канала. Если же создать канал с заданным рамером, то она может зависнуть только при его переполнении.
      2. С помощью библиотеки runtime можно контролировать смену контекста. Ну в зацикливающиемся приложении вина не на языке.


    1. ElOtroYo
      29.04.2016 18:29

      Вы не могли бы поделиться своим субъективным взглядом на Phoenix Framework и Elixir в частности? Даже поверхностным.


      1. UA3MQJ
        29.04.2016 21:30

        Сложно вот так без подготовки. В принципе фреймворков на Erlang (в конечном итоге) не так и много. Каждый из них находится в определенной мере либо заброшен уже сколько то лет, либо малопопулярен по количеству последователей. Думаю, это общая проблема Erlang экосистемы. Я бы попробовал оценить сообщество разработчиков на Phoenix (где общаются, активность, количество), примеры готовых проектов на нем, попробовать что-то сделать на нем и уже после этого пробовать делать выводы. В этой статье его сравнивают с рубирейлс. Может рельсовики что-то скажут. А так, основа erlang, cowboy, вебсокеты, в техническом плане вроде все нормально. Оценивать надо на сколько удобны сами модели представления, разработки. Сам я пробовал только N2O, но было это еще до того, как я стал серьезно работать с вебом, и на тот момент у меня как-то совсем не пошло.

        Конкретно в веб проектах я участвую не очень давно. Но приходится как-то поддерживать две системы, одна на yii, вторая на codeigniter. Поэтому фреймворки я как-то не очень люблю. И не потому что PHP, а из за их замкнутости. Фреймворк загнулся — знания на помойку. Посадили за фреймворк «B», и знания по фреймворку «А» тебе не помогут. Если делать отдельно фронт и бекенд, связывая их стандартными протоколами, то части системы можно будет заменять. Сейчас вот работаю в проекте с AngularJS на фронте. Так он не вызывает внутреннего отторжения. И при разработке такой системы даже в случае с крахом ангуляра, знания и опыт по бекенду точно не пропадут. Либо наоборот, если фронт на AngularJS остается без изменений, а бекенд на скорую руку написан на PHP. После этого можно его переделать на Python или Go или на чем-то еще, в зависимости от того, каких разработчиков получится найти. Поэтому сейчас, когда захотелось написать фронт на ангуляре и бекенд на ерланге со связью через вебсокеты, мне пришлось поискать, кто из ерланг веб серверов умеет вебсокеты. В итоге я остановился на cowboy и yaws, последний выбрал по причине того, что он поставился на win ;) Но даже вот после этого я постарался выйти к тому, чтобы по максимуму отвязаться от типа выбранного сервера. Фронт устанавливает стандартное ws соединение. Сервер yaws это поддерживает в виде конечной точки и функции обратного вызова. В нее попадает поток из ws. В этом месте надо уже забыть про yaws и слать этот поток стандартному Erlang приложению. И тогда нормально будет. А то представьте, на 1/6 часть суши 1000 эрлангистов. Из них yaws знают 1/4. Даже с этой точки зрения найти людей будет проще. Но, возможно, я слишком глобально смотрю на этот вопрос. Если нужен стандартно-типовой проект, быстро что-то сделать, внедрить и забыть о нем, то фреймворки как раз позволяют это сделать. Так случилось и в моем случае: до меня запилили два решения(yii и codeigniter), а когда дошло дело до постоянных каких-то доработок (как это часто бывает на производстве)… а мне с этим жить )

        Про плюсы Elixir недавно описал Юрий Жлоба из варгейминга. Я сделал для себя выводы, что знающих напрямую Erlang не особо интересует Elixir. И Exilir интересуются вроде бы как тех, кто в лоб Erlang тяжело воспринял. Однако, сложилось впечатление, что в Elixir более активное сообщество, библиотеки, системы установки модулей. Но не суть. Я думаю, по той причине, что elixir работает на VM Erlang, то под капотом там всё нормально (вспоминаем scala и JVM).

        Извините, как-то опять много буков вышло.


        1. Source
          30.04.2016 02:39
          +1

          По внутреннему устройству Phoenix совсем не похож на RoR. Он гораздо проще и по сути является набором plugs, которые можно использовать и комбинировать по своему усмотрению, + Ecto для работы с базами данных + Channels для веб-сокетов.
          Собственно, Plug — и есть центральная концепция, чем-то сродни middleware в Ruby, только используется единообразно на всю глубину стека: роутер — это plug, контролер — тоже plug и т.д.


  1. asolomonoff
    28.04.2016 17:52
    +1

    спасибо, отличные вопросы, соберу информацию, отпишусь в новой статье