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

Го - это портируемый Си


Для кого эта статья


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

  • Go очень часто используется для написания backend-сервисов и очень редко для всего остального. Существует ещё две популярные пары для этого же: Java/C# и Python/Ruby. Go, на мой взгляд, нацелен именно на то, чтобы забрать долю у пары Python/Ruby.
  • Go наследует своё странное поведение именно из нюансов синтаксиса Си, неочевидно спрятанных в языке. Поскольку в Go есть чёткие моменты отторжения до такой степени, что порой хочется удалить компилятор Go и забыть, то понимание принципов Си и того, что Go в каком-то смысле является надмножеством Си, позволяет их существенно сгладить.


Что по-поводу пары Java/C#? Go ей ни разу не конкурент, по крайней мере пока он молод (речь про версию Go 1.11).

Чего не будет в статье


  • Мы не будем говорить о том, что Go плох, так как в нём нет фичи X, как в языке Y. У каждого языка свои правила игры, свои подходы и свои поклонники. Хотя кого я обманываю, конечно же об этом нам придётся поговорить.
  • Мы не будем сравнивать напрямую интерпретируемые и компилируемые языки.


А что будет? Только конкретные случаи дискомфорта, которые доставляет язык в работе.

Начало работы


Хорошим вводным по языку мануалом является короткая онлайн книга Введение в программирование на Go. Читая которую вы довольно быстро наткнётесь на странные особенности. Приведём для начала первую партию из них:

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


Поддерживаются только египетские скобки
Поддерживаются только египетские скобки, то есть следующий код не компилируется:
package main

func main()  // Не компилируется
{

}

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

Многострочные перечисления должны заканчиваться запятой
a := []string{
	"q"  // Нет запятой, не компилируется
}

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

Не использовал переменную? Не компилируется!
Нет, это не шутка.
package main

func main() {
	a := []string{
		"q",
	}
	// Не компилируется, переменная не использована
}


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

Неиспользуемые параметры приходится заглушать и это смотрится странно, хотя в питоне так тоже можно:
for _, value := range x {
    total += value
}



Но это всё цветочки и даже просто вкусовщина разработчиков. Теперь перейдём к более тяжеловесным вещам.

«Безопасный» язык


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

Вот цитата одного из создателей языка:
«Ключевой момент здесь, что наши программисты (прим.пер.: гуглеры) не исследователи. Они, как правило, весьма молоды, идут к нам после учебы, возможно изучали Java, или C/C++, или Python. Они не в состоянии понять выдающийся язык, но в то же время мы хотим, чтобы они создавали хорошее ПО. Именно поэтому язык должен быть прост для понимания и изучения.»

Спионерено отсюда: Почему дизайн Go плох для умных программистов.

Так значит вы говорите безопасный язык?
var x map[string]int
x["key"] = 10

и после запуска программы получаем:
panic: runtime error: assignment to entry in nil map


В этом невинном примере мы «забыли» выделить себе память и получили ошибку времени выполнения. Так а какой безопасности может идти речь, если вы меня не спасли от неверной ручной работы по выделению ресурсов?
Хабраюзер tyderh замечает, что:
Безопасность заключается в том, что при выполнении отлавливается ошибка, а не происходит неопределённое поведение, способное произвольным образом изменить ход выполнения программы. Таким образом, подобные ошибки программистов не способны привести к появлению уязвимостей.


Следующий пример:
  var i32 int32 = 0
  var i64 int64 = 0
  
  if i64 == i32 {
    
  }

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

import (
	"fmt"
)

func eq(val1 interface{}, val2 interface{}) bool {
	return val1 == val2
}

func main() {
	var i32 int32 = 0
	var i64 int64 = 0
	var in int = 0

	fmt.Println(eq(i32, i64))
	fmt.Println(eq(i32, in))
	fmt.Println(eq(in, i64))
}

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

powerman поделился ещё один примером ложных ожиданий:
func returnsError(t bool) error {
	var p *MyError = nil
	if t {
		p = ErrBad
	}
	return p // Will always return a non-nil error.
}
err := returnsError(false)
if err != nil {
  # Истина
}

Интерфейс с nil не равен просто nil, будьте осторожны. В FAQ языка этот момент есть.

Ну и завершая про безопасность. Разыменование в языке убрано, а вот спецэффекты в зависимости от вида доступа от доступа (по указателю или по копии) остались. Поэтому следующий код:
package main

import "fmt"

type storage struct {
	name string
}

var m map[string]storage

func main() {
	m = make(map[string]storage)
	m["pen"] = storage{name: "pen"}

	if data, ok := m["pen"]; ok {
		data.name = "-deleted-"
	}

	fmt.Println(m["pen"].name) // Output: pen
}

Выведет pen. А следующий:
package main

import "fmt"

type storage struct {
	name string
}

var m map[string]*storage

func main() {
	m = make(map[string]*storage)
	m["pen"] = &storage{name: "pen"}

	if data, ok := m["pen"]; ok {
		data.name = "-deleted-"
	}

	fmt.Println(m["pen"].name) // Output: -deleted-
}

Выведет "-deleted-", но пожалуйста, не ругайте сильно программистов, когда они на эти грабли наступят, от этого в «безопасном» языке их не спасли.
В чём же отличие в этих чёртовых кусках?
В одном примере:
m = make(map[string]storage)
а в другом:
m = make(map[string]*storage)


Ха, вы думали всё? Я тоже так думал, но неожиданно напоролся ещё на одни грабли:
Наступить на грабли
package main

import "fmt"

var globState string = "initial"

func getState() (string, bool) {
	return "working", true
}

func ini() {
	globState, ok := getState()
	if !ok {
		fmt.Println(globState)
	}
}

func main() {
	ini()
	fmt.Println("Current state: ", globState)
}

Возвращает initial и это верно ибо оператор := создаёт новые локальные переменные. А его мы вынуждены были использовать из-за переменной ok. Опять таки всё верно, но изначально строчка
globState, ok := getState()
могла выглядеть как
globState = getState()

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

А это значит, что теперь нам надо у PVS просить статический анализатор для языка Go.

Краткий вывод: безопасность присутствует, но она не абсолютна от всего.


«Единообразный» язык


Выше в разделе странности компилятора было указано, что при неверном форматировании кода, компилятор упадёт. Я предположил, что это было сделано для единообразия кода. Посмотрим насколько код единообразный.
Вот например, два способа выделить память:
make([]int, 50, 100)
new([100]int)[0:50]

Ну да, ну да, это просто фишка функции new, которую мало кто использует. Ладно будем считать это не критичным.

Вот например, два способа создать переменную:
var i int = 3
j := 6

Ладно, ладно, var используется реже и в основном для резервирования под именем определённого типа или для глобальных переменных неймспейса.

Ладно, с натяжкой будем считать Go единообразным языком.

«Колбасный» код


А вот ещё частая проблема, конструкция вида:
result, err := function()
if err != nil {
    // ...
}

Это типичный кусок кода на Go, назовём его условно колбасой. Среднестатистический код на Go состоит на половину из таких колбас. При этом первая колбаса сделана так result, err := function(), а все последующие так result, err = function(). И в этом не было бы проблемы, если бы код писался только один раз. Но код — штука живая и постоянно приходиться менять местами колбасы или утаскивать часть колбас в другое место и это вынуждает постоянно менять оператор := на = и наоборот, что напрягает.

«Компактный» язык


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

И в первую очередь из-за «колбас», о которых я упоминал чуть выше. Сейчас ноябрь 2018 и все Go программисты ожидают версию 2.0, потому что в нём будет новая обработка ошибок, которая наконец покончит с колбасами в таком количестве. Рекомендую статью по ссылке выше, в ней суть проблемы «колбасного» кода разъяснена наглядно.

Но новая обработка ошибок не устранит все проблемы компактности. По прежнему будет не хватать конструкций in и not in. На текущий момент проверка нахождения в map значения выглядит так:
if _, ok := elements["Un"]; ok {
}

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

Молодой язык и бедный синтаксис


К Go существует очень много написанного кода. И есть просто потрясающие вещи. Но не редко вы выбираете между очень плохой библиотекой и просто приемлемой. Например SQL JOIN в одном из лучших ORM в GO (gorm) выглядит так:
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

А в другом ORM вот так:
query := models.DB.LeftJoin("roles", "roles.id=user_roles.role_id").
  LeftJoin("users u", "u.id=user_roles.user_id").
  Where(`roles.name like ?`, name).Paginate(page, perpage)

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

А вот один из лучших образцов компактного роутинга в вебе:
a.GET("/users/{name}", func (c buffalo.Context) error {
  return c.Render(200, r.String(c.Param("name")))
})

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

Спорные недостатки


Публичные функции


Угадайте, как сделать функцию публичной для использования в других пакетах? Здесь есть два варианта: либо вы знали или никогда бы не угадали. Ответ: зарезервированного слова нет, нужно просто назвать функцию с большой буквы. В это вляпываешься ровно один раз и потом привыкаешь. Но как питонист помню про правило «явное лучше неявного» и предпочёл бы отдельное зарезервированное слово (хотя если вспомнить про двойное подчёркивание в питоне, то чья бы корова мычала).

Многоэтажность


Если вам нужен словарь объектов, то вы напишите что-то такое:
elements := map[string]map[string]string{
		"H": map[string]string{
			"name":  "Hydrogen",
			"state": "gas",
		},
        }

Пугающая конструкция, не правда ли? Глазу хочется каких-нибудь скобочек, чтобы не спотыкаться. К счастью они возможны:
elements := map[string](map[string]string){
        }

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

Атомарные структуры


Их нет. Для синхронизации надо явно использовать мьютексы и каналы. Но «безопасный язык» не будем вам пытаться мешать писать одновременно из разных потоков в стандартные структуры и получать падение программы.
helgihabr любезно напомнил, что в 1.9 появился sync.Map.

Тестирование


Во всех не очень безопасных языках безопасность хорошо реализуется через тестирование с хорошим покрытием. В Go с этим почти всё в порядке, кроме необходимости писать колбасы в тестах:
if result != 1 {
    t.Fatalf("result is not %v", 1)
    }

Понимая ущербность данного подхода, мы сразу нашли в сети библиотеку, реализующую assert и доработали её до вменяемого состояния. Можно брать и использовать: https://github.com/vizor-games/golang-unittest.

Теперь тесты выглядят так:
assert.NotEqual(t, result, 1, "invalid result")


Две конвертации типов


В языке сущность интерфейса имеет особый статус. Они в том числе часто используются, чтобы заткнуть «бедность» синтаксиса языка. Выше уже был пример с реализацией шаблонов через интерфейсы и неявным вредным спецэффектом, порождённым этим случаем. Вот ещё один пример из этой же серии.
Для преобразования типов можно использовать обычную конструкцию в Си-стиле:
string([]byte{'a'})

Но не пытайтесь применить её к интерфейсам, ибо для них синтаксис другой:
y.(io.Reader)

И это довольно долго будет вас путать. Я для себя нашёл следующее правило для запоминания.
Преобразование слева называется conversion, его корректность проверяется при компиляции и в теории для констант может производится самим компилятором. Такое преобразование аналогично static_cast из Си++.
Преобразование справа называется type assertion и выполняется при выполнении программы. Аналог dynamic_cast в Си++.

Исправленные недостатки


Пакетный менеджер


vgo одобрен, поддерживается JetBrains GoLand 2018.2, для остальных IDE как временное решение подойдёт команда:
vgo mod -vendor

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

Достоинства


Прочитав статью может возникнуть предположении, что над нами стоит надсмотрщик с плёткой и заставляет писать на Go, исключительно ради наших страданий. Но это не так, в языке есть фишки существенно перевешивающие все вышеописанные недостатки.
  • Единый бинарник — скорее всего весь ваш проект скомпилится в единый бинарник, что очень удобно для упаковки в минималистичный контейнер и отправки на деплой.
  • Нативная сборка — скорее команда go build в корне вашего проекта соберёт этот самый единый бинарник. И вам не потребуется возиться с autotools/Makefile. Это особенно оценят те, кто регулярно возится с ошибками Си компиляторов. Отсутствие заголовочных файлов — дополнительное преимущество, которое ценишь каждый день.
  • Многопоточность из коробки — в языке не просто сделать многопоточность, а очень просто. Настолько просто, что очень часто просто импорт библиотеки в проект и использование какого-либо её примера уже может содержать явно или неявно в себе работу с многопоточностью и при этом в основном проекте ничего от этого не ломается.
  • Простой язык — обратная сторона бедности синтаксиса — возможность освоить язык за 1 день. Даже не за 1 день, а за 1 присест.
  • Быстрый язык — в виду компилируемой природы и ограниченности синтаксиса вам будет сложно выжирать много памяти и процессорного времени в ваших программах.
  • Строгая типизация — очень приятно, когда IDE в любой момент знает тип переменной и переход по коду работает как часы. Это не преимущество именно Go, но в нём оно тоже есть.
  • Защита от расширения структур — ООП в Go эмулируется структурами и методами для структур, но правило такое, что это должно лежать в одном файле. И это очень хорошо в плане анализа чужого кода, в Ruby есть паттерн подмешивания и иногда чёрт ногу сломит.
  • Отложенная деинициализация. Лучше всего иллюстрируется примером:
    package main
    
    import (
        "fmt"
        "os"
        "log"
    )
    
    func main() {
        file, err := os.Open("file.txt")
        if err != nil {
            log.Fatal(err)
        }
        defer file.Close()
    
      b, err := ioutil.ReadAll(file)
      fmt.Print(b)
    }
    
    Благодаря
    defer file.Close()
    мы сразу сообщаем рантайму, что, в независимости от того каким и где будет выход из функции, в конце надо выполнить определённый код. Это сразу частично решает проблему с отсутствием деструкторов и почти полностью решает проблему отсутствующих контекстов (например питоновский with).


Почему так получилось


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

Резюме


  • Go отлично подходит для быстрого написания экономных и быстрых микросервисов, при этом для этой работы годятся любые опытные разработчики с других языков. Именно в этом вопросе ему мало равных.
  • Go молод. Как верно было отмечено кем-то из комментаторов: «Идея на 5, реализация на 3». Да, как универсальный язык — на три, а чисто для микросервисов на 4. Плюс язык развивается, в нём вполне можно исправить половину описанных недостатков и он станет существенно лучше.
  • Первый месяц работы вы будете бороться с компилятором. Потом поймёте его характер и борьба пройдёт. Но этот месяц придётся пережить. Половина хейтеров языка месяц не протянули. Это надо чётко понимать.
  • Любителям STL надо сказать, что пока придётся собирать с миру по нитке. Ибо пока доступных контейнера три, не считая встроенных map и array. Остальное придётся эмулировать или искать в сторонних библиотеках.


