Привет! Меня зовут Андрей Литвиненко и я имею почти 15-летний опыт разработки на PHP. Уже около трех месяцев я активно пишу на Go и хочу поделиться наблюдениями, которые отметил для себя. Возможно, этот текст будет полезен тем, кто сейчас знаком с PHP и подумывает познакомиться с Go.

В разное время я работал на разных позициях, начиная от джуниор-разработчика и заканчивая Руководителем Отдела продуктовой разработки. А сейчас я принял вызов и присоединился к команде платформенного бэкенда NUT.Tech для разработки низкоуровневого проекта на Go. Про этот язык программирования я конечно же слышал, но никогда до этого мне не приходилось сколько-нибудь серьезно с ним работать.

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

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

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

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

Пакеты. Очень отдаленно похожи на неймспейсы в PHP, но есть свои особенности. Все файлы одного пакета находятся в единой области видимости. Это, с одной стороны, удобно, а, с другой стороны, может вызывать недопонимание - нужно ли разделять функционал на файлы или писать все в одном. Из-за этого часто можно увидеть файл на 1000 строк с нескольким десятком и даже сотней функций. А в некоторых библиотеках нередко наблюдается ситуация, когда весь код написан в файлах, которые расположены в корневой папке проекта.

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

В Go хорошая поддержка документации публичных пакетов. Если писать документацию в правильном формате, то после публикации своего пакета, например, на GitHub.com, документация этого пакета автоматически появится на https://pkg.go.dev.

Нравится, что есть инструменты тестирования из коробки. Легко сразу начать писать тесты к своей программе. 

Единый стандарт написания кода. Есть готовые инструменты для проверки синтаксиса (gofmt).

Многопоточность. Для разработчика на PHP непривычная штуковина. Интересная, мощная, но очень опасная. Нужно постоянно писать код с оглядкой на то, где будет выполняться та или иная функция; использовать локи, чтобы не было состояний гонки в памяти. И тут, кстати, очень хорошо могут помочь тесты, которые большинство таких проблем могут отловить и подсветить.

Горутины и каналы. Горутины — это функции, которые работают параллельно с другими функциями, а каналы – это способы общения между горутинами. Так как в PHP нет многопоточности, то и горутин с каналами тоже нет совсем.

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

Иногда мне не хватает каких-то простых и таких привычных для PHP-разработчиков функций в стандартных библиотеках (или я их плохо искал). Например, определение минимального/максимального числа. Сортировка массива.

С массивами вообще, мне кажется, в PHP все намного проще. В PHP, в отличие от Golang, массив может быть и собственно массивом, и списком, и хеш-таблицей, и словарем, и коллекцией, и очередью, и стеком. И это иногда очень удобно. 

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

Примеры того, чего мне не хватает из «стандартного» функционала работы с массивами (тут я подразумеваю скорее карты): проверка вхождения элемента в карту, сортировка карты по значениям и т.п.

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

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

Оператор defer. Очень интересный оператор, который позволяет задать действия, которые необходимо выполнить в конце работы функции. Причем неважно в каком месте происходит возврат.

В конце этого текста могу сказать, что нужно вспомнить фразеологизм «Ехать в Тулу со своим самоваром»: PHP и Golang – это совсем разные языки программирования. У них разная философия и области применения. При изучении нового языка не нужно стараться подгонять его под свои знания и пытаться из Go сделать PHP. Если эту ситуацию рассматривать через призму прохождения пяти стадий принятия неизбежного,  то я нахожусь где-то между Депрессией и Принятием  ☺

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


  1. inforus
    25.11.2021 14:37
    +1

    interface же еще прям огонь


  1. Goodwinnew
    25.11.2021 14:40

    Нравится возможность из функции возвращать несколько значений

    Сорри, а разве в PHP из функции нельзя вернуть массив? И туда набрать несколько значений, какие надо.


    1. alitvinenko Автор
      25.11.2021 14:55
      +4

      Конечно можно вернуть массив.

      list($first, $second) = $this->someMethod();

      Но на мой взгляд это менее удобно по нескольким причинам:

      1. Нет никаких гарантий что метод вернет именно два параметра в массиве

      2. Нет никаких гарантий что принимаете вы два параметра

      3. Нет никакой гарантии что элементы возвращаемого массива не будут перепутаны.

      4. И так далее.


      1. Goodwinnew
        25.11.2021 15:07

        :)

        я по старинке

        $arr[1] = 10;
        $arr[2] = 100;
        $arr[3] = TRUE;
        return $arr;


      1. Djeux
        25.11.2021 15:47
        +5

        Когда нужны пункты 1, 2, 3, то надо возвращать уже объект. В PHP есть дурная привычка все делать через массивы с ключами, в результате использовать функцию возвращающую массив без того что бы смотреть сорсы той функции, становится невозможно.

        Это тоже самое что в Яве бы все плюнули и стали Map-ами швырятся.

        П.С. при возвращении массивов есть и более короткий синтаксис уже, если заранее известны ключи

        ["one" => $one, "two" => "two"] = $this->someMethod();
        [$one, $two] = $this->someMethod();


      1. ua6xh
        26.11.2021 01:50
        +1

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

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


        1. Gizmich
          26.11.2021 17:08
          -1

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


          1. alitvinenko Автор
            26.11.2021 17:21

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

            Про сигнатуру согласен, она нагляднее)


  1. yellow79
    25.11.2021 14:45

    Иногда мне не хватает каких-то простых и таких привычных для PHP-разработчиков функций в стандартных библиотеках (или я их плохо искал). Например, определение минимального/максимального числа. Сортировка массива.

    Плохо искали. math.Min math.Max для сортировки пакет sort


    1. e-ma-a2
      25.11.2021 14:59
      +4

      Но функции min/max для integer там таки нет.


      1. yellow79
        25.11.2021 16:33
        +1

        это вопрос времени, дженерики уже практически завезли


        1. alitvinenko Автор
          25.11.2021 17:40
          +1

          Да, ждем.


        1. hello_my_name_is_dany
          26.11.2021 05:40
          +3

          Под каждой статьей про GoLang с 2015 года вижу подобные комментарии...


          1. falconandy
            26.11.2021 08:47
            +4

            Дженерики выйдут в версии Go 1.18 в феврале 2022 г. В ветке master они уже доступны, можно их попробовать, используя gotip:
            pkg.go.dev/golang.org/dl/gotip

            Пока что generic-функции для работы со slices и maps будут доступны как экспериментальные (чтобы обкатать их), но потом их перенесут в стандартную библиотеку:
            slices.go
            maps.go


  1. Devoter
    25.11.2021 15:23
    +6

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

    Что касается наследования, то его нет, но есть композиция, вкладываете одну структуру в другую и она может вызывать ее методы:

    package main
    
    import "fmt"
    
    type Printer struct {
      value string
    }
    
    func NewPrinter(value string) *Printer {
      return &Printer{value: value}
    }
    
    func (p Printer) Print() string {
      return p.value
    }
    
    type ComboPrinter struct {
      Print
      id int
    }
    
    func NewComboPrinter(id int, value string) *ComboPrinter {
      return &ComboPrinter {
        Printer: *NewPrinter(value),
        id: id,
      }
    }
    
    func (cp ComboPrinter) PrintCombo() string {
      return fmt.Sprintf("%d: %s", cp.id, cp.Print())
    }
    
    func main() {
      cp := ComboPrinter{}
      
      fmt.Println(cp.PrintCombo())
      fmt.Println(cp.Print())
    }

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

    Про пакет sort вам уже выше сказали, да, не очень удобно, но есть. Тут другая парадигма, и стоит отбросить свои привычки из php и изучить язык поглубже.

    Что касается поиска ключа в map - такая функция есть:

    value, ok := mymap["search_key"]
    
    if ok {
      // do something
    } else {
      // no key
    }


    1. alitvinenko Автор
      25.11.2021 15:45
      +1

      Вы правы, про ООП вопрос холиварный. Нужны выверенные формулировки чтобы точно выразить свою мысль :)

      Спасибо за примеры.

      Про возможность встраивания структуры в структуру знаю, как найти ключ в мапе тоже понятно. Я же писал о том что нет аналога array_search/in_array.

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


      1. Bigata
        25.11.2021 15:55

        .includes()?


        1. alitvinenko Автор
          25.11.2021 18:17

          Что вы имеете ввиду? Не смог ничего связанного в интернетах найти.


      1. sedyh
        25.11.2021 20:07

        Ну почему вы ее не реализовали

        Предположу, что из-за отсутствия перегрузок и генериков, а реализация через рефлексию будет в 50 раз медленнее.


      1. Devoter
        25.11.2021 23:05
        +1

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

        Решение с итератором
        package main
        
        import "fmt"
        
        type Iterator interface {
        	HasNext() bool
        	Next()
        	Get() interface{}
        }
        
        type Iterable interface {
        	MakeIterator() Iterator
        }
        
        type Comparable interface {
        	Iterable
        	Eq(it Iterator, v interface{}) bool
        }
        
        func Includes(sl Comparable, v interface{}) bool {
        	for it := sl.MakeIterator(); it.HasNext(); it.Next() {
        		if sl.Eq(it, v) {
        			return true
        		}
        	}
        
        	return false
        }
        
        type IntSlice []int
        
        func (isl *IntSlice) Eq(it Iterator, v interface{}) bool {
        	return it.Get() == v
        }
        
        func (isl *IntSlice) MakeIterator() Iterator {
        	return NewIntSliceInterator(isl)
        }
        
        type IntSliceIterator struct {
        	isl   *IntSlice
        	index int
        }
        
        func NewIntSliceInterator(isl *IntSlice) *IntSliceIterator {
        	return &IntSliceIterator{isl: isl}
        }
        
        func (it *IntSliceIterator) HasNext() bool {
        	return it.index < len(*it.isl)-1
        }
        
        func (it *IntSliceIterator) Next() {
        	if it.index < len(*it.isl)-1 {
        		it.index++
        	}
        }
        
        func (it *IntSliceIterator) Get() interface{} {
        	return (*it.isl)[it.index]
        }
        
        func main() {
        	arr := IntSlice{1, 2, 3, 4, 5}
        
        	fmt.Printf("arr: %v includes %d: %t\n", arr, 4, Includes(&arr, 4))
        	fmt.Printf("arr: %v includes %d: %t\n", arr, 0, Includes(&arr, 0))
        }
        
        Чуть более примитивный вариант для типовых срезов
        package main
        
        import "fmt"
        
        type Comparable interface {
        	Len() int
        	Eq(index int, v interface{}) bool
        }
        
        func Includes(sl Comparable, v interface{}) bool {
        	for i := 0; i < sl.Len(); i++ {
        		if sl.Eq(i, v) {
        			return true
        		}
        	}
        
        	return false
        }
        
        type IntSlice []int
        
        func (isl *IntSlice) Len() int {
        	return len(*isl)
        }
        
        func (isl *IntSlice) Eq(index int, v interface{}) bool {
        	return (*isl)[index] == v
        }
        
        func main() {
        	arr := IntSlice{1, 2, 3, 4, 5}
        
        	fmt.Printf("arr: %v includes %d: %t\n", arr, 4, Includes(&arr, 4))
        	fmt.Printf("arr: %v includes %d: %t\n", arr, 0, Includes(&arr, 0))
        }
        

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

        package main
        
        import "fmt"
        
        func main() {
        	arr := []int{1, 2, 3, 4, 5}
        
        	includes := func(arr []int, value int) bool {
        		for _, v := range arr {
        			if v == value {
        				return true
        			}
        		}
        
        		return false
        	}
        
        	fmt.Printf("arr: %v includes %d: %t\n", arr, 4, includes(arr, 4))
        	fmt.Printf("arr: %v includes %d: %t\n", arr, 0, includes(arr, 0))
        }
        

        Сторонники Go скажут, что вам тут сам язык предлагает задуматься: "если я часто делаю операции поиска с несколькими типами срезов, то почему бы мне не унифицировать процесс, реализовав универсальную функцию Includes, а если у меня каждый раз разные типы, то, может, проще сделать поиск на месте?" Но вот я, как человек, который уже более двух лет пишет на Go, могу сказать, что тут это просто неудобно, потому что много однообразного кода приходится писать.


  1. thousandsofthem
    25.11.2021 15:39
    +4

    Строгая типизация. PHP тоже сейчас идет в сторону строгой типизации, 

    не надо путать динамическую/статическую и строгую(сильную)/слабую типизацию

    первая это про int x = 1; вторая это про x = 1; echo $x + " banana".

    у php слабая динамическая типизация (как у javascript. у python/ruby строгая динамическая типизация). вот как раз строгая типизация php вылечила бы 90% его болячек, но это из области фантастики, т к сломает примерно весь существующий код


    1. joffer
      25.11.2021 16:14
      +2

      ну тут бы скорее согласился с автором, php за последние 5 (или сколько там) лет очень типизировался - по крайней мере, появились возможности задавать типы для аргументов, для возвращаемых значений, сначала это добавили только для скалярки и простых типов, потом добавили классы. По крайней мере мне кажется, что автор имел в виду этот аспект

      Конечно, можно по-прежнему в стиле пхп5 писать весь код без единого типа на "магии" и авто-приведении - но при желании можно накрутить "строгий" режим и обвязать поля классов типами как следует и ходят легенды, что это даже помогает ускорить выполнение кода


    1. alitvinenko Автор
      25.11.2021 18:00

      Да, вы правы, имелась ввиду динамическая типизация. Косноязычность моя проклятая)


    1. Dekmabot
      25.11.2021 23:22
      +2

      При желании всё есть:

      <?php declare(strict_types=1);
      
      $x = 1;
      echo $x + " banana";

      Такой код вызовет TypeError: Unsupported operand types: int + string


      1. thousandsofthem
        26.11.2021 00:03

        О, а вот это полезно. Интересно, на практике используются людьми?


        1. ASGAlex
          26.11.2021 00:38

          Сам давно не пишу, но у своих коллег в коде - видел :-)


        1. Dekmabot
          26.11.2021 01:03
          +1

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


        1. tommyangelo27
          26.11.2021 01:48
          +1

          Да, уже пару лет пользуемся. В code sniffer есть правила, не дающие заапрувить пулл реквест с методами без заданных типов.


        1. HellWalk
          26.11.2021 09:57
          +2

          О, а вот это полезно. Интересно, на практике используются людьми?

          Да, любой хороший разработчик заходит в настройки phpStorm, правит шаблон создаваемого класса, и в любой созданный класс будет сразу создаваться со строчкой

          declare(strict_types=1);


        1. Rukis
          26.11.2021 10:09
          +2

          Конечно. В современных PHP проектах сейчас почти все так пишут (субъективное впечатление). Плюс используются стат анализаторы, чтоб поймать косяк с типом заранее, а не в рантайм.


        1. rjhdby
          26.11.2021 11:22
          +1

          У нас так - весь новый код только со стриктом, в старый добавляется в момент, когда понадобилось его править.


      1. trokhymchuk
        27.11.2021 12:31
        +1

        При желании всё есть

        Я так оправдываю дженерики в докблоках. ;)

        А strict_types действительно очень помогает, да.


      1. batyrmastyr
        08.12.2021 16:02

        На PHP 8 оно так и без strict_types, с 7.1 по 7.4 было предупреждение.


    1. trokhymchuk
      27.11.2021 12:29

      У РНР так-то смесь.

      http://sandbox.onlinephpfunctions.com/code/60377181cb139ac367c13660a54f4e01733472ec

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

      x = 1; echo $x + " banana"

      А тут поможет strict_types.


  1. thousandsofthem
    25.11.2021 16:18

    Это вы про статическую типизацию. Строгая это про "1"+1, и это так просто не убрать из языка


  1. Xeldos
    25.11.2021 18:36
    +1

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


    1. LARII
      26.11.2021 11:47

      Обход в цикле аналогично циклу Для Каждого в 1С. Если обходишь коллекции с индексом, получается детерминированный результат. А коллекции на КлючИЗначение - нет.


      1. Xeldos
        26.11.2021 12:07
        +2

        А зачем вам "детерминированный результат"? Вот к примеру... ну скажем конфиг, и в нём опции. Вам важно, в каком порядке они располагаются? Точно важно? Вы какую задачу решаете? Может, вы не тут структуру данных используете?


        1. LARII
          26.11.2021 12:14
          -2

          Я сейчас не решаю задачу. Я привел пример необходимости и указал на недостатки. А детерминированный результат при программировании это очень удобно. Обращаешься, например, элемент[0], получаешь первый элемент в коллекции. Удобно, чё. Если был бы рандом, было бы не так удобно.


          1. Xeldos
            26.11.2021 13:17
            +2

            В карте нет "первого элемента". По смыслу структуры данных. Есть элемент по ключу. Есть ещё структура данных "множество", там тоже нет первого элемента.


            1. LARII
              26.11.2021 13:23
              -1

              Зачем вы мне это пишите? Вы думаете я этого не знаю? Я разве писал что в карте есть первый элемент?


              1. Xeldos
                27.11.2021 07:27
                +1

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


                1. LARII
                  27.11.2021 10:08
                  -1

                  Я привел пример детерминированного результата, в котором привел пример получения первого элемента индексированной коллекции.

                  Мы точно должны вести диалог по вашим правилам: "Вы читаете несколько символов в моем комментарии, а остальное домысливаете, а я оправдываюсь на основе ваших домыслов"?!!


                  1. Xeldos
                    27.11.2021 16:28

                    Для карты определен детерминированный результат. Получение элемента по ключу. Или получение множества ключей. Или получение мультимножества значений.

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

                    PS минусы не мои.


                    1. LARII
                      27.11.2021 16:37

                      Для карты не определен детерминированный результат при обходе коллекции в цикле Для Каждого (For Each).

                      https://habr.com/ru/post/591527/comments/#comment_23752001


  1. MyraJKee
    25.11.2021 18:51

    Эм... На java не пробовали программировать? )


    1. alitvinenko Автор
      26.11.2021 10:44
      +1

      Почему-то уже лет 10, наверно, нет такого желания) До этого времени были попытки.


  1. sedyh
    25.11.2021 19:57
    +1

    Наследования реализации нет. И мне его не хватает. Очень непривычно, когда нет возможности расширить реализацию.

    В большинстве случаев нужна скорее композиция, чем наследование. А для композиции, как уже сказали выше, существует type embedding.

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

    Если нужно протестировать внутренний интерфейс программы, тогда положите тест рядом с исходником, если внешний - в отдельный пакет. Зависит от вашего подхода (blackbox/whitebox). Для облегчения создания моков следуйте правилу: "принимайте интерфейс, возвращайте структуру".

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

    Зато всегда четко видно, кто и что может вернуть без необходимости залезать в документацию. Для обработки ошибок по иерархии существуют конструкции errors.Is, errors.As, fmt.Errorf("%w"), для получения контекста ошибки можно использовать type assertion на пользовательский тип.

    В Go только строгая типизация.

    Я бы ещё обозначил, что типизация структурная, это очень важная особенность.


    1. eandr_67
      26.11.2021 00:05
      +1

      типизация структурная, это очень важная особенность.
      Структурная только для интерфейсов. А для типов — только именная. Причём предельно жёсткая именная. Такой код вызовет ошибку «mismatched types ti and int64»:
      type ti int64
      var a ti = 1
      var b int64 = 2
      a += b
      


  1. LARII
    25.11.2021 20:54

    Также очень сильно не хватает исключений

    Ну panic(Описание исключения) же. Почти то же как в 1С Enterprise ВызватьИсключение(Описание исключения)


    1. domix32
      26.11.2021 15:19

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


      1. LARII
        26.11.2021 16:37

        Ловить в смысле попытка-исключение-конецпопытки?


        1. domix32
          26.11.2021 17:32

          Ага. А еще и по типу определять NPE это или out of bounds какой. Речь в общем-то не про конкретно 1с исключения, а про любые языки с исключениями типа той же Java.


          1. LARII
            26.11.2021 18:48

            Ну там же есть библиотеки типа syscall, как я понимаю одна из идеологий голэнг - "нету функционала, запили сам."


            1. domix32
              26.11.2021 19:07
              -1

              я слышал, что "запили сам" это не про go — люди много всякого просят отдать на публику, но разработчики языка очень ревностно относятся к такому, хотя внутри языка вполне себе используют самостоятельно (high precision timers как пример). Так что люди может быть и рады бы такое провернуть, но оно оборачивается каким-нибудь костылем типа имплементации этого где-нибудь в сишной либе подключенной через FFI или оберткой поверх fork() которое по факту разрешает чайлдам умирать на панике и просто запускает новый fork(). Ну и конечно форки самого голанга, но такое почти никогда не присутствует вне конкретных кампаний. Короче пока нет нормального способа их реализовать, либо я не слышал о нем.


              1. LARII
                26.11.2021 19:34

                1. domix32
                  26.11.2021 22:52

                  А есть пример бросания и ловли?


  1. Akuma
    25.11.2021 21:58

    Тоже много лет на PHP/JS и на днях захотелось нового, читаю изучаю Go.

    Расскажите, какие задачи вы им решаете? В каких показал себя лучше чем PHP или нода?


    1. alitvinenko Автор
      26.11.2021 10:49
      +1

      Рассказать подробности проекта не могу, информация не публичная.

      Если говорить образно, то Go мы решили использовать для глубокого бэкенда. Где большая нагрузка по количеству запросов на сервис.


    1. QeqReh
      26.11.2021 10:51
      +3

      PHP и Go не конкуренты, у них скорее симбиоз.

      Например с помощью Go решаются задачи с большим объемом данных и трафиком.


      1. Rukis
        26.11.2021 15:56

        Было бы интересно почитать как организуется такой симбиоз на практике.


        1. Djeux
          26.11.2021 16:13

          Бизнес логика на PHP, микросервисы где критичен перформанс на Go.

          У Slack в блоге подобные статьи проскакивали.


        1. QeqReh
          29.11.2021 06:54

          Как вариант gRPC и\или очереди RabbitMQ (и ему подобные).


        1. rudinandrey
          02.12.2021 18:25

          PHP все что касается например интерфейса, работы с пользователями, формочки вот это вот все. На Go можно писать API, сервисы, которые что-то делают в фоне. Я следующий проект так собираюсь делать, если он состоится.


  1. atomheart
    25.11.2021 23:03
    +1

    Про моки доя тестов - в Go тоже все хорошо, и есть отличное решение: https://github.com/golang/mock


  1. Dekmabot
    25.11.2021 23:05

    Примерно такие же ощущения после перехода, каналы мне доставляют огромное удовольствия. Кстати многопоточность на php всё же есть через pcntl_fork, да и файберы уже вот вот подвезут.

    Отсутствие наследования меня не расстроило, наоборот, навешать какой-то метод на любой объект - некая дополнительная степень свободы.

    Правда вот со строгой типизацией приходится повозиться, работая с неизвестным источником. А ещё сначала вгоняло в ступор, что import`ы не могут пересекаться в разных модулях, но это скорее была моя ошибка проектирования.


    1. NiceDay
      26.11.2021 01:01

      pcntl_fork это не про многопоточность, так как к потокам не относится вообще. это про многопроцессность. еще параллельных процессов можно наспаунить, например, через proc_open.

      для многопоточности от слова "потоки" есть pthreads и parallel.

      в parallel, кстати, есть каналы. в остальных подходах они с переменным успехом эмулируются.


      1. domix32
        26.11.2021 15:23

        А процессы разве не создают своих отдельных потоков?


  1. Vitaly83vvp
    26.11.2021 07:56

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

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

    mockedObject("Имя функции", параметры...).Return(что должно возвращаться)

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

    ...Return().Once()

    И, понятно, что mockedObject должен содержать заглушки этих функций.

    У меня была похожая ситуация, но на Go уже давно. Синтаксис и идеология совершенно отличаются от того, к чему привык. Да, тут нет привычного ООП, но много других возможностей. Что-то нравится, что-то нет. В первое время сильно раздражало, что сборка не шла из-за того, что был неиспользуемый пакет или неиспользуемая переменная. Точнее не понимал, почему нельзя было просто игнорировать их. Я понимаю для чего это сделано - Go позиционировался (может и сейчас так же) как язык для обучения разработке, но подобные ошибки не дают сосредоточиться на коде. А, вот, что мне точно нравится в этом языке - результат сборки - статический бинарник, который не зависит от библиотек системы (хотя и бывают некоторые исключения). Достаточно скопировать один бинарник. Без пачки библиотек.


  1. GothicJS
    26.11.2021 10:51

    А если Go сравнивать с Rust - какие плюсы и минусы? Вопрос к тем, кто имеет представление об обоих языках)


    1. lowitea
      26.11.2021 11:26
      +3

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

      Несмотря на то, что эти языки любят сравнивать, они концептуально очень различные. Go создавался как простой язык для микросервисов в корпорации добра (Google), на нём нормально работает подход "сел и написал". Благодаря некоторой утиной типизации и своим подходам к классам, нет необходимости заранее проектировать интерфейс, потом проектировать иерархию классов, как например в Java. В этом весь Go - быстрый, относительно не сложный, не тебующий большой подготовки. К тому же у Гоши, на мой взгляд, очень удобная нативная система сопрограм.

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

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


    1. rjhdby
      26.11.2021 11:27

      Порог входа в Go на порядок ниже, чем в Rust. Во втором придётся знатно вывернуть наизнанку все свои представления о временах жизни и областях видимости.


    1. godzie
      26.11.2021 11:48
      +1

      На расте приятно писать, но через пол года, вернувшись к коду - без пол-литры не разберешься. На GO наоборот, сахара нет, зато читаемость кода лучше. Туда же изменчивость раста и стабильность GO. Ну и если хотите писать низкоуровневые вещи то GC go, его модель памяти и пр. может вставлять палки в колеса, а раст подойдет отлично.

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


      1. Vilaine
        28.11.2021 06:53
        +1

        На расте приятно писать, но через пол года, вернувшись к коду — без пол-литры не разберешься. На GO наоборот, сахара нет, зато читаемость кода лучше.
        По-моему, наоборот крупные кодобазы на Go не очень из-за его невыразительности и слабой типизации. Разработчику нужно больше интеллектуальных способностей, памяти и внимания, чтобы понять и поддерживать код на Go (если не учитывать синтаксис, что дело наживное).


        1. godzie
          28.11.2021 12:30

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

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

          PS GO строго типизированный


          1. Vilaine
            28.11.2021 18:34

            PS GO строго типизированный
            Полудинамически типизированный из-за golangdocs.com/type-switches-in-golang и interface{}. В строготипизированном ЯП рантайм не таскает за собой типы ко всем значениям, хотя бы не так явно и легкодоступно.
            Вот как раз больше интелектуальных способностей и нужно, чтобы понять, что там очередной разработчик сварганил вооружившись, без спорно, очень мощной системой типов раста
            Да не очень мощной, это же не Scala какая-нибудь. Умение читать код на любом ЯП приходит с опытом. Без опыта, конечно, читать некоторые ЯП проще, чем некоторые другие. Я говорю об интеллектуальных требованиях при разработке, которые исходят от сложности самого кода. Сложный код, сложные системы на Go требуют большей памяти и внимательности, чем Rust, где намного проще расслабиться и сосредоточиться на доменной области. Потому что синтаксис и компилятор сильнее держат разработчика в рамках каких-то гарантий.


            1. godzie
              28.11.2021 20:10

              Полудинамически типизированный из-за golangdocs.com/type-switches-in-golang и interface{}. В строготипизированном ЯП рантайм не таскает за собой типы ко всем значениям, хотя бы не так явно и легкодоступно.

              Строго типизированный потому что нет магических преобразований из int32 в int64 например.

              Рантайм в GO ничего не таскает, тем более для всех значений. Информация о ниже-лежащем типе действительно вшита внутрь реализации interface{}. Но это касается только интерфейсов.

              Сложный код, сложные системы на Go требуют большей памяти и внимательности, чем Rust, где намного проще расслабиться и сосредоточиться на доменной области

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


              1. Vilaine
                28.11.2021 22:08
                -1

                Строго типизированный потому что нет магических преобразований из int32 в int64 например.
                Type juggling не имеет отношения к виду типизации. В динамическом ЯП может отсутствовать (в Python, к примеру).
                Динами́ческая типиза́ция — приём, используемый в языках программирования и языках спецификации, при котором переменная связывается с типом в момент присваивания значения, а не в момент объявления переменной.
                Это из Википедии. Динамическая типизация — это когда сигнатура нам не говорит о поведении. Как в Go через interface{} и последующую проверку типа переменной, когда она связывается с типом во время исполнения, динамически. В Go можно вообще не использовать типизацию и всё делать через interface{}. Но чаще всё-таки используют статическую типизацию, поэтому я и называю это "полудинамической типизацией" — как и в Python или PHP с использованием статических анализаторов типов (MyPy, Psalm).

                Можно, конечно, говорить о практиках использования. К примеру, если бы система типов в Go была достаточно выразительна, и никто бы использовал динамическую типизацию. Наверно, в Scala как-то так. Но я вижу это в Go коде лишь чуть реже, чем всегда, поэтому определение «полудинамический ЯП», я думаю, вполне заслужено.
                Вот Rust — это строго-типизированный ЯП без всяких «но» (хотя и там есть мелкое нарушение параметричности).
                Рантайм в GO ничего не таскает, тем более для всех значений.
                variable.(type) можно на переменной любого типа вызвать. Точнее, на переменной с меткой любого типа (в случае динамики некорректно говорить о типизации). Таскает рантайм за собой метки для всех значений. =)
                реализации interface{}.
                Что это вообще значит? interface{} — это просто top type, он населён всеми типами. Другими словами, все типы являются interface{}. Это важно лишь для статического анализа.
                кроме сосредоточения на доменной области нужно еще будет думать о памяти, синхронизации и пр
                Так и на Go вам об этом думать не меньше, а то и больше. Rust — это же не C, там работа в основном под капотом засчет умного компилятора (который может быть умнее из-за сильных гарантий). Но кроме этого с Go ещё нужно ещё держать в голове более полную модель кода, потому что она намного хуже описана типами. В Rust всё же модель кода лучше описана типами и статический анализатор берёт больше работы на себя по контролю над программистом. Поэтому больше личного внимания можно уделить домену. Конечно, после выработки привычки на нем писать.
                Это касается больших кодовых баз. Чем меньше и чем проще система, тем меньше проблем с Go, и меньше бенефитов от лучших гарантий. Собственно, с какого-то объёма оно вообще вообще нормально ложится и на скрипты без всяких типов.


                1. godzie
                  29.11.2021 01:14
                  +1

                  Вы точно не путаете статическую/динамическую типизацию с слабой/строгой? Вами было заявлено что GO слабо типизированный, причем тут википедия и динамическая типизация?

                  Но я вижу это в Go коде лишь чуть реже, чем всегда, поэтому определение «полудинамический ЯП», я думаю, вполне заслужено

                  Если "чуть реже, чем всегда" то это говорит о качестве таких проектов, кстати пиханием interface{} где надо и не надо страдают в основном новички которые перешли с динамических языков. Далее, в расте тоже есть std::any, он теперь тоже "полудинамический ЯП"?

                  variable.(type) можно на переменной любого типа вызвать. Точнее, на переменной с меткой любого типа (в случае динамики некорректно говорить о типизации). Таскает рантайм за собой метки для всех значений. =)

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

                      var a interface{} = int64(5) 
                      print(unsafe.Sizeof(a)) //16, тут 8 бит значение и 8 - тип
                  
                      var b int64 = 5
                      print(unsafe.Sizeof(b)) //8, тип известен
                      на этапе компиляции, хранить ничего не нужно

                  Что это вообще значит? interface{} — это просто top type, он населён всеми типами. Другими словами, все типы являются interface{}. Это важно лишь для статического анализа.

                  Это значит что информация о ниже лежащем типе хранится в переменной тип которой interface{} либо любой другой интерфейсный тип.

                  Чем меньше и чем проще система, тем меньше проблем с Go, и меньше бенефитов от лучших гарантий. Собственно, с какого-то объёма оно вообще вообще нормально ложится и на скрипты без всяких типов.

                  Вобщем вы частное привели к общему, все остальное - маркетинг.


    1. domix32
      26.11.2021 16:19
      +2

      Go
      &plus; супер низкий порог входа. возможно один из простейших способов стать вайтишником за много денег.
      &plus; fibers
      &plus; все что нужно для написания микросервисом из коробки
      − if err != nil
      − генерики отсутствуют. хотя выше по треду обещают, но кто знает сколько их еще будут осваивать и как это повлияет на качество библиотек.
      − архитектура и библиотеки местами склепаны из заглушек потому что
      − не предназначено для general use. например получение прав доступа на разных файловых системах и ОС вернут не то что ожидается, нет доступа к субмилисекундным таймерам и так далее — дьявол кроется в деталях
      − написать дедлок довольно просто
      − все еще нельзя жить без gc. Хотя вроде wasm направление помаленьку продавливает этот вопрос


      Rust
      &plus; система типов, включая трейты и optional-типы
      &plus; good defaults
      &plus; fearless concurrency, memory safety и проче empowering штуки из слоганов по большей части правда
      &plus; general use даже больший чем у C/C++
      &plus; batteries included по всем направлениям радуют людей — документирование, тестирование, шаблонизация проектов (типа wasm-pack), всякие верификации лицензий, уязвимостей и так далее и тому подобное. Туда же всякие areweXYZyet, где XYZ — какое-нибудь направление в разработке — веб, нейронки, геймдев и тд. Правда не знаю как с этим обстоят дела у go дальше тестов и форматирования.
      &plus; отличное исполнение платформозависимых фишек и управление ими в коде.
      − высокий порог входа даже для хардкорщиков из плюсов и си
      − некоторые вещи выражаются слишком громоздко
      − версионирование библиотек часто сопровождаются болью, в частности из-за разделения на stable и nightly
      − иногда слишком фанатичное коммунити
      − мультипоток хоть и достаточно fearless он все еще довольно болезненный.
      − асинхронщину завезли относительно недавно и степень адаптирования довольно низкая. плюс там вроде были какие-то нюансы с executors для них
      − народ очень хочет GAT, но его еще вроде не успели завести, хотя оно уже в пути.
      − у хаскелистов имеются вопросы относительно генерализации некоторых штук


      Но мне до сих пор непонятно почему идет холивар Go vs Rust. Да и минусы начинают всплывать или когда ты только начал и что-то делаешь не так либо когда преисполнился и выжимаешь из языка что можешь.


      1. jenki
        26.11.2021 23:51

        + fibers

        Всё-таки threads. fibers - немного иное

        − if err != nil

        Проверять, что действие не возвращает ошибку не такое плохое решение.

        − генерики отсутствуют

        Без них прям никак?

        − все еще нельзя жить без gc

        Как вы думаете, почему один из создателей ЯП Go, имевший ранее опыт в создании другого довольно успешного ЯП, пошёл на такой шаг? Ведь Си довольно быстрый и в нём нет никакого gc, правда могут быть серьёзные проблемы, когда прозевают момент с управлением памятью.


        1. Devoter
          27.11.2021 00:31

          Всё-таки threads. fibers - немного иное

          Таки горутины ближе к нитям, так как сопрограммы, и уж точно не являются потоками.

          Без них прям никак?

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

          Как вы думаете, почему один из создателей ЯП Go, имевший ранее опыт в создании другого довольно успешного ЯП, пошёл на такой шаг? Ведь Си довольно быстрый и в нём нет никакого gc, правда могут быть серьёзные проблемы, когда прозевают момент с управлением памятью.мусормус

          Это - довольно дискуссионный вопрос, очевидно, что у авторов было ТЗ от Google, чьи задачи и решает язык. Собственно, об этом и авторы рассказывали: низкий порог входа, гарантированная надёжность кода, быстрая компиляция. Очевидно, что какой-то фактор помешал найти подходящую реализацию без GC, возможно, время.


        1. domix32
          27.11.2021 17:11

          Всё-таки threads. fibers — немного иное

          Да, наверное спутал одно с другим. Стоило назвать Goroutines


          Проверять, что действие не возвращает ошибку не такое плохое решение.

          Есть более эргономичные способы по типу Either или Option. В бум развития JS очень в ходу был паттерн error first и функции возвращали кортеж (error, result), и то же делали if error != undefined всюду, нынче же практически не встретишь такого. Жить с этим можно, но код все равно усложняется.

          Без них прям никак?

          жить без них конечно можно, и заниматься копипаст программированием, но нельзя назвать это чем-то хорошим. Кроме того вещи типа вышеупомянутых Either и Option нельзя сделать без генериков насколько мне известно.


          почему один из создателей ЯП Go, имевший ранее опыт в создании другого довольно успешного ЯП, пошёл на такой шаг?

          Я прекрасно понимаю почему в прод добираются managed языки. Однако для большинста таких языков существует опция выключения гц или смены алгоритма управления, то бишь кастомные аллокаторы. Насколько мне известно сейчас есть люди которые пытаются решить эту проблему, чтобы иметь тонкий рантайм для wasm, например, но сталкиваются с ограничениями, наложенными автором на системные API. Естественно все это не first class проблемы, о чем я и написал выше в последнем предложении.


      1. Devoter
        28.11.2021 02:12

        − все еще нельзя жить без gc. Хотя вроде wasm направление помаленьку продавливает этот вопрос

        А можете дать какие-нибудь ссылки на информацию про реализацию Go без GC, а то ничего, кроме пары предложений а issue github и форумах, не удалось.


        1. domix32
          28.11.2021 05:34

          Сходу едва ли найду. Лучше забегите в телеграм чат по wasm, там народ внимательнее следит за таким. Велик шанс, что пока все парой issue и ограничивается, а сам wasm таргет у go пока только для галочки.


  1. WASD1
    26.11.2021 20:04

    Подскажите в Go нет ни исключений ни "монады" Either (когда можно писать весь код, исходя из предположения, что у вас Left значение ("ok"), а Right-значение ("Error") обрабатывать отдельно там, где вы хотите?

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


    1. Devoter
      26.11.2021 20:53

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


      1. WASD1
        26.11.2021 21:50

        >> А в чем беда? В том, что каждый вызов нужно явно обрабатывать?

        Дело не в явной обработке (её где-то надо делать), а в том, что основная логика "разрывается" обработкой ошибок, как в условном C.

        А разные техники (например монадическая с Maybe \ Either или Exceptions) - они позволяют писать основной код так, будто ошибок нет вообще, а потом все ошибки разобрать где-то в специально-отведённом месте.
        Вот в том и дело - есть ли такие техники в Go.

        ПС
        Хотел приложить код на условных C и Haskell - но обнаружил, что не знаю как вставить на хабре код.
        < code > < source > < src > - не работают и за 5 минут не нагуглилось ((


        1. Devoter
          27.11.2021 00:16
          +1

          Как работают исключения, Maybe возврат ошибок в C мне известно. Под явной обработкой и имел ввиду обработку непосредственно за вызовом. Именно решением этой проблемы сейчас и заняты разработчики языка, так как хотелось бы поместить один общий handler в пределах функции, и там обрабатывать все ошибки, которые не нужно обрабатывать на месте, собственно, примерно то, о чем вы и пишите. Если найду ссылку на rfc - могу сбросить, с ходу не нашел, но там предлагается что-то вроде defer, только для ошибок, опять же, если все правильно помню.

          P.S.: Если с телефона, то там внизу для нового абзаца есть tool buttons, а если с ПК, то /.


        1. godzie
          27.11.2021 01:09

          Дело не в явной обработке (её где-то надо делать), а в том, что основная логика "разрывается" обработкой ошибок, как в условном C.

          Так в том и дело, что GO'шники считают обработку ошибок частью основной логики программы. Мне например текущая обработка ошибок в GO очень даже заходит.

          Кстати писать системный код на GO поэтому тоже довольно удобно, C код с errno отлично мапится в GOшный.


  1. gryphon
    27.11.2021 01:58

    ага еще впечатления голанг от уборщицы осталось только написать


  1. Vilaine
    28.11.2021 07:01
    -2

    В Go только строгая типизация.
    В Go скорее полудинамическая типизация. В Go рантайм таскает для каждого значения его тип. Приведение типов используется везде, т.к. часто используется interface{}, что есть аналог any из Typescript. Динамическая диспатчизация (проверка на тип) используется во все ворота, как и в РНР, что есть отказ от строгой типизации.


  1. gnomeby
    07.12.2021 15:42

    С массивами вообще, мне кажется, в PHP все намного проще. В PHP, в отличие от Golang, массив может быть и собственно массивом, и списком, и хеш-таблицей, и словарем, и коллекцией, и очередью, и стеком. И это иногда очень удобно

    Это удобно, но концептуально неверно. В языках где есть чёткое разделение между set, tuple, dict, list у разработчиков часто есть понимание о сложности выполнения тех или иных операций с каждым типом, что бывает полезно.