Библиотеки для тестов


  • github.com/vizor-games/golang-unittest — нормальные человеческие assert и check для тестов, похоже на питон, вдохновлялось им же. С нормальным выводом строчек, где именно тест повалился.
  • godoc.org/github.com/powerman/check — библиотека догружающая стандартный тестовый интерфейс, привнося в неё полезные методы типа: Equal, Less,…. Поделился powerman.
  • github.com/stretchr/testify — ещё одна хорошая библиотека для тестов. Поделился esata.
  • github.com/onsi/ginkgo — ещё одна хорошая библиотека для тестов. Поделился tyderh.
  • github.com/smartystreets/goconvey — BDD подход к тестированию, выглядит очень непривычно, но может кому пригодится. Поделился negasus.

Что почитать


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


  1. avraam_linkoln
    20.12.2018 13:04
    -1

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


    1. gnomeby Автор
      20.12.2018 13:36

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


      1. zuborg
        20.12.2018 14:36
        +1

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


        1. 5oclock
          21.12.2018 07:40

          В любом случае, это отловится на этапе компиляции, что вроде как соответствует духу Go?
          Зачем тогда нагружать компилятор неожиданными для программиста функциями по дополнению кода?


          1. Edison
            21.12.2018 18:48

            ну тут проблема в стиле — «вот в json не нужно запятую после последнего элемента ставить, а в Go нужно, ууууу, неудобно».
            @zubor правильно подметил — запятая после последнего элемента в много строчном объекте требуется специально (даже Russ Cox об этом писал), как раз из-за того, что при удалении и добавлении элементов (и генерации кода) не нужно было постоянно проверять последний ли элемент это или нет.


    1. glebasterBajo
      20.12.2018 13:51

      А чего Вы ожидали? Человек разбирался в языке за один присест с книгой Введение в программирование на Go ))
      Я работаю с Питоном несколько лет, попробовав Go остался доволен. Все те «недостатки» про которые все говорят принял как данность, типа: «А, хорошо — здесь это так работает и устроено так! Запомним!»
      Почему-то некоторые начинают сравнивать этот язык со своим любимым/используемым…
      Жаль на работе не дают использовать, мол, специалистов мало на рынке (


      1. gnomeby Автор
        20.12.2018 13:53

        А чего Вы ожидали? Человек разбирался в языке за один присест с книгой Введение в программирование на Go ))

        С чего вы взяли? Я вообще в основном со спецификацией языка работаю и работаю давно.


        1. glebasterBajo
          20.12.2018 13:59

          Ну шутка же была, что уж Вы — смайлики в конце намекают на рофл. Извините.


        1. Stronix
          20.12.2018 15:02

          Я вообще в основном со спецификацией языка работаю и работаю давно.

          Почему же для вас тогда неожиданно то, что в Go всё передаётся по значению?


          1. gnomeby Автор
            20.12.2018 15:38

            Почему же неожиданным, просто в каких-то кусках кода это легко упустить.


        1. qrKot
          20.12.2018 15:05

          Я вообще в основном со спецификацией языка работаю и работаю давно.


          make([]int, 50, 100)
          new([100]int)[0:50]
          


          Вот это, все таки, немного разные вещи. Не то чтобы на выходе вы получали принципиально разный результат… Но констракт слайса длины 50 с капом 100 и создание массива из 100 элементов и после этого взятие слайса из первых 50… Как минимум, в полученом слайсе будет кап 50, и поведение при «растягивании» оного может сильно различаться.


          1. gnomeby Автор
            20.12.2018 15:37

            Это пример из документации, там утверждается, что это идентичные конструкции.


            1. qrKot
              21.12.2018 09:05

              Перечитал, в этом моменте согласен, был неправ, посыпаю голову пеплом.

              Однако некоторые ваши позиции достаточно спорны.

              var i int = 3
              j := 6
              


              Вторая строка — просто сокращенная запись с автовыводом типа. При этом достаточно очевидно, что именно при объявлении переменной предпочтителен первый вариант, ввиду явного указания типа и по принципу наименьшего удивления.
              Однако для возврата значения из функции мы имеем уже:
              var i int = someFuncReturningInt()
              

              против
              i := someFuncReturningInt()
              


              На этом месте нужность сокращенной записи все еще не очевидна, однако при возврате из функции нескольких значений становится понятно, для чего придумана сокращенная запись:
              result, err := some2ResFunc()
              // вполне себе разворачивается в:
              var result ResultType
              var err error
              result, err = some2ResFunc()
              


              Так что претензия к элементарному автовыводу типов выглядит достаточно странной.


              1. gnomeby Автор
                21.12.2018 09:40

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


                1. qrKot
                  21.12.2018 10:10
                  +1

                  Ну, собственно:

                  var i int — способ решения «классической проблемы» char *(*(**foo[][8])())[]

                  Вариант, собственно, хорош, но при этом он ведет(ввиду возможности множественных возвратов) к:

                  var res int
                  var err error
                  res, err = someFunc()
                  


                  А это уже достаточно многословно. И эта проблема решается
                  res, err := someFunc()


                  Сложно придумать что-то лучшее…
                  А для j := 6 оно, собственно, не предназначалось, и не рекомендуется.


        1. Source
          22.12.2018 01:05

          Вы ж сами написали: "Простой язык — обратная сторона бедности синтаксиса — возможность освоить язык за 1 день. Даже не за 1 день, а за 1 присест."
          Что, мягко говоря, вообще не правда. Go — весьма сложный для освоения язык, потому что граблей по нему разбросано неимоверное количество. Даже статьи с подборками писали, типа 50 Shades of Go. Часть граблей имеет какое-то более-менее разумное объяснение, а часть — WAT чистой воды. Но в любом случае, освоить его быстро не получится, т.к. опереться на предыдущий опыт с другими языками не получится.


          1. qrKot
            22.12.2018 19:47

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


            1. Source
              22.12.2018 22:51

              Слишком сильно зависит от бэкграунда, поэтому я бы сказал, что сравнить в общем случае невозможно. Кому-то быстрее Go получится освоить, кому-то — C#.
              Я больше про то, что все эти "за 1 день" — не более, чем маркетинговый булшит.
              Что-то начать писать можно на 1-й день на любом языке (особенно если у вас уже есть пяток ЯП в арсенале), но это не значит, что он освоен.


              1. qrKot
                23.12.2018 20:12

                Я про то, что:
                а) в Go сильно меньше языковых конструкций в принципе, чем в той же Java или C++ или C#. В нем реально примерно столько синтаксиса, что, с хорошим бэкграундом в других языках, читать и понимать код сможешь часа за 3 (без бэкграунда — за день-два), не натыкаясь на непонятные языковые конструкции. Он не то что прост, он прямо примитивен с точки зрения синтаксиса и системы типов.
                б) В «обросших энтерпрайзом» языках есть достаточно крупные фреймворки, большая стандартная библиотека и вот это вот все, без которых нельзя писать на этих языках эффективно. Навскидку, для Java нужны стримы, коллекции (и понимание того, в каких ситуациях какие лучше использовать), да тонна еще всего. В общем, в той же Java между «пониманием синтаксиса» и «умением писать эффективный код», да простят меня Java-программисты, реально пропасть. В Go пропасть существенно поменьше.


      1. Senpos
        20.12.2018 20:28

        Часто читаю отзывы, что питонистам нравится Go и все не могу понять — почему?
        Чем он вас цепляет после Питона?
        Я хоть владею Питоном не профессионально, но даже учить Go и его особенности неприятно. Мне куда ближе тот же Kotlin.


        1. gnomeby Автор
          20.12.2018 22:36

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


        1. wlr398
          21.12.2018 04:39

          С удовольствием перешёл с питона на го. Как уже сказано в соседнем комментарии — быстродействие приложений заметно выше получается. Потребление памяти заметно ниже.
          Интерпретаторы, честно говоря, никогда особо не нравились. Тащить за скриптом чемодан без ручки в виде интерпретатора и библиотек, такое себе развлечение. С го гораздо проще. Ну и по мелочи, потом всё-таки сложилось мнение, что питоновские отступы это не лучшее решение, скобки удобнее. Статическая типизация удобнее. Реализация асинхронности очень понравилась.
          В общем, к питону уже не вернусь.
          Как раз таки, если оперировать словом «хайп», то оно к питону больше подходит.
          Питон тут, питон здесь, питон суют куда только можно. Там где в общем и не очень понятно почему именно питон. Люди, которые даже пары строк написать не умеют и никогда не программировали, есть таких много в айти индустрии, всё равно знают, что питон это круто и модно.


          1. severgun
            21.12.2018 11:06

            Про Go только второй год не затыкаются. До этого были Scala и Ruby. Ну и где они теперь?
            Именно это — хайп. Домашние проектики и десяток стартапов. Go повторит историю Ruby.
            Питону уже нормально так лет, он в топ3, он в матером продакшене.


            1. wlr398
              21.12.2018 12:13
              +1

              он в матером продакшене

              Это да, гигатонны костылей, одноразовых и райтонли скриптов, всякой внутренней автоматизации…


            1. Edison
              21.12.2018 18:55

              > Домашние проектики и десяток стартапов.

              Ничего себе заявления. А вы случайно свои сайтики не в DigitalOcean хостите? А дропбоксом пользуетесь?
              Все проекты в CNCF написаны на Go (а те что не были, как linkerd, переписали на Go). Вот тут у Go как раз своя ниша — все что связано с платформой.

              Как бы вам не хотелось, но Go это как раз не домашние проектики.


        1. anonymous
          21.12.2018 08:43

          Я пишу на питоне с версии 1.4(примерно с 2003 года), пару лет назад перешёл на go. Так вот основное преимущество go для меня в том, что на нем нельзя так просто выстрелить себе в ногу, как это можно сделать на питоне. Ошибки на этапе компиляции, статическая типизация, потребление памяти, горутины и предсказуемое поведение программы.


          1. gnomeby Автор
            21.12.2018 09:31

            Я пишу в компаниях, где принято покрывать код тестами, и при таком подходе на выходе, что Python, что Go дают результаты одинакового качества.


            1. qrKot
              21.12.2018 09:40

              Например, не совсем правда…


            1. 0xd34df00d
              21.12.2018 17:00

              Зачем тестировать то, что может проверить тайпчекер?


              1. gnomeby Автор
                21.12.2018 17:12

                Чтобы проверить не поменялась ли логика при рефакторинге.


                1. 0xd34df00d
                  21.12.2018 17:17

                  А можно пример такой логики, которая может поменяться при рефакторинге?


                  1. gnomeby Автор
                    21.12.2018 17:47

                    Любое бизнес поведение программы.


                    1. 0xd34df00d
                      21.12.2018 17:50

                      Хорошо, давайте с другой стороны. А можно пример такого рефакторинга, который бы сломал бизнес-логику приложения?


                      1. gnomeby Автор
                        21.12.2018 17:57

                        У вас метод с большим количеством вложений if и for в друг друга. При очередном фиксе было решено разбить на 4 разных метода вызывающих друг друга. Но не все вызовы были расставлены правильно.


                        1. 0xd34df00d
                          21.12.2018 18:07

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


                          Условно, вложенный for пробегает по подмассиву — значит, у соответствующей функции будет тип [a] -> [a], а у вызывающей её — [[a]] -> [[a]]. С if'ами и фильтрацией, скажем, чуть сложнее, но можно аналогично.


                          1. gnomeby Автор
                            21.12.2018 18:09

                            Я дружелюбно вам завидую, но вакансий в моём городе по Haskell нет.


                1. qrKot
                  21.12.2018 17:25
                  +1

                  Тут фишка в чем:
                  — либо вы пишете тесты для проверки логики + тесты на валидность типов входных данных;
                  — либо вы пишете тесты для проверки логики, а валидность типизации входных данных за вас делает тайп-чекер.

                  Т.е. тайпчекер — это «встроенный в язык тест на валидность типа входных данных».


                  1. 0xd34df00d
                    21.12.2018 17:51

                    — либо вы не пишете тесты, а валидность логики тоже проверяет тайпчекер.


                    1. gnomeby Автор
                      21.12.2018 17:59

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


                      1. 0xd34df00d
                        21.12.2018 18:17

                        Как раз недавно в соседнем треде похожий пример приводил.


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


                        -- свойства сложения
                        sumZero : (x : MyInt) -> 0 + x = x
                        sumAssoc : (x, y, z : MyInt) -> x + (y + z) = (x + y) + z
                        sumCommutes : (x, y : MyInt) -> x + y = y + x
                        
                        -- свойства вычитания
                        subZero : (x : MyInt) -> x - 0 = x
                        subXX : (x : MyInt) -> x - x = 0
                        
                        -- совместные свойства
                        sumSubId (x, y : MyInt) -> (x + y) - y = x
                        sumSubCommutes1 (x, y, z : MyInt) -> (x + y) - z = (x - z) + y
                        sumSubCommutes2 (x, y, z : MyInt) -> (x + y) - z = x + (y - z)
                        sumSubExpands : (x, y, z : MyInt) -> x - (y + z) = (x - y) - z

                        Эти типы будут утверждениями соответствующих очевидных теорем, а их реализации — доказательствами этих теорем. То есть, доказательствами корректности соответствующей логики. Ну там, Карри-Говард, изоморфизмы всякие, propositions-as-types, все дела.


                        И их даже не надо запускать. Достаточно того, что они лежат рядом с MyInt и тайпчекаются.


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


                        1. Acciduccu
                          22.12.2018 14:05
                          -1

                          Учитывая наличие в вашем коде subZero, это должен быть fatality checker.


        1. prospero78su
          21.12.2018 08:53

          Go не идеален. Фейлы после питона можно получить на ровном месте (работа с памятью местами удручает, да).
          Но:
          1. Один файл.
          2. Скорость.
          3. Многопоточность на всех ядрах машины без лишних телодвижений.
          4. Мультиплатформенность. (Да, даже с питоном там не всё в порядке, не говоря уже про С/С++)
          И 5. (надо было поставить первым) строгая статическая типизация решает.


      1. kalininmr
        21.12.2018 05:22

        только вот со структурами как то странновато сделано в плане наследования.
        хотя по своему тоже фича


        1. gnomeby Автор
          21.12.2018 09:14

          Это самая необременяющая реализация типа ООП над структурами Си. При относительной дешёвости достигается гораздо лучшее чтение кода.


          1. kalininmr
            21.12.2018 14:14

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


            1. qrKot
              21.12.2018 14:29

              Наследование странное, вероятно, потому, что его нет… Композиция есть, наследования нету.


              1. kalininmr
                22.12.2018 00:54

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


                1. qrKot
                  22.12.2018 19:48

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


                  1. kalininmr
                    23.12.2018 00:44

                    в некотором роде таки есть.
                    и в принципе понятно как это работает.
                    наверное при таком подходе всё даже логично.


    1. green_wizard_987
      20.12.2018 13:51

      ИМХО, достаточно нетривиальное поведение.


    1. 5oclock
      21.12.2018 07:11

      Вы так говорите, будто это что-то хорошее
      :)


  1. Free_ze
    20.12.2018 13:09

    Почему гоферы так радуются единому бинарнику, преподнося его как огромный плюс? (Или почему так не делают любители других компилируемых технологий, вроде C++?) Деплой же обычно автоматизирован, а динамическую линковку когда-то тоже преподносили как решение некоторых проблем. Каков здесь истинный практический смысл?


    1. gnomeby Автор
      20.12.2018 13:38

      1. Потому что Go отъедает рынок у python, php, ruby и пр, где нет единого бинарника.
      2. Потому что иногда приходится возится с бинарником напрямую, и когда это один файл — это удобно.

      Да, в проекте, где CI уже настолько автоматизирован, что нет в нём никаких правок, это преимущество утрачивается.


      1. Source
        22.12.2018 01:24

        Потому что Go отъедает рынок у python, php, ruby и пр, где нет единого бинарника.

        Это фантазии любителей Go. Python, PHP, Ruby и так для HighLoad не часто использовались. А писать, к примеру, какую-нибудь админку (внутренний проект) с развесистой бизнес-логикой для 1000 юзеров в месяц на Go — это надо сильно упороться.
        Go играет на поле Scala, Elixir, Haskell, Rust, C-расширения. Причём первые 3 дают более приятные возможности для программирования.


        Потому что иногда приходится возится с бинарником напрямую, и когда это один файл — это удобно.

        Зачем с ним возиться напрямую? Вы по FTP что-ли деплоите, как в старые ламповые времена?


        1. qrKot
          22.12.2018 19:52

          Go играет на поле Scala, Elixir, Haskell, Rust, C-расширения.


          Го играет, в первую очередь, на поле node.js, и отлично играет, собственно.

          Причём первые 3 дают более приятные возможности для программирования.


          Scala… ну, как бы, если вы фанат функционального подхода — да, она лучше Go. Однако императив проще в понимании, поэтому функциональное программирование в массах «не взлетает».


          1. Source
            22.12.2018 22:58

            Ну, кстати, да. Про Node.js забыл.
            А что касается массовости, массам и Go не нужен, потому что для большинства проектов вообще пофиг отвечает у тебя сервер за 100 ms или за 30 ms.


          1. 0xd34df00d
            23.12.2018 17:57

            Однако императив проще в понимании, поэтому функциональное программирование в массах «не взлетает».

            Не проще. Привычнее, если вы последние 10 лет писали на императивных языках, да, но не проще.


    1. tyderh
      20.12.2018 13:47
      +1

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


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


      Короче, тут не особо важно, один бинарник или нет, важно, что сборка не вызывает такого количества боли.


    1. zuborg
      20.12.2018 14:38
      +2

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


      1. Free_ze
        20.12.2018 14:55
        -1

        С этим у меня обычно справляется пакетный менеджер)


      1. odiemius
        21.12.2018 00:32
        +2

        А потом, когда у Вас _ВСЁ_ запускаемое будет статическим Вы будете лихорадочно бегать по магазинам скупая оперативную память? Статическая линковка хороша до поры до времени или в спецслучаях.


        1. powerman
          21.12.2018 06:47

          Времена немного изменились. Раньше действительно было именно так. Сейчас — памяти много больше в любом компе, она заметно дешевле, а количество одновременно запущенных программ изменилось не так сильно (если изменилось вообще), чтобы несколько лишних мегабайт в тех программах, которые написаны на Go, имели хоть какое-то значение.


          1. staticmain
            21.12.2018 10:38
            +1

            Заметно дешевле? ЗАМЕТНО ДЕШЕВЛЕ? Когда вы в последний раз смотрели цены вообще? Сейчас просто не продаются материнские платы под DDR3, только под DDR4, а у них несовместимые разъемы. А DDR4 сейчас стоит как паровоз, даже если пытаться набрать тот же объем что и в DDR3. А теперь представьте, что у вас 1000 серверов по всей россии и их надо периодически обновлять, потому что железо со временем дохнет. А когда у вас 20, 50, 100 микросервисов на Го — ваши «несколько мегабайт» множатся на 20, 50, 100.


            1. gnomeby Автор
              21.12.2018 10:43

              Конечно лучше же тупо выжрать в 5 раз больше памяти аналогичным сервисом на Python.


              1. staticmain
                21.12.2018 10:44

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


                1. gnomeby Автор
                  21.12.2018 11:05

                  На Си не пишут микросервисы. У вас были опасения по поводу того, что микросервисы на Go могут выжрать дополнительную память. Так вот микросервисы на всём остальном выжрут её гораздо больше.


                  1. staticmain
                    21.12.2018 12:10
                    +1

                    На Си не пишут микросервисы.

                    Ну я вас расстрою. Мы пишем. И все работает не жря память, HDD и процессор.
                    image


                    1. gnomeby Автор
                      21.12.2018 12:13

                      Вы исключение и прекрасно это понимаете.


                      1. staticmain
                        21.12.2018 12:16

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


                        1. qrKot
                          21.12.2018 12:20

                          а можно ссылочку на уязвимость языка?


                          1. staticmain
                            21.12.2018 12:22

                            www.linux.org.ru/news/development/14673888
                            Пока существует только одна реализация — это уязвимости языка.


                            1. qrKot
                              21.12.2018 12:36

                              Ну, как я и думал, уязвимости в инструментарии, «написанном на...»

                              К самому языку, опять же, ортогональны. Таких уязвимостей на любой язык натаскать можно. Тем более в контексте противопоставления C — сколько еще раз мы будем слышать чУдную историю про выход за границы массива, неочищенную память и все вот это?

                              Пока существует только одна реализация — это уязвимости языка.


                              Вы точно не бредите? Давайте посмотрим на то, что вы показали:

                              1. directory traversal in «go get» via curly braces in import paths

                              Чудно, уязвимость в go get. 3 вопроса:
                              — Вы в курсе, что go get не единственная реализация пакетного менеджера (в конце концов, git делает ровно то же самое)?
                              — Пусть go get — официальная тулза экосистемы, отличненько. Вам список уязвимостей npm, pip и прочих показать? Или сами отыщете?
                              — Вас смущает то, что оно собрано внутрь бинаря go. Дык, бинарь go — это не язык, это компилятор, линковщик пакетный менеджер и все вот это. Уязвимости компиляторов других языков так же сами найдете, или погуглить за вас?

                              2. Вторая чУдная уязвимость «remote command execution during „go get -u“» в том же go get. Те же вопросы повторить?

                              3. Третья: crypto/x509: CPU denial of service in chain validation

                              Уязвимость в стандартной библиотеке. Покажите мне уже язык, в котором НЕ находили уязвимости в стандартной библиотеке.


                        1. gnomeby Автор
                          21.12.2018 12:26

                          Не первый и не последний софт с уязвимостями. Выпустят патч и всего делов. А вот вы например уверены, что ваши программисты предотвратили переполнение буфера во всех местах. valgrind`ом каждый раз софт прогоняют? На Эльбрусе пробовали запускать, для аппаратного отсечения уязвимостей?


                          1. staticmain
                            21.12.2018 12:29

                            На эльбрусе нет смысла прогонять, мы под него не пишем. У нас используется статический анализ тремя тулзами (мерзкоPVS, cppcheck, clang), перед тем как отправлять на прод мы смотрим valgrind на предмет утечек и чтений\записи мимо. На самих узлах supervisord, который в случае падения вышлет нам полный отчет с core-dump'ом на почту. Плюс мы используем grafana-over-zabbix чтобы в real-time видеть что происходит.


                            1. gnomeby Автор
                              21.12.2018 12:34

                              Я же говорю, вы приятное исключение. С таким осознанным подходом можно писать на чём угодно хорошо. Даже для Go вы выключили бы статическую линковку и всё было бы хорошо.


                            1. 0xd34df00d
                              21.12.2018 17:11

                              Попробуйте собирать с asan вместо использования valgrind, кстати.


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


                              1. staticmain
                                21.12.2018 17:49

                                Не собирается. У нас на узлах старое ядро, на которое не ставится glibc той версии, в которой появился asan


                                1. 0xd34df00d
                                  21.12.2018 17:52

                                  Эм, asan вообще появился в clang, а потом в gcc.


                                  Но там с собой .so-шку таскать надо, да.


                    1. qrKot
                      21.12.2018 12:18

                      не жря память, HDD и процессор


                      Ортогонально линковке, например. Рискну предположить, что дело, скорее, в ручном управлении памятью.


                      1. MikailBag
                        21.12.2018 14:26

                        Т.е. вы реально готовы к тому, что каждая программа будет тащить копию джавы, Qt или электрона (т.е. Google Chrome)?


                        1. MikailBag
                          21.12.2018 14:28

                          И это не говоря о том, что ядро-то одно на всех. Непорядок. Надо каждую программу с ядром в один EFI-бинарник линковать)


                        1. qrKot
                          21.12.2018 14:37

                          Т.е. вы реально готовы к тому, что каждая программа будет тащить копию джавы, Qt или электрона (т.е. Google Chrome)?


                          В условиях изолированных контейнеров оно именно так и работает, например.

                          При этом на рантайме java дисковое пространство сэкономить еще как-то можно, слоями контейнера, а вот про процессор и память — тут уж, простите, никак. Каждый запущенный изолированный процесс будет запускать свою java, свою Qt(которая, к слову, достаточно невелика), свой электрон(правда, непонятно, каким он боком в сфере применения Go).

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


                1. qrKot
                  21.12.2018 11:29

                  Основная ниша Go — микросервисы. А их принято запускать в изолированном окружении. Т.е. основная фича разделяемых библиотек вида «одну либу слинковать в несколько бинарей» отпадает. Ну не могут два изолированных окружения шарить память между собой.

                  Поэтому и сравнение динамически-линкованного сишного бинаря со статически собранным Go-шным, мягко выражаясь, некорректное. Вся разница в том, что Go носит все свои зависимости выше libc в себе, а C — линкует их динамически. Сбилдите статический C-шный бинарь и сравните с Go-шным… Вас ждет удивление, вероятно.


              1. Free_ze
                21.12.2018 11:23

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


            1. powerman
              21.12.2018 17:20
              +1

              Когда у меня 100 микросервисов, и в каждом, условно, 5MB общего кода, то это всего пол гига памяти. Учитывая, сколько денег стоит написать 100 микросервисов, что запуск всех 100 на одном сервере это практически невероятная ситуация, и что обычно они запускаются в контейнерах (которые всё-равно помешают использовать общие динамические библиотеки если только не пойти на какие-то дикие танцы с бубном) — статическая линковка не является реальной проблемой для 99.9% проектов.


              1. qrKot
                21.12.2018 17:26

                Красава, например, ППКС.


        1. gnomeby Автор
          21.12.2018 08:26

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


          1. severgun
            21.12.2018 11:11

            Новый гейтс?
            64 Гигов хватит всем?


            1. gnomeby Автор
              21.12.2018 11:15

              Я про то, что не видно тенденций к тому, что всё запускаемое будет статическим.


        1. gnomeby Автор
          21.12.2018 11:06

          Когда «наступит до поры до времени» вы сможете пересобрать без статической линковки, go просто собирает так по умолчанию.


    1. Eagle_NN
      20.12.2018 14:53

      В серьезных проектах на C++ периодически сталкивался с ситуациями (особенно в *nix системах) когда программа «ведет себя странно». В результате анализа выяснялось что по тем или иным причинам на runtime загружается не та версия библиотеки (не из того каталога к примеру). В этом случае монолит — спасение.
      С другой стороны иногда осознанно хочется поделить на несколько частей, для компактности обновления, к примеру.


      1. Free_ze
        20.12.2018 15:01
        +1

        Что есть смысл все свое таскать с собой буквально я не ставлю под сомнение) Удивляет то, что это входит в TOP-5 фич Go, хотя решение очень распространенное.
        Видимо, дело в нише, где Go конкурирует со скриптовыми языками, где нет ни Java-jar'ов, ни сборок в стиле .NET


        1. Eagle_NN
          20.12.2018 15:14

          Данный TOP-5 вообще слабо коррелирует с моим пониманием.
          Чего стоит хотя-бы наличие одномоментно 2-х пунктов:
          Многопоточность из коробки — в языке не просто сделать многопоточность, а очень просто.
          Быстрый язык — в виду компилируемой природы и ограниченности синтаксиса вам будет сложно выжирать много памяти и процессорного времени в ваших программах.


          1. Free_ze
            20.12.2018 15:47

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


            1. Eagle_NN
              20.12.2018 16:39

              Я может непонятно выразился.
              Дело в том, что если многопоточность реализуется легко, то несколько потоков будут намного более активно выжирать и память и процессор, нежели 1 поток. (на самом деле в Go потоков будет столько-же сколько ядер процессора, а остальная параллельность будет реализована программно)

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


              1. gnomeby Автор
                20.12.2018 16:42

                GOMAXPROCS и всё в ваших руках, регулируйте в нужную сторону.


              1. Free_ze
                20.12.2018 17:29

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


                1. gnomeby Автор
                  20.12.2018 17:33

                  Любая статья на Хабре — мнение автора различной квалификации. Для оченки качества этого мнения даны различные инструменты.


                  1. Free_ze
                    20.12.2018 17:45

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


          1. gnomeby Автор
            20.12.2018 16:43

            Укажите ваше понимание, я смогу подправить ТОП, если соглашусь.


        1. severgun
          21.12.2018 11:17

          Не ТОП5, а ТОП1.
          Когда нет карт, приходится делать вид, что у тебя есть козыри.


      1. klirichek
        21.12.2018 09:48

        Ну, это одна из причин, почему получает распространение контейнеризация в той или иной форме. И речь не только про docker/kubernetes на сервере, на пользовательском прикладном уровне. Вроде тех же снапов в убунте.


    1. inew-iborn
      20.12.2018 15:40

      Скорее всего исчезает проблема в решении зависимостей от разных версий библиотек


      1. Free_ze
        20.12.2018 15:54

        Зависимости можно положить в подкаталог, например.


        1. inew-iborn
          20.12.2018 16:49

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


          1. Free_ze
            20.12.2018 17:33

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


            1. gnomeby Автор
              20.12.2018 17:42

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


            1. inew-iborn
              20.12.2018 19:48

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

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


            1. inew-iborn
              20.12.2018 20:06

              такой надуманный пример.
              Допустим у нас есть 3 микросервиса, которому нужна зависимость от библиотеки, библиотека ставит табы(ох уж этот мир javascript, интриги, расследования… факАпы, обнималшкиАпы).

              И каждый микросервис требует свою версию, вот тут и возникает проблема…
              Так понимаю в golang, опять же, из-за своей ниши решили категорично решить эту проблему. Просто в других языках, именно компилируемых, не пытались занимать именно эту нишу и решать эту проблему. А в go решили ее решить, так как создавался он Google и под свои решения.

              Может быть из-за этого…
              (хотя может быть в каких-то она и решена)


            1. qrKot
              21.12.2018 09:23
              +1

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


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

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

              В общем и целом вопрос в комплексности деплоя/доставки. Го проектировался под сетевые сервисы, и конкретно их задачи решает, и решает хорошо. Соответственно, он находится в той же области, что Docker, и решает примерно те же задачи. Отсюда и предпочтение монолитам — принести на сервер цельный бинарный кусок сильно проще, чем настроить деплой, конфигурирование сервера под задачу, настройку, установку зависимостей и прочее развертывание.

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


              1. Free_ze
                21.12.2018 11:47

                Взгляните на node.js. Простенький проект с исходниками в пределах полумегабайта просто при ините для того, чтобы собраться/запуститься докачивает пару-тройку сотен мегабайт своих зависимостей

                1. Текстовый формат действительно менее эффективен, чем бинарный. Любой типичный проект только в сорцах будет весить немало. NPM вроде бы не умеет в дедуплекацию, из-за чего порождает чертовы фракталы. Но это его проблема, а не концепции.
                2. npm init делать не нужно, ведь программист будет копипастить дистрибутив, а не собирать проект на продакшне, как глупец.
                3. Никто не мешает сбандлить JS со всеми зависимостями в один монолитный файл.
                4. Нода у клиента нужна потому, что это рантайм. Суть не поменяется, если вместо скриптовых файлов будут бинарники. Например — линуксовые пакеты (deb), в которых есть информация о зависимостях, которая в идеале должна без проблем разруливаться пакетным менеджером.


                Да и доставить на целевую машину 1 монолитный бинарник, как правило, проще

                Чем именно это проще? Команда копирования как была одна, так и останется.


                1. gnomeby Автор
                  21.12.2018 11:52

                  Чем именно это проще? Команда копирования как была одна, так и останется.
                  Мы тупые разработчики нового поколения, когда мы одной командой копирования копируем один файл, мы не стрессуем.


                1. qrKot
                  21.12.2018 12:16

                  NPM вроде бы не умеет в дедуплекацию, из-за чего порождает чертовы фракталы. Но это его проблема, а не концепции.


                  Не выгораживайте концепцию. Любая палка — о двух концах. Поэтому просто выпишите на листочке в два столбика pros/cons и решите, что лично для вас и в каких случаях лучше.

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

                  npm init делать не нужно, ведь программист будет копипастить дистрибутив, а не собирать проект на продакшне, как глупец.


                  Классическое УМВР, ага…

                  Никто не мешает сбандлить JS со всеми зависимостями в один монолитный файл.


                  Но никто не избавит от необходимости притащить туда же node.js нужной версии. А потом обновлять…

                  Нода у клиента нужна потому, что это рантайм.


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

                  Можете сравнить с тем же pyhon'ом или node'ой. Зависимости вы можете упаковать в один бандл с приложением — для этого у вас есть соответствующие инструменты, а рантайм — хрен.

                  Т.е. вы можете в процессе деплоя иметь (на выбор): 1 головняк, 2 головняка, 3 головняка. Go предлагает остановиться на цифре 1. Вас это может не устраивать, но это уже вкусовщина. Хотите 2? Ну, есть ключи сборки в Go — отпилить зависимости отдельно, выпилить рантайм и все вот это. Просто по дефолту Go предлагает цифру 1, и это в его нише — отличная цифра.

                  Суть не поменяется, если вместо скриптовых файлов будут бинарники.


                  Ну да, для деплоя особой разницы нет, прямо не спорю. Возьмите ту же Java. Помните, 1/2/3-выбери нужное? Вы можете собрать зависимости внутрь jar'а и будете носить 2 куска (рантайм + все остальное), вы можете собрать jar'ник без зависимостей и будете носить 3. А теперь еще пилят (может, уже и допилили) умелку собрать java-рантайм в тот же бинарь, причем только те куски рантайма, которые нужны и получить ровно то, что по дефолту предлагает Go. И тут по барабану, компилируемый ли это язык, или интерпретируемый — вообще не роляет. Роляет только выбор: 1, 2 или 3. При этом, есть конкретные предпочтительные случаи на каждую из 3 цифр, но во всех из них 1 проще, чем 2 или 3.

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


                  Ключевую фразу пометил. На практике есть неразрешимые на уровне пакетного менеджера кейсы.


                  1. Free_ze
                    21.12.2018 12:48

                    Не выгораживайте концепцию.

                    А то у вас половина аргументов выпадет?) Я не против, что вы будете апеллировать к недостаткам Nodejs, но пометку о том, что это такой себе референс все же стоит оставлять. Сами по себе недостатки ноды — это не преимущества Go.

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

                    В чем именно проблема? Что ломается по дороге, если все необходимое тащится скопом?

                    Классическое УМВР

                    Это сюда каким боком? Использовать технологию неправильно и говорить, что она в этом виновата — это такая себе методика сравнения.

                    Go в одном бинаре носит сразу и рантайм

                    У Go есть рантайм? Или вы про стандартную библиотеку?

                    вы можете в процессе деплоя иметь (на выбор): 1 головняк, 2 головняка, 3 головняка.

                    Эти «головняки» сильно преувеличины.
                    Зависимости — это не головняк вовсе, а способ организации проекта. Как я уже выше писал, в ноде вам никто не запрещает собирать единый бандл.
                    Наличие рантайма — это зависит от конкретной технологии. Вполне реально рантайм таскать вместе с конкретным проектом.

                    Ну хорошие в Go дефолты — это отлично. Но чтобы это было киллер-фичей технологии...)


                    1. qrKot
                      21.12.2018 13:29

                      У Go есть рантайм?


                      Есть, даже у C он есть. В этих ваших Линуксах, например, libc называется, и даже несколько альтернативных реализаций имеется: glibc, eglibc, libmusl и все вот это. В системе без libc C-приложения не запускаются.

                      Хотя, в случае C проблемой доставки это назвать сложно, т.к. весь остальной юзерспейс работает ровно поверх системной libc, а значит и собственно linux-окружение без libc работоспособным считаться не может.

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

                      Есть, конечно, интерпретируемые вещи, вроде той же node.js, python — у них в понятие «рантайм» входит непосредственно интерпретатор. Есть компилируемые Java и C# — для них рантаймом считается ВМ. Одно остается фактом: все то, что общепринято считается рантаймом для перечисленных языков, на целевой системе один хрен работает поверх все того же C-шного рантайма.

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

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

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

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


                    1. qrKot
                      21.12.2018 13:40
                      +1

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


                      Собственно, Go — достаточно нишевой язык. Если целые области, где его применять не стОит. И вот конкретно в его нише (сетевые сервисы, контейнеризованные приложения и т.д.) решения авторов языка местами очень хороши (хоть и не без причуд). Как бы, если вам не нравится референс на Node.js, скажите, с кем предлагаете сравнивать, попробую референсить на другую экосистему. Просто именно в нише, под которую Go разрабатывался, монолитный бинарь — действительно фича, и при этом немаловажная.

                      Сами по себе недостатки ноды — это не преимущества Go.


                      В контексте сферы применения Go… Кхм… Именно недостатки ноды и есть ключевые преимущества Go. У них ниши очень сильно пересекаются, и проектировался язык именно так, чтобы НЕ наступить именно в ту лужу, в которую села нода.

                      При этом, фактически, в роли «микросервисных» языков у Go достаточно много и других конкурентов (тупо навскидку: Java, Python, C# — тысячи их), однако, что характерно, ни один из них не разрабатывался изначально с прицелом на сервисную архитектуру, а потому им «есть куда отступать». Для каждого есть сфера, в которой он откровенно лучше Go, и гошные решения в которой будут выглядеть, как минимум, странно. А ноде отступать, честно говоря, некуда… Особенно с учетом ухода автора ноды в Go-разработчики.


                    1. qrKot
                      21.12.2018 14:01

                      В чем именно проблема? Что ломается по дороге, если все необходимое тащится скопом?


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

                      Тупо навскидку возьмем даже C. У него рантайм-зависимость ровно та же, что у Go. Однако остальные зависимости в C принято размазывать по системе. На целевой машине может оказаться какой-нибудь грязных хак в path, там может оказаться что-то из стандартной библиотеки не той версии, там может оказаться просто какая-то не обновленная версия библиотеки, да все что угодно случиться может.

                      Без вопросов, для C эта часть «головняка» решаема тупо статической сборкой… Однако, стоп… Чем в этом случае C-шный бинарь, по сути, будет от Go-шного отличаться? По факту, в решении вопроса доставки C не лучше Go. Да, и не хуже, потому что ничего (в плане простоты деплоя) не может показать результаты лучше, чем статически собранный C-шный бинарь под целевую платформу. «Положил, и оно просто работает» — именно этот подход. Прикол в том, что никаких Go-шных специфических бинарников и не существует. Они ничем от статически сбандленных C-шных не отличаются, они и есть C-шные.

                      А в подавляющем числе прочих конкурентов есть некоторая своя «проблематика».

                      Ты взял python, запилил в нём venv, проверил, у тебя все работает? Выкуси, на сервере python другой версии.

                      Ты собрал маленький jar-ник? Не вопрос, какая версия java у тебя стоит?

                      Ты упаковал исходники node-проекта и lock-файл приложил? Обломись, с сервера нет линка до репозиториев npm, и зависимости не подсосались. Или подсосались, но свежее твоих разработческих, а в них что-то поломали. Или все хорошо, но на сервере нода другая, старше твоей, а в ней что-то поломалось. Или твоя старше, ты не обновился, а сервер обновился, а в новой версии что-то сломалось…

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


                    1. qrKot
                      21.12.2018 14:14

                      Это сюда каким боком? Использовать технологию неправильно и говорить, что она в этом виновата — это такая себе методика сравнения


                      Погодите, ваши же слова:
                      npm init делать не нужно, ведь программист будет копипастить дистрибутив, а не собирать проект на продакшне, как глупец.


                      «И эти люди запрещают мне ковыряться в носу?» Как так не делать npm init? Где программист будет собирать дистрибутив, и зачем его копипастить? Вы реально считаете, что кодер сбандлит бэкенд на своей разработческой машине, положит полученный, простите за выражение, артефакапт на прод, и это норм?

                      Насколько я помню, «правильное использование» nod'ы выглядит так:
                      1. Взять окружение, максимально соответствующее тому, что крутится в проде (но чистое, «голое, если хотите»). На всякий случай заметим, что машина разработчика, как правило, существенно отличается от прод-окружения, поэтому просто сбандлить проект на месте — плохая идея. На сервере тысячу раз все может «пойти не так».
                      2. Положить в него исходники (именно голые, из репозитория), сказать npm init или yarn или whatever чего вы там еще используете, после этого собрать проект (рекомендуется таки обновлять зависимости). Собственно, лучше иметь 2 идентичных сервера, один из которых — отладочный. На нем и собирать.
                      3. Оттестировать, что оно собралось и все еще работает.
                      4. Доставить новую версию на прод (в теории весьма неплохо иметь билд-сервер, и в момент доставки на прод еще раз прогонять тесты).


                      1. Free_ze
                        21.12.2018 15:33

                        Да, не init, а install, прошу прощения.

                        Вы реально считаете, что кодер сбандлит бэкенд на своей разработческой машине, положит полученный, простите за выражение, артефакапт на прод, и это норм?

                        Да, это так и должно происходить. Билд-сервер собирает, линтит, гоняет тесты, делает пакет, который деплоится на боевой/близкий к боевому тестовый сервер.

                        Насколько я помню, «правильное использование» nod'ы выглядит так

                        Не вижу ни единой причины более считать сборку проекта на сервере клиента правильным решением.


                        1. qrKot
                          21.12.2018 15:42

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


                          Один нюанс: билд-сервер воспроизводит боевое/близкое к боевому окружение и в нем собирает пакет бандл, который после тестов деплоится «в бой».


                          1. Free_ze
                            21.12.2018 15:53

                            Это уже зависит от того, где гоняются тесты. А так — достаточно, чтобы завёлся подходящий SDK.


                            1. qrKot
                              21.12.2018 16:34

                              Java/C#/OtherVM detected!

                              В смысле, для JVM, .NET Core и других VM-based окружений — норм, без вопросов, так и есть. Оно потому и VM называется, что в изолированной песочнице работает. Для не-VM окружений уже не факт. Docker-based решения не зря цветут и пахнут.


                              1. Free_ze
                                21.12.2018 16:59

                                Кросскомпиляторы, например)


                                1. qrKot
                                  21.12.2018 17:22

                                  А вы отважный…

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

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


                    1. qrKot
                      21.12.2018 14:28

                      Наличие рантайма — это зависит от конкретной технологии. Вполне реально рантайм таскать вместе с конкретным проектом.


                      1. Рантайм есть всегда.
                      2. Если вы носите рантайм + зависимости + приложение кучей, то какие у вас есть преимущества перед одним бинарем, кроме, разве что:
                      а) преимущества в нагрузке на файловую систему (в сторону бОльшей нагрузки: посчитайте нагрузку на дисковый io при копировании 1000 мелких файлов и копирования одного бинаря ровно такого же размера, как суммарно все эти 1000 файлов);
                      б) неоспоримого преимущества в размере бандла (в бОльшую сторону: при импорте библиотеки в проект в бандл она упадет вся, в статический бинарь — только используемая часть);
                      в) еще одной фичи размера бандла (опять же в бОльшую сторону: если вы тащите с собой там же рантайм (ВМ или транслятор), он падает в ваш бандл целиком, вместе со всей стандартной библиотекой в довесок).

                      Ну, т.е. я плавно подвожу вас к мысли, что Go неплохо так экономит дисковое пространство, относительно «конкурирующих предложений».

                      Если же вы все это не упаковали в артефакт, получите и распишитесь: вы не уверены, что это запустится на целевой машине.


                      1. Free_ze
                        21.12.2018 15:45

                        Рантайм есть всегда

                        Помимо стандартной библиотеки и продуктов жизнедеятельности компилятора, виртуальная машина в составе «рантайма» есть не всегда.

                        преимущества в нагрузке на файловую систему

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

                        там же рантайм (ВМ или транслятор), он падает в ваш бандл целиком

                        Ничто не мешает прихватить его с собой без инжекции в бандл.

                        вы не уверены, что это запустится на целевой машине

                        Учитывая нацеленность на сервера, этот страх не особо оправдан.


                        1. qrKot
                          21.12.2018 16:25

                          Помимо стандартной библиотеки и продуктов жизнедеятельности компилятора, виртуальная машина в составе «рантайма» есть не всегда.


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

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


                          Кхм, мы же в контексте микросервисов разговариваем? В нише Go?

                          Ну вот, значится, список вопросов:

                          1. Откуда эта маниакальная склонность к ручной работе? То вы копипастите сбандленную на машине разработчика сборку на прод-сервер, то вы «копируете руками» бандл со сборочного сервера на прод… Если ручного копипаста без CI/CD вам хватает, может, вам просто не нужны микросервисы? Может, просто масштаб не тот, чтобы с этим заморачиваться?

                          2. При доставке на прод-сервер мы имеем 2 значимые по io части: заливку по сети (что кладет нам пропускную способность сети), запись на диск (что кладет дисковый io). Мы помним, мы держим в памяти, что мы в условиях микросервисов, т.е. на целевой машине, кроме вас, еще хренова туча народу (или процессов/контейнеров), и все они пытаются что-то лить в сеть, что-то читать-писать на диск. Да, это может стать затыком.

                          3. Самое смешное, что решение первой половины проблемы вы сами и предложили — запихать в тарболл, т.е. доставлять одним большим файлом. Осталось понять, каким образом тарболл поможет нам решить вторую половину поблемы — дисковый io. Вы же не собираетесь прямо архив запускать? Т.е. вы его предлагаете слить по сети, а там распаковать. Каким образом это решит проблему «записать 100500 мелких файлов на диск»? Правильно, никак. Как писались, так и писаться будут.

                          4. Теперь вспоминаем, что общепризнанно считается самым узким местом серверных систем. Точно, дисковая подсистема. Чем реже вы к ней обращаетесь, тем лучше. С точки зрения стоимости ресурса диск, конечно, дешевле. С точки зрения производительности память окупается более эффективно. И вот тут мы вспоминаем, что нам нужно сначала распаковать полученный ранее (по вашей же идее) тарболл (что случается достаточно редко, в процессе деплоя/рестарта, т.е. действительно можно пренебречь до определенных разумных пределом), а потом еще и читать/исполнять то, что мы там на диск понаписали. И вот тут-то и оказывается, что при высокой (и конкуррентной) нагрузке на файловый io сервера читать 100500 мелких файлов — тоже то еще удовольствие. Дисковый кеш поначалу спасает, но он тоже не бесконечный (сильно не бесконечный), и поэтому постоянное чтение постепенно ставит всю систему раком. И вот тут вам приходит мысль: «Мы же офигенно решили проблему доставки по сети, упаковав в тарболл. Вот бы так же с диском, прямо этот тарболл бы и запускать!» И вот прямо в этот момент перед глазами у вас появляюсь я и с торжествующим видом говорю: «Ага! А я что говорил? Говорил же, бери Go! По сути, он именно это и делает — разгружает дисковый io в условиях жесткой конкуррентной нагрузки на диск. И это прекрасно же! А я говорил!»

                          В общем, надеюсь, суть понятна?)))

                          Ничто не мешает прихватить его с собой без инжекции в бандл.


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

                          Учитывая нацеленность на сервера, этот страх не особо оправдан.


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


                          1. Free_ze
                            21.12.2018 17:04

                            Откуда эта маниакальная склонность к ручной работе?

                            Это некогда было одним из основных аргументов здесь.

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

                            Один файл — это просто, а сотня (бандл с рантаймом) — это становится сильно сложно. Все относительно, но все же не настолько весомо.

                            Вы же не собираетесь прямо архив запускать?

                            Это как WAR'ы? Может и собираюсь.

                            А это уже «грабля», простите.

                            В смысле «костыль»?) Настолько же, насколько и статическая линковка для тех же целей, тем более докер.


                            1. qrKot
                              21.12.2018 20:27

                              Откуда эта маниакальная склонность к ручной работе?

                              Это некогда было одним из основных аргументов здесь.


                              Ну вот, собственно, времена и изменились. Тем более в контексте CI/CD и микросервисов.

                              Один файл — это просто, а сотня (бандл с рантаймом) — это становится сильно сложно. Все относительно, но все же не настолько весомо.


                              Сотня? Хм, вот ща заглянул в текущий проект. ls -f node_modules/* | wc -l Говорит 12956, и в этом вся нода. Теперь вспоминаем, что а) на ноде ворочают бэкенд, б) чтение одного файла по минимуму 2 io-операции, остальное зависит от размера файла и степени вложенности в иерархии файлов. Ну, как бы, нагрузка на файловую систему разнится не то в разы, и даже не на порядки. «Разлет» может достигать «пары тысяч раз».

                              Это «не настолько весомо» — ну, Г-дь вам судья.

                              Это как WAR'ы? Может и собираюсь.


                              Хм, и давно в ноде WAR'ы появились? В Java — видел. Оно, конечно, понятно, что каждая фишка Го в отдельно-стоящем виде встречается в более другом языке, ничего усраться-прорывного в Го нет. Однако аргументация вида «вот эта фича уже есть в языке X, а вот эта — в другом языке, а третья — в третьем». Как бы, для конструктива, определитесь, с чем мы сравниваем, будем рассуждать более детально.

                              А это уже «грабля», простите.

                              В смысле «костыль»?) Настолько же, насколько и статическая линковка для тех же целей, тем более докер.


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

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


                              1. gnomeby Автор
                                21.12.2018 20:32

                                Сотня? Хм, вот ща заглянул в текущий проект. ls -f node_modules/* | wc -l Говорит 12956, и в этом вся нода. Теперь вспоминаем, что а) на ноде ворочают бэкенд, б) чтение одного файла по минимуму 2 io-операции, остальное зависит от размера файла и степени вложенности в иерархии файлов. Ну, как бы, нагрузка на файловую систему разнится не то в разы, и даже не на порядки. «Разлет» может достигать «пары тысяч раз».
                                Справедливости ради отмечу, что всё будет закешировано ядром ОС. Но это не повод мириться с этим безобразием.


                                1. qrKot
                                  21.12.2018 20:39

                                  Еще один нюанс: кеш не бесконечный. Пока проект живет один на физической машине, он закешируется, без вопросов. А вот в условиях, когда таких «небольших проектов» на физическом хосте запущено, предположим, сотня…

                                  Ну и холодный старт никто не отменял. Короче:
                                  а) неочевидно все это;
                                  б) как бы то ни было — лютый писец все это)


                    1. Edison
                      21.12.2018 19:01

                      > У Go есть рантайм? Или вы про стандартную библиотеку?

                      Рантайм рантайм — сборщик мусора, планировщик goroutine, etc. Рантайм.
                      Все это будет в вашем одном бинарном файле.


            1. Eagle_NN
              21.12.2018 11:29

              «Неужели раньше нельзя было взять с собой зависимости, просто разместив их в подкаталоге?»

              Даже в таком случае возникают проблемы. Часто они связаны со странным прописыванием переменной PATH на разных машинах. И может оказаться, что тривиальная загрузка библиотеки LoadLibrary(«lib\library.dll») загрузит ее не из текущего каталога программы, а вообще из другой программы.


    1. anonymous
      21.12.2018 10:07

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


      1. severgun
        21.12.2018 11:27

        Собрать в apk не один файл будет? Нет?
        Собрать можно в deb rpm jar setup.exe и в кучу других пакетов для дистрибуции. Это не проблема и Go не решение. Так зачем этим козырять первым пунктом везде? Важнее ничего нет?


    1. Barafu_Albino_Cheetah
      21.12.2018 13:00

      Потому что они программисты, а не админы. Им это всё потом не поддерживать. Допустим, у нас на сервере все демоны написаны на инструментах типа "всё своё ношу с собой". Устанавливать очень удобно, не спорю. Меня вебсервер на go очень выручил однажды, когда я его смог запустить без рута и в очень ограниченной среде на минимизированном линуксе доисторических версий.
      Но представьте себе ситуацию: в LTS версии очень популярной libpelmen нашли опасную уязвимость. Нам, админам, требуется:


      1. Просканировать все бинарники в системе на тему наличия в них libpelmen
      2. В найденных случаях проверить, какая именно версия используется
      3. При обнаружении уязвимых бинарников, их нужно расколпаковать, переколпаковать и снова выколпаковать, подложив патченную библиотеку.
      4. Задеплоить полученную версию. Обычно это просто — подменить один файл. Но не всегда.
      5. Перезапустить и проверить, что всё работает.

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


      1. Посмотреть версию libpelmen в системе. 1 команда.
      2. Если она уязвимая, накатить апдейт. 1-2 команды.
      3. Получить список сервисов, которые от неё зависят, и перезагрузить их. 5 минут работы.
      4. Для особых параноиков, убедиться, что уязвимой версии не осталось в памяти. 5 минут работы.

      Так что нафиг такие удобства. Это для специальных случаев, читай: костылей и хаков. Будете поднимать торрент-трекер на утюге — пакуйте всё в один файл.


      1. qrKot
        21.12.2018 14:54

        Потому что они программисты, а не админы.


        Вы это сейчас написали потому, что вы админ, а не программист. Правда, ведь?

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


        Вот это вы как себе представляете? Часто вы «перепаковываете» бинарники?

        Какбэ, варианта-то 3:

        1. Приложение доступно в исходниках.

        — Пересобираем из исходников, предварительно обновив «багованную зависимость» (работа один хрен не админская, обратитесь к программисту)

        2. Приложение не доступно в исходниках (возможно, проприетарное), но доступен автор/правообладатель, т.е. у вас есть возможность постучаться за «поддержкой».

        — Стучимся к автору, просим пересобрать бинарь, отдать патченную версию.

        3. Приложение недоступно в исходниках, автор шлет лесом/недоступен/помер/хочет много денег.

        — Прощаемся с уязвимым софтом либо смиряемся с наличием уязвимого софта на нашем сервере.

        Остальное кажется мне достаточно лютой фантазией. Ну, либо путаницей между статическим бинарем и вещами вроде AppImage, Snap и FlatPak. Правда, не понимаю, каким образом что-то из этого вы собираетесь заюзать в качестве системного демона… Ну и, как бы, из всего этого при наличии уязвимости в системной библиотеке «пересобирать» лично вы будете только AppImage, и то, опять же, при отсутствии адекватного доступа к автору.

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


        Ребят, по большей части:
        а) пересобирать бинари — не ваша забота;
        б) именно с точки зрения простоты сборки к go меньше всего претензий (все сведется к паре команд), поверьте, это проще, чем C-шный бинарь пересобрать. Прямо в разы.


        1. Barafu_Albino_Cheetah
          22.12.2018 08:07

          Вся речь именно о том, что если везде использовать системы статической сборки, то все бинарники станут «навроде AppImage» и пересобирать их, если что — станет заботой админа.


          1. qrKot
            22.12.2018 20:08

            Смотрите, встречается 2 задачи: «хорошо работать в качестве части системы» и «хорошо работать на любой системе». Задачи не то, чтобы ортогональны, они противоположны. И усидеть на двух стульях нельзя.

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

            Вторая… Ну, ее много раз пытались решить, и решение даже примерно найдено: все, что нужно, носить с собой. Выглядит не идеально, но остальные варианты по ряду критериев просто хуже. И вот эта модель — она не для сервисов, демонов, системных библиотек и всего вот этого, она для приложений. Для них AppImage, Flatpak, Snap и все вот это.

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

            А про ручную пересборку офисов вместо обновления версии от официального производителя руками админа… Ну, это уже как-то на сказку похоже.

            Если же вас беспокоит место на диске. Собственно, когда размер общей кодовой базы дорастает до определенного значимого размера, вполне нормально и «монолитные» решения пилятся на куски. Go вполне позволяет собирать динамические библиотеки, Snap и FlatPak вполне имеют механизм слоев… В случае необходимости патча core-библиотеки, вы просто пересобираете базовый слой. В Snap еще и с плагинами мутились, не уверен, что у них получилось, но оверрайд куска слоя вполне себе пилили. Вероятно, к тому времени, когда это станет критично, допилят.

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


  1. tyderh
    20.12.2018 13:22

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

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

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

    ru.wikipedia.org/wiki/%D0%91%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0_%D0%BA_%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8


    1. gnomeby Автор
      20.12.2018 14:02

      Я не согласен с вашим категорическим тоном, но согласен с замечанием. Утащил ваше замечание в статью.


  1. esata
    20.12.2018 13:23

    Вопрос к автору статьи — чем ваша библиотека для тестирования отличается от того же testify например?


    1. gnomeby Автор
      20.12.2018 13:33

      Она нам как-то не попадалась при поисках, мы попробуем и скажем. Пока добавил в конец статьи.


      1. tyderh
        20.12.2018 13:43

        Тогда уж сразу и ginkgo добавляйте


                ?(result).Should(Equal(1))


        1. gnomeby Автор
          20.12.2018 13:57

          Тоже выглядит неплохо, добавил.


          1. negasus
            21.12.2018 08:50

            Go Convey
            Вот вам в копилку


            1. gnomeby Автор
              21.12.2018 08:58

              Идея интересная, но нет, ибо плохо поддерживается синтаксисом языка. Хотя в статью добавлю.

              func TestSpec(t *testing.T) {
              
              	// Only pass t into top-level Convey calls
              	Convey("Given some integer with a starting value", t, func() {
              		x := 1
              
              		Convey("When the integer is incremented", func() {
              			x++
              
              			Convey("The value should be greater by one", func() {
              				So(x, ShouldEqual, 2)
              			})
              		})
              	})
              }


              1. powerman
                21.12.2018 09:07

                GoConvey лично мне в первую очередь интересен удобным веб-UI и автоматическим прогоном тестов при изменении файлов в проекте. Довольно удобно держать в браузере открытую вкладку на http://127.0.0.1: порт/ и видеть всплывающее системное уведомление о (не) прохождении тестов каждый раз когда в vim сохраняется файл. Ну и плюс там удобно сразу смотреть покрытие и diff — причём всё это доступно и для обычных тестов, без необходимости писать тесты через Convey().


              1. Tyranron
                21.12.2018 10:30
                +1

                Мне лично с GoConvey понравилось работать больше всего. И дело далеко не в синтаксисе. Да, синтаксис, не такой приятный как у того же gingko, и привычные BDD идиомы given/before/after и assert'ы вывернуты немного иначе, нежели привыкаешь в других языках (в этом плане gingko гораздо более похож на стандартные реализации). Но к синтаксису, на самом деле, просто привыкаешь со временем и достаточно быстро.


                Основная киллер-фича GoConvey — это модель исполнения его каскадов, и она невероятно удобна для жирнющих тестов, где Вам нужно инициализировать кучу моков/стабов а потом прогонять 100500 сценариев с ними. ginkgo же в этом плане заставляет инициализацию выносить в before-блоки, а так как эти блоки являются просто отдельными Go'шными функциями, то сразу рисуется проблема передачи наинициализированного дела в describe- и after-блоки, хоть через внешние переменные, хоть как. В GoConvey же, ты просто об этом даже не думаешь, всё просто работает без всяких чертыханий, ибо определения из внешних каскадов автоматически прокидываются во внутренние по замыканию, а параллельные сценарии выполнения автоматически разделяются на отдельные контексты. Это очень сильно уменьшает шум и бойлерплейт, оставляя в коде теста для чтения строго то, что нужно. Написание таких тестов тоже становится очень приятным делом.


                Ну а если соединить GoConvey и httpexpect, то это прямо благодать какая-то для написания E2E-тестов для всяких API'шек.


  1. helgihabr
    20.12.2018 14:20
    +1

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

    Авторы, частенько, хитрят и притягивают за уши «непривычности», выдавая их за недостатки:
    не нравятся запятые за последним элементом? пишите так:

    a := []string{"q"}

    и все скомпилируется.

    Когда вы пишите о вариантах объявления переменной, так пишите тогда так:
    var i uint = 3
    j := 6

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

    Если вам кажется небезопасным код:
    var x map[string]int
    x["key"] = 10

    так пишите сразу более безопасно:
    x := make(map[string]int)
    x["key"] = 10

    И почему вы не упомянули sync.Map, а сразу так отрезали что «их нет»?

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


    1. Stronix
      20.12.2018 15:39

      x := make(map[string]int)
      x[«key»] = 10

      Или
      var x = map[string]int{}
      

      или
      x := map[string]int{}
      


    1. gnomeby Автор
      20.12.2018 15:50

      Я с вами согласен, отмечу только что:

      не нравятся запятые за последним элементом? пишите так:
      a := []string{«q»}
      Естественно подразумевается списки достаточно длинные по вертикали, не из одного элемента.

      Если вам кажется небезопасным код:
      var x map[string]int
      x[«key»] = 10

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

      Вот выделение памяти может потеряться.

      И почему вы не упомянули sync.Map, а сразу так отрезали что «их нет»?
      Прошло мимо меня, сейчас добавлю.


      1. powerman
        21.12.2018 06:53

        В реальном проекте:

        Не надо так, пожалейте реальный проект, им потом будут реальные люди пользоваться. Лучше почитайте https://peter.bourgon.org/blog/2017/06/09/theory-of-modern-go.html


      1. qrKot
        21.12.2018 09:28

        В реальном проекте:
        * объявление переменной может быть при запуске программы и глобально для некоторого файла,
        * выделение для неё памяти — в одной из веток инициализации,
        * а присвоение значения на каком-то этапе работы после


        Мне кажется, это какой-то плохой проект… Неправильный…


    1. nexmean
      21.12.2018 08:30

      И почему вы не упомянули sync.Map, а сразу так отрезали что «их нет»?

      У sync.Map есть один огромный минус и имя ему пустой интерфейс.


    1. JekaMas
      21.12.2018 20:25

      Или если все в одном месте, то уж сразу
      x := map[string]int{"key": 10}


  1. Stronix
    20.12.2018 15:10

    В этом невинном примере мы «забыли» выделить себе память и получили ошибку времени выполнения.

    >Map types are reference types, like pointers or slices
    blog.golang.org/go-maps-in-action
    Указать автоматически инициализировался nil, что не так?


    1. gnomeby Автор
      20.12.2018 15:51

      Ответил выше.


  1. Scratch
    20.12.2018 15:20
    +1

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


    1. gnomeby Автор
      20.12.2018 15:52

      Отсеять заранее несогласных тоже полезно, чтобы хипстеры не набежали.


      1. TheShock
        21.12.2018 16:09
        +1

        Как они могут набежать, если они уже все там?


  1. inew-iborn
    20.12.2018 16:56
    +1

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


    1. gnomeby Автор
      20.12.2018 17:00

      В 1.11 вроде эту проблему решили.


      1. powerman
        21.12.2018 07:01

        Ну, не сразу. Пока не подправят кучу утилит работать вне GOPATH всё ещё неудобно. Особенно мешает отсутствие gorename, я уже слишком привык к удобному рефакторингу прямо внутри vim. Так что сейчас приходится выкручиваться следующим образом:


        • проект пока остаётся в GOPATH
        • vim запускается с не установленной GO111MODULE
        • команды go test/run/build/mod/… запускаются в отдельном терминале с export GO111MODULE=on

        При этом подходе внутри vim корректно работают все вспомогательные утилиты полезные для навигации по коду, рефакторинга, документации, etc. и при этом проект получает все преимущества кеширования тестов/сборки плюс повторяемость сборки на CI и у других разработчиков благодаря использованию модулей.


    1. zelenin
      20.12.2018 18:32

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

      вендоринг завезли в го 3 года назад.


      меня удивляет необходимость переопределять переменную GOPATH

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


    1. inew-iborn
      20.12.2018 19:15

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

      по поводу

      Нужно всего лишь принять рекомендуемую систему структуры воркспейса.


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

      Но так как ситуация изменилась, вопрос снимается. Пойду обновляться)


      1. zelenin
        20.12.2018 22:13

        Извиняюсь, мое упущение, у меня установлен версия 1.10.1.

        мой коммент валиден для всех версий за последние два года


        модули банальны

        я не о модулях, хотя модули очевидно упрощают


    1. qrKot
      21.12.2018 09:30

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


      Собственно, именно так оно и работает. Сначала в vendor текущего проекта ищет, потом в «юзерском» GOPATH, а уже затем в стандартной библиотеке.


  1. 0xd34df00d
    20.12.2018 17:15

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

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


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

    Ну это в 2018-м году, конечно, успех.


    Это сразу решает проблему с отсутствием деструкторов и контекстов (например питоновский with).

    А вот этот вот defer можно как-то передать наружу вызывающему коду? Ну, как можно мувнуть unique_ptr или какой-нибудь свой RAII.


    1. gnomeby Автор
      20.12.2018 17:38

      как мне одним лёгким и изящным движением руки преобразовать мою последовательную программу в параллельную, где список бы обрабатывался чанками по N элементов?
      Троллинг защитан. Конечно же без введения специальных типов или специального отношения к типам это не сделать лёгким движением руки. Ну так и на Хаскеле нет работы.

      Ну это в 2018-м году, конечно, успех.
      Успех для мигрирующих с динамических языков с GIL или с языков с не очень безопасной многопоточностью.

      А вот этот вот defer можно как-то передать наружу вызывающему коду?
      Нет, тут я подправлю статью. Не буду столь категоричен.


      1. 0xd34df00d
        20.12.2018 17:44

        Троллинг защитан.

        Какой троллинг? Это относительно регулярно возникающая задача. В очередной раз она возникла вот прямо вчера.


        Ну так и на Хаскеле нет работы.

        Пойду скажу об этом своему работодателю. Хотя я тут и на плюсах тоже пишу, в принципе.


        ЧСХ, кстати, на хаскеле и подобных куда больше (интересной для меня) работы, чем на Go.


        1. gnomeby Автор
          20.12.2018 17:49

          ЧСХ, кстати, на хаскеле и подобных куда больше (интересной для меня) работы, чем на Go.
          Да, её даже на Си++ куда больше. Go — нишевая вещь всё-таки.

          Если не секрет: чем на Хаскеле занимаетесь?


          1. 0xd34df00d
            20.12.2018 17:55

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


            В свободное время пилю байндинги к clang и какое-то подобие статического анализа. Но там совсем ерундово всё.


    1. powerman
      21.12.2018 07:07

      А вот этот вот defer можно как-то передать наружу вызывающему коду?

      Можно, но без синтаксического сахара:


      func main() {
          ctx, cancel := context.WithTimeout(context.Background(), time.Second)
          defer cancel()   // <- примерно таким образом
          // ...
      }


      1. 0xd34df00d
        21.12.2018 16:57

        То есть, вызывающий код теперь ответственен за то, чтобы этот defer правильно вызвать?


        1. gnomeby Автор
          21.12.2018 17:13

          Да, так делать не надо, но возможность есть.


          1. 0xd34df00d
            21.12.2018 17:17

            Ну так это не замена деструкторам и RAII ну вот прямо совсем вообще.


            1. powerman
              21.12.2018 18:01

              Деструкторы в Go тоже есть: runtime.SetFinalizer. Но на практике они почти не используются.


              1. 0xd34df00d
                21.12.2018 18:18

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


                1. gnomeby Автор
                  21.12.2018 18:22

                  Да, в Go пока есть как бы всё, но половина из этого — некоторый компромисс и работает или не так хорошо или не всегда по сравнению с другими языками.


              1. JekaMas
                21.12.2018 20:34

                Давольно активно пользуется там, где cgo. Например imagemagic https://github.com/gographics/imagick/blob/master/imagick/magick_wand.go
                Чтобы не бегать по гошному коду и не искать, когда пора высвободить C ресурсы.


                1. 0xd34df00d
                  21.12.2018 20:41
                  +1

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


                  1. JekaMas
                    21.12.2018 20:47

                    Пример действительно отличный, поскольку бизнес-фичу запустили быстро, поправив этот биндинг к C коду.
                    Да и, расскажите, чем добавление интерфейса destroyable + финализатора, дергающего по нему, плохо? Ресурсы управляются, C не течет, клиент о ресурсах не думает, а задача — решена и обрабатывала картинки товаров Lazada года полтора, пока не поменяли на другую библиотеку.


                    1. 0xd34df00d
                      21.12.2018 22:54

                      Да не, просто все эти ручные IncreaseCount, DecreaseCount, KeepAlive — это довольно забавно. Рефакторить такие вещи очень клёво, наверное. Особенно код двигать туда-сюда.


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


                      For example, if p points to a struct that contains a file descriptor d, and p has a finalizer that closes that file descriptor, and if the last use of p in a function is a call to syscall.Write(p.d, buf, size), then p may be unreachable as soon as the program enters syscall.Write. The finalizer may run at that moment, closing p.d, causing syscall.Write to fail because it is writing to a closed file descriptor (or, worse, to an entirely different file descriptor opened by a different goroutine). To avoid this problem, call runtime.KeepAlive(p) after the call to syscall.Write.

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


                      На самом деле это всё даже хорошо. Заставляет задуматься о жизни. Читаешь ты какие-то статьи там высокопарные, заморачиваешься, что в твой любимый хаскель ещё линейные типы не завезли, а когда завезут — они будут не совсем такими. Почитываешь библиотечки всякие, где линейные типы прикостыливают сбоку как-то, а тут люди не парятся, прям руками хреначат free, KeepAlive, вот это всё, и в прод. И вот сидишь ты, читаешь это и думаешь: а, может, зря заморачиваешься? Зря это всё?


                      1. JekaMas
                        22.12.2018 18:07
                        +1

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


                      1. qrKot
                        22.12.2018 20:10
                        +1

                        Вы правы, может и зря…

                        Как всегда, классика: На арену приглашаются Шашечки вёрсус Ехать.


                        1. 0xd34df00d
                          23.12.2018 17:58

                          Ехать и на С можно, если я коннотации правильно распарсил.


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


          1. powerman
            21.12.2018 17:53

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


    1. qrKot
      21.12.2018 09:38

      Заводить по гринтреду на каждый элемент дорого, так что как мне одним лёгким и изящным движением руки преобразовать мою последовательную программу в параллельную, где список бы обрабатывался чанками по N элементов?


      Собственно, либо побить по чанкам ручками (к слову, достаточно недорого в плане производительности, ибо слайсы) и отдать каждый чанк обработчику.
      Либо почитать про sync.WaitGroup, если, например, хочется иметь не более N одновременно работающих обработчиков (ладно, уговорил, N мало, лучше пусть будет K)


      1. 0xd34df00d
        21.12.2018 16:58

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

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


        Либо почитать про sync.WaitGroup, если, например, хочется иметь не более N одновременно работающих обработчиков (ладно, уговорил, N мало, лучше пусть будет K)

        Очевидно, что это не то же самое, и что оверхед от того, что у вас в N раз больше кусков работы, когда обработка каждого элемента очень дешева (просто элементов очень много) будет весьма заметен.


        1. powerman
          21.12.2018 18:10

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

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


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


          1. 0xd34df00d
            21.12.2018 18:19

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

            Только в том же хаскеле это можно было сделать ещё лет 10 назад без всяких проблем и с куда большими гарантиями.


            Про эрланг я тоже что-то такое слышал, но я не фанат того языка, так что про него знаю меньше.


            1. powerman
              21.12.2018 18:22

              Конечно. В Limbo я этим пользовался лет 15 назад, а вышел Limbo вообще в 90-х, если не путаю. Но широким массам это стало доступно только когда Go стал достаточно популярен.


              1. qrKot
                21.12.2018 20:01

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


                1. powerman
                  21.12.2018 22:53

                  Limbo сильно проще, за счёт отсутствия POSIX вокруг. Но по производительности Go сильно обогнал Limbo, сил в Go вложили намного больше.


        1. JekaMas
          21.12.2018 20:39

          Знаете, а вы правы! Такую штуку с чанками надо добавить в го, но, думаю, на уровне библиотеки, например awg https://github.com/lazada/awg


          1. 0xd34df00d
            21.12.2018 20:46

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

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


            1. JekaMas
              21.12.2018 20:54

              А зачем? Да, согласен, удобно было бы иметь пакет, который принимает массив, по какому числе объектов разбивать или на сколько кусков разбивать, функцию обработки и все. Зачем решать все множество всех возможных задач? Да, го отвратителен, когда хочется общего решения. Ужасен.
              Но он прекрасен, когда занимаешься инженерными задачами, но не исследовательскими, и делаешь бизнес штуки.
              Может они могли бы быть в чем-то лучше, но… Это и есть главная инженерная задача — искать подходящий компромисс, решающий задачу.


              1. 0xd34df00d
                21.12.2018 22:56

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

                Композабельные стратегии работают немножко не так.


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

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


                1. JekaMas
                  21.12.2018 23:13
                  +1

                  Не получается предусмотреть, да.
                  Golang, на мой взгляд, это история про достаточно хороший инструменты для большинства задач, с которыми можно столкнуться разработчику, работающему на определенный бизнес: Highload, но не realtime; хорошее медианное значение быстродействия, но отвратительные крайние; легко ему обучать, но много однотипного кода; есть какая-то безопасность, но не везде, а только для частых случаев; полная обратная совместимость годами, но мало нововаедений… И так далее. Это инструмент под свои задачи. Немного сьранным будет ожидать от него чего-то иного.


                1. qrKot
                  22.12.2018 20:22

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


                  Вот именно из этого при проектировании Go и исходили. Предусмотреть все варианты на этапе проектирования практически нереально, поэтому язык и предполагает отказ от обобщенного-first подхода. Посмотрите на Java или C++. «Канонический» подход — родить абстракцию, которая удовлетворяет определенным критериям, выделить обобщенные куски реализаций, реализовать эти обобщенные куски, написать конкретную реализацию и уже на этом этапе словить по лбу граблей «в абстракции чего-то не предусмотрели» и уйти в рекурсию, начиная с первого пункта.

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

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


  1. VanquisherWinbringer
    20.12.2018 20:49

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


    1. inew-iborn
      20.12.2018 21:03

      тут наверное не проблема ЯП, а проблема в бизнесе и Ваших личных потребностях, хотя, и это назвать проблемой сложно.

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


      1. VanquisherWinbringer
        21.12.2018 14:50

        Вот у PHP тоже низкий порог входа. Вспомнил шутку про PHP и немного дополняю — «На паралимпиаду по программированию приглашаются программисты на 1С, PHP и Go»


    1. zelenin
      21.12.2018 15:46

      самый ужасный недостаток Go — низкий порог входа

      это особенность, а не недостаток


      потому что я хочу работать с людьми способными освоить высокий порог входа

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


    1. qrKot
      21.12.2018 16:37

      Какой очаровательный юношеский максимализм!

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


  1. dae-fromru
    20.12.2018 22:39

    Что по-поводу пары Java/C#? Go ей ни разу не конкурент, по крайней мере пока он молод (речь про версию Go 1.11).


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


    1. gnomeby Автор
      20.12.2018 22:45

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


      1. VanquisherWinbringer
        20.12.2018 23:39

        1. gnomeby Автор
          21.12.2018 08:53
          +1

          Я сам с удовольствием проснусь в том мире, в котором Rust станет нормой. Но пока он ещё более узко специализирован, чем Go. На Go хотя бы работа есть.


          1. VanquisherWinbringer
            21.12.2018 15:13

            Ну раз зашла речь про работу то вот в C# для мобильных приложений есть Xamarin, для кросс платформенного GUI есть Avalonia для распределенный вычислений с использованием системы акторов есть Akke.net и MS Orleans для игр Unity для Сайтов и микросервисов есть ASP.NET Core (котором можно отключить MVC и все лишнее если вам нужен совсем мелкий микросервис). В стандартной библиотеки есть HttpListener который я сам использовал для написания сетевой части высоко нагруженного микросервиса с нуля. Для работы с БД есть ORM EntityFramework c LINQ. У С# производительность и надежность на уровне Go. По количеству библиотек/тулзов/вакансий превосходит его. Что такого может дать Go чего нет в C#?


            1. gnomeby Автор
              21.12.2018 15:28

              Кажется в начале статьи написано, что Go связке Java/C# не конкурент.


            1. qrKot
              21.12.2018 15:50

              Сайтов и микросервисов есть ASP.NET Core


              Для микросервисов:
              — размер сборки;
              — отсутствие необходимости таскать рантайм за собой;
              — простая до примитивизма и предсказуемая модель асинхронности;
              — порог вхождения;
              — отсутствие привязки к Майкрософту (да, не все любят Майков);
              — бОльшую стабильность API, обратную совместимость;
              — возраст использования в высоконагруженном продакшене.

              В остальных областях Go с C# не конкурирует.


              1. VanquisherWinbringer
                21.12.2018 16:54
                +1

                — отсутствие привязки к Майкрософту (да, не все любят Майков);

                Прекратите нести бред. Может кто — то терпеть не может Google и поэтому не будет использовать Go который Googe продвигает? Ладно, думайте что хотите. Мне все равно. Вместо весомых аргументов какая то мелкая чушь. Глупо с таким как вы спорить и что — то вам доказывать. Вот поэтому я не люблю языки с «низким порогом входа». Там таких как вы много.


                1. qrKot
                  21.12.2018 20:28
                  -1

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

                  кто — то терпеть не может Google и поэтому не будет использовать Go который Googe продвигает?


                  Именно! И вот когда вы спросите, какие преимущества есть у C# над Go, одним из пунктов в списке будет «Отсутствие зависимости от Google (Да, Google не все любят)». Это проблема?


                  1. 23rd
                    22.12.2018 15:04
                    -1

                    Можно подробнее про зависимости от Google?


                    1. qrKot
                      22.12.2018 20:27

                      Подробнее:

                      У Go есть преимущество (для кого-то значимое) над C#, заключающееся в том, что Go не зависит от корпорации Microsoft.

                      В свою очередь у C# есть преимущество над Go, заключающееся в том, что C# не зависит от компании Google, которое для кого-то может стать решающим.

                      При этом у обоих языков есть преимущество над Java — они не аффилированы с Oracle.

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

                      Не вижу противоречий, собственно.


              1. kasthack_phoenix
                21.12.2018 22:52
                +1

                — отсутствие необходимости таскать рантайм за собой;

                • CoreRT
                • Mono умел бандлиться ещё в 2010

                — бОльшую стабильность API, обратную совместимость;

                .net core реализует большую часть .net fx, а тому скоро третий десяток лет пойдёт.


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

                Предыдущий пункт — на оригинальном дотнете десятилетиями пишут.


                1. qrKot
                  22.12.2018 20:46

                  — отсутствие необходимости таскать рантайм за собой;

                  CoreRT


                  С июня 2018… Отстали на 6 лет.

                  Mono умел бандлиться ещё в 2010


                  Собственно, 2 проблемы:

                  — Mono не полностью совместим с NET Core
                  — В критичных для бизнеса проектах не любят «альтернативные реализации фреймворков от сторонних разработчиков».

                  .net core реализует большую часть .net fx, а тому скоро третий десяток лет пойдёт.


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

                  тому скоро третий десяток лет пойдёт.


                  А .NET Core (и .NET Standard), еще и одного десятка нет. Цифра, вроде как, ближе к «2 года с момента релиза»… И уже 3-ю версию пилят…

                  К тому же .NET Framework 3.2, 3.4, 3.5, 4.6 и т.д. — несовместимы между собой. На основании чего ждать иного поведения от .NET Core, так ни разу и не вышедшего в LTS. Первый же, вроде, похоронили уже? А ему 2 года, на минуточку…

                  на оригинальном дотнете десятилетиями пишут


                  Вы невнимательно читали заголовок списка. Он для «микросервисов», где Go уделывает .NET-платформу, как Г-дь черепаху. На «оригинальном дотнете» а) не пишут десятилетиями контейнеризованные приложения, б) в принципе не пишут контейнеризованные приложения.


          1. JekaMas
            22.12.2018 18:15

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


            1. gnomeby Автор
              22.12.2018 18:17

              Ну как бы потому, что безопасный язык лучше не безопасного. Сколько можно уже стек переполнять или обращаться к null ?!


              1. JekaMas
                22.12.2018 18:29

                Ну тогда действительно haskell.


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


                1. gnomeby Автор
                  22.12.2018 18:37

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


                  1. JekaMas
                    22.12.2018 18:42

                    Скорость запуска продукта? Цена поддержки? Они равны с go?


                    1. gnomeby Автор
                      22.12.2018 18:48

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


                      1. JekaMas
                        22.12.2018 19:02

                        Понятно, мне стало понятнее ваше мнение.


        1. Gizmich
          21.12.2018 09:20

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


          1. gnomeby Автор
            21.12.2018 09:36

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

            Хотя конечно Go сравним по перформансу с Java и частично заходит на поле Java, поэтому их приходится сравнивать.


            1. Gizmich
              21.12.2018 11:09

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


              1. gnomeby Автор
                21.12.2018 11:14

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


          1. 0xd34df00d
            21.12.2018 19:31

            А с растом можно сравнить скажем по сложности чтения кода и порогу вхождения.

            Почему, если, как вы написали, Go ему не альтернатива?


      1. dae-fromru
        21.12.2018 00:16

        У go есть определенная область применения, в которой он великолепен. Те же, кто пытаются натянуть сову на глобус представить go в качестве универсального языка, либо не понимают его, либо лукавят. Гвозди и микроскопом можно забивать, только результат печален будет.
        Все это к тому, что у вас изначально предвзятое мнение в сторону go, это видно из статьи. Язык это лишь инструмент, и не стоит поливать его, не зная его ниши. Эдак можно про любой язык наворотить.


        1. gnomeby Автор
          21.12.2018 08:35

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


          1. dae-fromru
            21.12.2018 12:32

            Я просто предупреждают тех людей, для которых выразительность языка очень важна.

            Если б я увидел это в разделе «для кого эта статья», не сказал бы ни слова :)

            P.S.
            я читаю статьи целиком


            1. gnomeby Автор
              21.12.2018 12:35

              Давайте добавим.


      1. JekaMas
        22.12.2018 18:13

        Насколько я знаю, Али сейчас эксперементирует с переводом части сервисов с java на go. Так что да, конкуренты в определенных нишах.


  1. Dudka
    20.12.2018 23:20

    Хочу добавить, что в Golang из коробки отсутствует полноценная поддержка PCRE.
    Поддерживаются лишь ограниченные RE2.


    1. powerman
      21.12.2018 05:38

      RE2 — это не "ограниченное" подмножество PCRE, это очень правильные регулярки, всегда выполняющиеся за предсказуемое время, что полностью окупает отсутствие нескольких фич из PCRE.


      1. Dudka
        21.12.2018 10:15

        Спасибо за разъяснения.
        Ваша фраза «полностью окупает отсутствие нескольких фич» является весьма субъективной. Для меня, например, не окупает.


        1. powerman
          21.12.2018 17:00

          Она кажется субъективной ровно до того момента, когда незначительное изменение во входных данных приведёт к тому, что микросервис ранее обрабатывавший запрос за долю секунды потратит пару дней на обработку этого запроса. Фичастые регулярки в интерактивных приложениях вроде ack/grep/sed/vim — это действительно очень удобно, но вот в микросервисах критично важно избегать такой неопределённости со скоростью выполнения регулярок. В некоторой степени для PCRE проблема решалась чтением Фридла, но, всё-равно, не всегда удаётся на ревью проконтролировать корректность написания всех регулярок, иногда попадаются не очень очевидные случаи… в общем, переход на RE2 и его гарантии реально позволил выдохнуть с облегчением.


  1. kuftachev
    21.12.2018 00:33

    Человек, которой сам заявляет, что пишет на языке, на котором нужно, чтобы просто создать класс написать конструктор копий, копирующий конструктор и ещё кучу всякой хрени, а во втором языке нужно при вызове каждого метода класса передавать ссылку(или указатель, не помню что такое self в Python), просто не может говорить о странностях других языков.


    Ничего из описанного не является проблемой.


    1. 0xd34df00d
      21.12.2018 04:50

      чтобы просто создать класс написать конструктор копий, копирующий конструктор и ещё кучу всякой хрени

      Среди этой хрени, наверное, конструктор копирования, конструктор копировальный, копий конструктор и конструктор копирующий?


      А если чуть серьёзнее, писать их явно нужно только во владеющих ресурсами классах. Но на то они и владеющие ресурсами.


    1. gnomeby Автор
      21.12.2018 08:41

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

      O RLY?
      class A:
         def m(self):
           pass
       
       a = A()
       a.m()  # Где тут нужно передавать ссылку?
      


  1. powerman
    21.12.2018 05:36

    Ловите ещё одну библиотеку для удобных тестов: https://godoc.org/github.com/powerman/check


    1. gnomeby Автор
      21.12.2018 08:47

      Вот это действительно хорошо, сам хотел также сделать, но не вкурил как. Добавил.


  1. powerman
    21.12.2018 07:31

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


    Но поскольку в Go пока (пока!) нет шаблонов, то очень часто они эмулируются через интерфейсы

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


    В языке сущность интерфейса используется, чтобы заткнуть «бедность» синтаксиса языка.

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


    P.S. Кстати говоря, я замечал, что питонисты в Go часто лепят interface{} и далее type switch или type assertion там, где этого нафиг не нужно было делать — это не проблема Go, это проблема мышления разработчика в стиле питона. Примерно как джависты часто по привычке создают много лишних слоёв практически без полезного кода. На Go надо писать в стиле Go, и эти проблемы уйдут.


    1. gnomeby Автор
      21.12.2018 09:29

      Вы забыли упомянуть ещё как минимум один не очевидный момент: nil конкретного типа в интерфейсе.
      Я не забыл, а не знал, просто не сталкивался.
      А в реальном коде для шаблонов обычно используется кодогенерация.
      Можете привести пример кодогенерации? Может нам стоит в проекте заюзать.
      либо это просто неудачный словесный оборот при попытке сказать что иногда для обхода ограничений языка используют конкретно interface{}
      Да это неудачный словесный оборот, подправил.
      Что касается интерфейсов, то лично для себя решил, что чем их меньше в коде — тем лучше. Про необходимость избегания type switch согласен.


      1. qrKot
        21.12.2018 10:03
        +1

        Что касается интерфейсов, то лично для себя решил, что чем их меньше в коде — тем лучше.


        Лучше переформулируйте. Чем меньше в коде пустых интерфейсов, тем лучше — тут можно сразу соглашаться.

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

        В языке сущность интерфейса используется, чтобы заткнуть «бедность» синтаксиса языка.


        Вот тут совсем неправильно, имхо. Нет никакой «бедности синтаксиса». Есть «намеренно примитивная система типов» — вот тут да, это факт, и никто с этим не спорит.

        Посмотрите в Java/C#, как и для чего используются интерфейсы там. Вы проектируете интерфейс нужной вам сущности, долго его планируете, вылизываете, приходите к какому-то решению, а затем уже «отливаете в бронзе», т.е. пишете конкретные реализации интерфейса, явно указывая, что, мол, «данный класс — реализация интерфейса X».

        Сама «фича» с точки зрения работы с интерфейсами в Go — это отсутствие явного указания того, что структура реализует конкретный интерфейс. Интерфейс-матчинг в Go — с «утиной типизацией».

        А делается это для того, чтобы НЕ проектировать интерфейс заранее. И определять его рекомендуется не в пакете с реализации, а прямо рядом с ресивером/потребителем. Т.е. вы не проектируете интерфейс заранее, вы сразу пишете реализацию. И пишете потребителя, работающего конкретно с этой уже существующей реализацией.

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

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

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


        1. gnomeby Автор
          21.12.2018 10:12

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

          Нет никакой «бедности синтаксиса». Есть «намеренно примитивная система типов» — вот тут да, это факт, и никто с этим не спорит.

          Я же писал, я из питона. У меня в питоне есть декораторы, in, контекстный менеджер, __str__ и генераторы. Благодаря чему и веб код выглядит компактнее и ORM не нуждается в кусках SQL для затыкания своей ущербности.


          1. qrKot
            21.12.2018 11:20

            У меня в питоне есть декораторы, in, контекстный менеджер, __str__ и генераторы.


            Собственно, вкусовщина, по большей части. Ну и более детально (я не спец в Python'е, могу где-то ошибаться. если что, поправьте):

            __str__ — такая штука, которая определяет строковое представление объекта для методов print, str, format и т.д.? Оно?

            Вот тут я вас сейчас порадую, в Go можно так же. Есть такая штука — интерфейс Stringer. Другими словами, вам просто нужно для структуры описать метод `String() string` — и вы получите ровно то же самое.

            Разница тупо в реализации — в Пайтоне вы перегружаете метод, в Go — реализовываете интерфейс.

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

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

            Контекстный менеджер — фактически функция-враппер. Имхо, перекрывается полностью умелками defer'а и наличием замыканий в Go.

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

            Благодаря чему и веб код выглядит компактнее


            Вот этого не понял. Ни разу не видел в веб-коде пайтона… Там же, вроде, `html + js + css`-only?

            ORM не нуждается в кусках SQL для затыкания своей ущербности


            Ущербность ORM слаба связана с языком, имхо. Тем более, что ORM — объектная реляционная модель. Странно требовать от не-ООП языка аналогичного с ООП-языком удобства работы с объектной моделью. В Go принято, по большей части, не использовать ORM.


            1. gnomeby Автор
              21.12.2018 11:28

              Вот этого не понял. Ни разу не видел в веб-коде пайтона… Там же, вроде, `html + js + css`-only?
              Имеется код бекэенда веб приложений.
              В Go принято, по большей части, не использовать ORM.
              Скорее всего вопрос в том, что пока все ORM ущербны. Но если ваш способ `String() string` работает хорошо, то видимо хороший ORM не за горами.


              1. qrKot
                21.12.2018 11:36

                Имеется код бекэенда веб приложений.


                Ну тут мы в субьективизм впадаем уже. Мне лично, вероятно, в силу привычки, гораздо проще читать Go-шный код. А от Пайтоновских отступов начинает кровь из глаз течь, и голова болит… Субъективизм как есть: «к чему больше привык».

                Скорее всего вопрос в том, что пока все ORM ущербны.


                ORM'ы сами по себе — достаточно спорное решение. И это безотносительно языка. Тем более тянуть ООП-шную ORM-парадигму в не-ООПшный язык — затея еще более сомнительная (это уже относительно языка). Опять же — привычка. Вы привыкли, что ORM есть, а я их не люблю, и неплохо справляюсь без них.

                Но если ваш способ `String() string` работает хорошо, то видимо хороший ORM не за горами.


                Не получится, Go — строго статически типизированный. Тупо String не поможет, смиритесь. Не будет на Go вменяемого ORM'а. Да, собственно, и нужен ли он?


                1. gnomeby Автор
                  21.12.2018 11:42

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


                  1. qrKot
                    21.12.2018 12:24

                    ORM мне не нужен так, как нужен многим молодым разработчикам, потому что они не знаю SQL.


                    Ну, как бы, может, молодым разработчикам подучить SQL, что ли… Полезный навык же!

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


                    Может, простенького query-builder'а хватит, например, на такие случаи? Нафига целый ORM тащить?


                    1. gnomeby Автор
                      21.12.2018 12:27

                      Может, простенького query-builder'а хватит, например, на такие случаи? Нафига целый ORM тащить?
                      Может. Пример покажете?


                      1. qrKot
                        21.12.2018 12:41

                        Честно говоря, конкретно с такими случаями не заморачивался. Навскидку могу squirrel разве что предложить.
                        github.com/Masterminds/squirrel

                        Ну и в принципе, вот сюда загляните, тут достаточно много вкусного.


                  1. 0xd34df00d
                    21.12.2018 20:31

                    ORM — очень нужен. Даже не очень молодым разработчикам.


                    Хотя этот мой ORM, конечно, очень маленький и смешной. Скорее, такое, «ма, посмотри, как я могу на темплейтах».


                    1. qrKot
                      21.12.2018 20:36

                      Не совсем понимаю, каким образом diff коммита проекта на C++ показывает нужность ORM в Go. Или вы про то, что он нужен «в принципе» и «кому-то»?


                      1. 0xd34df00d
                        21.12.2018 20:46

                        Да, про то, что в принципе и кому-то.


      1. powerman
        21.12.2018 10:09
        +1

        Можете привести пример кодогенерации?

        Я использую https://github.com/cheekybits/genny, довольно удобно. Напр. у нас есть файл queue.go, в котором реализована довольно шаблонная структура очереди, но в ней намертво прошит тип элементов очереди "Msg", пример сигнатуры конструктора:


        func newQueueMsg(size int, out chan<- Msg) *queueMsg {}

        и нам нужна такая же, но для типов "Event" и "Item" вместо "Msg" — добавляем в начале файла queue.go строку:


        //go:generate genny -in=$GOFILE -out=generic_$GOFILE gen "Msg=Event,Item"

        запускаем go generate, и получаем новый файл generic_queue.go с конструкторами:


        func newQueueEvent(size int, out chan<- Event) *queueEvent {}
        func newQueueItem(size int, out chan<- Item) *queueItem {}

        Что касается интерфейсов, то лично для себя решил, что чем их меньше в коде — тем лучше.

        Тогда рекомендую глубже с ними разобраться, прочувствовать стоящую за ними идею. Это реально базовый элемент, на котором нужно строить архитектуру и внутренние API, который делает жизнь в целом намного легче, а тестирование проще на порядок. Чем больше функций/методов принимает параметрами значения интерфейсных типов (а возвращает при этом обычно значения конкретных типов) — тем обычно лучше. Особенно это касается функций-конструкторов и инверсии зависимостей, когда для создания одних сервисов им нужно параметрами передать сервисы, от которых они зависят, логгер, подключение к БД, etc. — все они должны быть интерфейсного типа. Плюс стоит внимательнее изучить стандартную библиотеку sort, на предмет того, как можно сортировать один и тот же тип по разным критериям простым приведением его к другому типу (Example SortWrapper). И ещё стоит посмотреть на реализацию http.HandlerFunc.


      1. anonymous
        21.12.2018 10:20

        Пример: easyjson


      1. pawlo16
        21.12.2018 16:26

        Я не забыл, а не знал, просто не сталкивался.


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

        Можете привести пример кодогенерации? Может нам стоит в проекте заюзать.


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

        Что касается интерфейсов, то лично для себя решил, что чем их меньше в коде — тем лучше


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


        1. gnomeby Автор
          21.12.2018 16:39

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


      1. JekaMas
        23.12.2018 07:40

        Кодогенерация:



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


  1. 411
    21.12.2018 08:23

    Да простят меня программисты на Go, но последний раз такое отвращение к синтаксису языка я испытывал, когда читал код на PHP лет 5 назад.


    1. qrKot
      21.12.2018 09:45

      Прощаем, так уж и быть, чо.

      Рекомендация: не читайте код на Go.


      1. 411
        21.12.2018 19:51

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


    1. YaakovTooth
      21.12.2018 10:48

      Очень важная реплика. У меня отвращение вызывают тараканы в общественных заведениях. Последний раз видел лет пять назад.

      Совпадение? Не думаю.


      1. 411
        21.12.2018 19:56

        Неуместный сарказм. Статья про проблемы Go, я выделил ещё одну на свой взгляд.

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


        1. YaakovTooth
          22.12.2018 00:36

          Объявлять объективной проблемой языка программирования то, что у вас там где-то отвращение на что-то — это определённо заявка на победу в межпланетарном конкурсе на самое раздутое эго.

          Но ссылаться завуалированно к какой-то там псевдообъективности в этом ключе и тем более выносить какие-либо оценки мне апеллируя к личности — это однозначная победа в конкурсе главмудаков как минимум этой звёздной системы, поздравляю, с наступающим!


          1. 411
            22.12.2018 11:45

            объективной

            когда я написал


            одну на свой взгляд

            Советую внимательнее читать комментарии.


            апеллируя к личности

            и после


            главмудаков

            Мне до сих пор непонятно, вы комментарии к своим же комментариям что ли пишете? Я на личности не переходил.


  1. dzolotarev
    21.12.2018 10:23

    На вкус и цвет все фломастеры разные. Я, например, со своей стороны много гуано могу накидать про ООП, от которого порой тошнит.


  1. szelga
    21.12.2018 12:28

    vgo одобрен

    вот теперь-то можно будет Go, наконец, пользоваться.


    1. Edison
      21.12.2018 19:05

      и уже в 1.11. Если проект не в GOPATH — по-дефолту модули включены.


  1. ilitaexperta
    21.12.2018 18:44
    +1

    «Ключевой момент здесь, что наши программисты (прим.пер.: гуглеры) не исследователи. Они, как правило, весьма молоды, идут к нам после учебы, возможно изучали Java, или C/C++, или Python. Они не в состоянии понять выдающийся язык, но в то же время мы хотим, чтобы они создавали хорошее ПО. Именно поэтому язык должен быть прост для понимания и изучения.»

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


    1. qrKot
      21.12.2018 20:03

      Как что-то плохое, в самом деле…


  1. egaxegax
    22.12.2018 00:41

    Гугл выпустил Гугл.Хром. Яндекс выпустил Яндекс.Браузер.
    Гугл следал язык Го. Яндекс сделает язык Я.
    … Пусть всегда будет Солнце (Гугл)… Пусть всегда буду я (Яндекс.)…


  1. Stormwalker
    22.12.2018 08:46

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


    1. gnomeby Автор
      22.12.2018 08:48

      Это вы про аннотации что-ли, которые надо самостоятельно в своём коде обрабатывать? Так этож медленно.


      1. Senpos
        22.12.2018 11:02

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

        Может показаться, что «для этого нужно стороннее решение», но это не так важно, так как mypy — самый используемый тайпчекер, стандарт дефакто и поставляется с любым плагином для среды разработки по Питону (VSCode тот же). В PyCharm есть свой. То есть, проверка типов встраивается очень приятно и без лишних телодвижений.


        1. gnomeby Автор
          22.12.2018 14:04

          Если я вызываю метод не напрямую, а предварительно записал его в переменную, эти расширения это поймут?


          1. Senpos
            22.12.2018 23:51

            Поймут.

            Вот пример, если я правильно понял ваш вопрос:

            # app.py
            name = "John"
            times = "1"
            
            
            def greet_times(name: str, times: int = 1):
                for _ in range(times):
                    print(f"Greetings, {name}!")
            
            
            greet_times(name, times)
            
            secret = greet_times
            secret(name, times)


            mypy app.py найдет и сообщит мне о двух ошибках: как для оригинальной функции, так и для делегированной:
            app.py:10: error: Argument 2 to "greet_times" has incompatible type "str"; expected "int"
            app.py:13: error: Argument 2 has incompatible type "str"; expected "int"
            

            На самом деле, они мощные и умеют сильно больше, чем пример выше.
            Даже если немного начать типизировать код — это помогает. Это еще называют gradual typing.


            1. gnomeby Автор
              23.12.2018 09:52

              А если так:

              # app.py
              name = "John"
              times = "1"
              
              
              def greet_times(name: str, times: int = 1):
                  for _ in range(times):
                      print(f"Greetings, {name}!")
              
              def greet_times2(name: str, times: str):
                  for _ in range(times):
                      print(f"Greetings, {name}!")
              
              
              if newInterface:  # внешнее условие
                  secret = greet_times2
              else:
                  secret = greet_times
              
              secret(name, times)


              1. Senpos
                23.12.2018 13:12

                mypy поможет уже начиная с момента, когда мы попытаемся передать times: str аргуметом в range(...), который ожидает int.


                Но если говорить про момент с условием — mypy сообщит, что тут что-то не так:


                app.py:19: error: Incompatible types in assignment (expression has type "Callable[[str, int], Any]", variable has type "Callable[[str, str], Any]")

                Похоже, тут mypy видит, что одной и той же переменной передаются Callable с разными типами и он сразу же об этом сообщает.


                Так же, насколько я понял, mypy делает предположение (так как он не может знать заранее что будет лежать в newInterface), что условие выполнится и в secret будет лежать функция greet_times2, которая на вход принимает str, str и не поднимает дополнительную тревога .


                Если же присвоение функций в условии поменять местами (при выполнении условия в secret присвоится greet_times, который str, int), mypy даст знать, что мы еще и типами ошиблись:


                app.py:22: error: Argument 2 has incompatible type "str"; expected "int"

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