Как стать автором
Обновить

Комментарии 481

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

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

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

Это только в случае удаления последней строки, в середину тоже самое, то есть редкий кейс. Да и правильно — git diff и код ревью вам в помощь.

Редкий не редкий, но зачем убирать такую защиту?
А если полагаться на дисциплинированность программистов и повсеместную отличную организацию их работы, то можно наверное много чего придумать, для упрощения работы компилятору.
Это не защита, это источник ошибок. При добавлении элемента, нужно постоянно изменять предыдущею строку, что еще и неудобно.
Ошибка пойманная компилятором — не ошибка.
А вот, повторю, удаление последнего элемента списка в Go должно отлавливаться уже на других этапах. Которых может и не быть.
А работа с синтаксисом языка у опытного программиста вообще на автомате происходит: думаешь о том что должно быть («функция такая-то», «добавить значение в конец списка» или вообще думаешь как алгоритм выразить в языке), а банальный синтаксис руки сами оформляют. Вот если какие-то хитровывернутые конструкции — там да, надо думать и над синтаксисом.
А уж запятую поставить или убрать — вообще не проблема.
Как часто вы случайно удаляли последний элемент в списке?
У меня никогда такого не случалось.
Ошибка — это то, что вам нужно исправить. Вот с удалением (или добавлением) запятой после редактирования кода я сталкивался — json был невалидный.
Статистику такую конечно не веду, но вполне себе могу представить рабочую ситуацию (т.е. не умозрительно, а из каких-то глубин памяти), что при выделении и копи-пасте захватил лишнего (последнюю строку).
И компилятор ругнётся сразу в двух местах: и там откуда забрал лишнее (список остался с висящей в конце запятой) и там куда вставил (лишний, не последний элемент в списке без запятой).
А в Go, выходит ни то, ни другое компилятор не заметит.
Вы на компилятор возлагаете какую-то совсем уж странную функцию: «контроль корректности заполнения данных программистом». К тому же не понятно, каким образом отсутствие запятой в конце списка вам должно помочь.
Отвечу на последний вопрос:
Если в конце списка и только в конце списка предполагается запятая, то там откуда я забрал лишнюю (последнюю) строку — в конце списка окажется несанкционированная запятая.
А там куда я вставлю вырезанное — будет 2 строки без запятой.
Да это-то я понял. Я не понял, чем именно последняя строка списка так сильно отличается от, допустим, предпоследней, или первой, или второй сверху?

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

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

Чота вы с ветряными мельницами воюете…
Да это-то я понял. Я не понял, чем именно последняя строка списка так сильно отличается от, допустим, предпоследней, или первой, или второй сверху?

Черт. Я понял! Нужно для каждой строки свой символ ввести. Тогда уж точно будет очень удобно и компилятор защитит нас от ошибки
Можно еще так — четные строки с запятой, нечетные без.
Получается 100% защита от случайного удаления любой строки.
Как тебе такое, 5oclock?
Можно вспомнить, что кроме кодов обнаружения ошибок еще есть избыточное кодирование, которое позволит её исправить! Нужно ставить 3 запятых где она должна быть, и 0 там, где не надо.
Блин, сперва прям порадовался гениальности решения, но…

Это же не защитит от случайного удаления ровно двух соседних строк! Что делать???
Увеличиваете количество запятых, и всё. Для исправления N ошибок требуется N+2 кодирование. Выбираете такое N, которое больше нравится, и используете соответствующее количество запятых.

Короче, надо просто добавить капчу в IDE, когда кто-то пытается удалить последнюю строку.

«Решите простенькое интегральное уравнение, чтобы подтвердить, что вы действительно хотите удалить эту строку».
Я придумал, придумал! Еще лучше придумал!

1. Нужно строки нумеровать (строго по порядку) — тогда мы защитимся не только от удаления строки, но еще и от случайных перемещений строки.
2. Вместо спецсимвола нужно использовать хеш от номера строки + её содержимого! Тогда мы заодно сможем защититься от случайного изменения строки!

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

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

И от чтения врагами (шифрованием получившегося).

ТакЪ победимЪ!!!
Я-то не воюю.
Это создатели Go решили из каких-то своих соображений сделать «не так как у всех» и заодно сломали такую вот «защиту последней строки», убрали наглядное подтверждение завершённости списка для читающего код.

Это как питон со своими отступами.
Удалил отступ у последней строки цикла или if'а и никто этого не заметит: ни компилятор, ни тот кто будет читать/сопровождать программу…
убрали наглядное подтверждение завершённости списка для читающего код.


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

https://github.com/golang/go/issues/23966#issuecomment-377997161


For example, the hacked-up blog post system I built stores a JSON blob at the top of each file, above the post text, because it was very easy to implement that. But I am sick of needing to leave out the comma after the last key-value pair, because it makes adding a new key-value mean editing the previous one too. This is exactly why we allow trailing commas in Go literals. Those annoyances add up.
Это не защита, это ересь какая-то, да еще и грабля впридачу.
А чего Вы ожидали? Человек разбирался в языке за один присест с книгой Введение в программирование на Go ))
Я работаю с Питоном несколько лет, попробовав Go остался доволен. Все те «недостатки» про которые все говорят принял как данность, типа: «А, хорошо — здесь это так работает и устроено так! Запомним!»
Почему-то некоторые начинают сравнивать этот язык со своим любимым/используемым…
Жаль на работе не дают использовать, мол, специалистов мало на рынке (
А чего Вы ожидали? Человек разбирался в языке за один присест с книгой Введение в программирование на Go ))

С чего вы взяли? Я вообще в основном со спецификацией языка работаю и работаю давно.
Ну шутка же была, что уж Вы — смайлики в конце намекают на рофл. Извините.
Я вообще в основном со спецификацией языка работаю и работаю давно.

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


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


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

Однако некоторые ваши позиции достаточно спорны.
var i int = 3
j := 6


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

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


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


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

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

Вариант, собственно, хорош, но при этом он ведет(ввиду возможности множественных возвратов) к:
var res int
var err error
res, err = someFunc()


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


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

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

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

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

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

По типу
var descriptor = getDescriptor<Descriptor1/>(param1, param2)

в любом современном строго типизированном языке
или

var descriptor Descriptor1
ok := getDescriptor(param1, param2, &descriptor)


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

Те же интерфейсы, которые казалось бы унифицированы и крайне просты…
Смотря в исходники(написанный людьми из гугла) я вижу:
    // Implements Something interface
    func (o object) doSomething (i Interface{}) {}

    // Implements AnotherThing interface
    func (o object) doAnotherThing (i Interface{}) {}


Да, самоучитель по GO я действительно(уже довольно давно) прошел часа за 3. Но читать его мне до сих пор местами крайне сложно, если имеется какая-то более-менее сложная логика требующая определенного кол-ва абстракций.
Мне кажется, проблема в том, что вы сетуете на то, что Go не Java…

var descriptor = getDescriptor<Descriptor1/>(param1, param2)


Выглядит очень похоже на абстрактную фабрику. Что есть неидиоматично для Go.

var descriptor Descriptor1
ok := getDescriptor(param1, param2, &descriptor)


Как минимум, именование функции странное для этого кейса. Что должна, собственно, делать эта функция?

И почему, собственно, не сделать *Descriptor1 ресивером функции?

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


Вот и я говорю: не пытайтесь на Go писать на Java.

Те же интерфейсы, которые казалось бы унифицированы и крайне просты…


Видимо, вы переусложняете. Семантика интерфейсов в Go отличается от общепринятой для ООП-языков.

// Implements Something interface
func (o object) doSomething (i Interface{}) {}

// Implements AnotherThing interface
func (o object) doAnotherThing (i Interface{}) {}


Абсолютно верно и идиоматично. В Go интерфейс — сущность необходимая принимающему объекту, а не типу, его реализующему. В этом и «одна из главных фич».
Java здесь вообще не при чем, это просто generic.
Сделать ресивером не выйдет т.к. эта функция должна, по сути, возвращать объекты различного типа.
Плюс ко всему, это код из библиотеки kubernetes, а не какой-то притянутый за уши пример, где java разработчики пытаются писать на GO.

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


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

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


Вот этот?

// Implements Something interface
func (o object) doSomething (i Interface{}) {}

// Implements AnotherThing interface
func (o object) doAnotherThing (i Interface{}) {}


Абсолютно нормальный код, так оно и должно быть. Что с ним не так?

В интерфейсах я делал акцент на комментариях, которые явно говорят о том, что семантики интерфейсов в GO явно не хватает.


Вы просто ожидаете от интерфейсов в Go соответствия интерфейсам Java или C#. Ну, собственно, зря. Интерфейсы Go — несколько иная сущность. Точнее, формально — то же самое. Предназначение — сильно разное.

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

Всегда есть trade-off. В некоторых языках (не будем показывать пальцем на Java, C#, C++) абстракции, упрощающие жизнь являются частью языка ценой возможности пиления абстракций, эту жизнь сильно усложняющих. Да и собственно ООП-языки задумывались, исходя из подхода «сначала абстракция». Т.е. предполагают сначала рождение абстракции, а затем уже конкретную реализацию в рамках задуманной абстракции.

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

Собственно, что есть хорошо, и что плохо — вещь сугубо ситуативная. Несложно понять, что каждый из подходов имеет право на существование в определенных условиях. Просто разный подход к проектированию.
1. Этот «неидиоматичный» подход решается введением generic'ов. Generic'и вводят во 2й версии Go.
2. Абсолютно нормальный код, который предполагает написание методов исключительно для реализации интерфейса(исходя из комментариев), что в корне противоречит вашему утверждению про интерфейс исходя из существующей реализации.
2. Ну когда вам нужно реализовать интерфейс, что бы использовать его в библиотеке, то все ок, да. Часто люди упрощают и передают один и тот же обьект, который реализует несколько интерфейсов.
Боюсь, дженерики, в том виде, в котором они появятся в языке-который-вы-не-используете, вызовут у вас не меньшее негодование, чем все остальное.

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


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

Нет, не задумывались, и таковыми не являются.
Видимо, статья — ответ на очередной виток холивара ООП vs ФП от, без сомнения, очень авторитетного чувака, — должна что-то обосновать в контексте обсуждения процедурного Go vs ООП-языки… Но я не уловил, что конкретно.

Смотри, на пальцах, разница в подходах:
1. Предварительно согласимся в терминологии. Есть «сигнатура» — предопределенное описание сущности. Есть «интерфейс» — сигнатура набора методов, обязательных к реализации. Есть «тип» — некая сигнатура сущности, которая может реализовывать интерфейс, а может не реализовывать. Есть «экземпляр типа» — инициализированный объект, соответствующий сигнатуре типа. Есть «потребитель» — некоторая сущность, принимающая на вход тип, ограниченный определенными критериями (в нашем случае интерфейсом).
2. В классическом ООП реализуемый интерфейс является неотъемлемой и обязательной частью сигнатуры типа. Т.е. вам необходимо явно указать, что тип реализовывает указанный интерфейс.
3. Также интерфейс является неотъемлемой частью сигнатуры потребителя.
4. В Go реализуемый интерфейс не является частью сигнатуры типа, и, по большому счёту, не имеет к оной никакого отношения.

Что это дает.

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

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

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

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

Проектирование тайпклассов несколько отличается от проектирования интерфейсов, но все еще не настолько, чтобы это было кардинально иной деательностью.

В «классических» ООП-языках есть инструменты для подхода «сначала абстракция». Есть, заодно, до кучи, целые библиотеки, состоящие исключительно из абстрактных типов/методов, из шаблонов и дженериков. Я не говорю, что это плохо. Я говорю, что в Go от этого решили отказаться — только и всего.

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


Ну да, идея прикочевала из C++, куда была занесена ветром из Хаскеля.

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


Не по недоразумению, а по замыслу авторов.

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


Вы же понимаете, что:
а) ваша выборка непрезентативна;
б) моя выборка тоже непрезентативна;
в) «обычно делают так» пишут в случает отсутствия пруфов.
Ну да, идея прикочевала из C++, куда была занесена ветром из Хаскеля.

Ну, гм, в C++ нет тайпклассов. Можно что-то там накостылять на темплейтах, но это не ветром из хаскеля, это просто так получилось по историческим причинам.


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

Ну, гм, в C++ нет тайпклассов.


Ну, видимо, есть, раз один из авторов Go утверждает, что идея заимствована из хаскеля, за основу взята C++-реализация.

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


Собственно, единственное различие: при изменении сигнатуры потребителя необходимо изменять сигнатуру потребляемого типа. В случае структурной типизации такой необходимости нету — только и всего.
Ну, видимо, есть, раз один из авторов Go утверждает, что идея заимствована из хаскеля, за основу взята C++-реализация.

Ну раз один из авторов Go утверждает, то конечно.

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

Эти тайпклассы, которые применяются автоматически — это на самом деле структурная типизация (вместо номинальной, как в Java/C#/не-темплейтном-C++). У неё есть свои преимущества, но есть и свои недостатки.

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

У неё есть свои преимущества, но есть и свои недостатки.


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

Я ничего не понял про ресиверы и вот это всё. Можете пример привести?

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

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

В процессе вы рожаете некий TypeB, реализующий функционал, аналогичный TypeA. Тут, я думаю, вы согласны, появление таковой необходимости очень часто бывает обоснованным. И вам нужно, чтобы Method1 на вход мог принимать обе реализации. Что же делать, что же делать?

Правильно, вы пилите интерфейс (пусть будет InterfaceAB), обобщающий оба типа, и меняете сигнатуру Method1, который теперь вместо TypeA на вход принимает InterfaceAB.

И вот в этом моменте вы обязаны явно указать TypeA, что он реализует InterfaceAB, и то же самое сделать для TypeB. Т.е. список реализованных типом интерфейсов — неотъемлемая часть этого самого типа.

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

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

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


И вот в этом моменте вы обязаны явно указать TypeA, что он реализует InterfaceAB, и то же самое сделать для TypeB. Т.е. список реализованных типом интерфейсов — неотъемлемая часть этого самого типа.

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


Трейты в расте работают аналогично, насколько я знаю.

Часто читаю отзывы, что питонистам нравится Go и все не могу понять — почему?
Чем он вас цепляет после Питона?
Я хоть владею Питоном не профессионально, но даже учить Go и его особенности неприятно. Мне куда ближе тот же Kotlin.
Дешёвый способ получить хороший перфоманс по сравнению с Python, когда это начинает быть важно.
С удовольствием перешёл с питона на го. Как уже сказано в соседнем комментарии — быстродействие приложений заметно выше получается. Потребление памяти заметно ниже.
Интерпретаторы, честно говоря, никогда особо не нравились. Тащить за скриптом чемодан без ручки в виде интерпретатора и библиотек, такое себе развлечение. С го гораздо проще. Ну и по мелочи, потом всё-таки сложилось мнение, что питоновские отступы это не лучшее решение, скобки удобнее. Статическая типизация удобнее. Реализация асинхронности очень понравилась.
В общем, к питону уже не вернусь.
Как раз таки, если оперировать словом «хайп», то оно к питону больше подходит.
Питон тут, питон здесь, питон суют куда только можно. Там где в общем и не очень понятно почему именно питон. Люди, которые даже пары строк написать не умеют и никогда не программировали, есть таких много в айти индустрии, всё равно знают, что питон это круто и модно.
Про Go только второй год не затыкаются. До этого были Scala и Ruby. Ну и где они теперь?
Именно это — хайп. Домашние проектики и десяток стартапов. Go повторит историю Ruby.
Питону уже нормально так лет, он в топ3, он в матером продакшене.
он в матером продакшене

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

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

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

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

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

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

Чтобы проверить не поменялась ли логика при рефакторинге.
А можно пример такой логики, которая может поменяться при рефакторинге?
Любое бизнес поведение программы.

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

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

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


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

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

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

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

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

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


Положим для простоты, что вы пишете свою хитрую арифметику поверх своего хитрого типа 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'а там нет, в частности).

Учитывая наличие в вашем коде subZero, это должен быть fatality checker.
НЛО прилетело и опубликовало эту надпись здесь
Не могу сказать, что много кодил на Python (3), но переход на GO прошел на ура по сл.причинам.
1. компилируемый = скрость
2. Горутины и каналы — изящно, красиво и многопоточно.
3. строгий контроль типов (знаю в питон тоже завезли, но в go оно из коробки был)
4. работа с переменными (динамическое объявление, автоматическая сборка мусора) очень близка к Python, а не к C (чего очень не хочется: ног я себе уже наотстреливал предостаточно)

т.е. есть плюсы и есть знакомые. приятные вещи. Посему переход — приятен.
Golang подходит почти всем, кому, нужен перфоманс в скорости по сравнению со скриптовыми языками, такими как ruby, python, php и даже javascript..., но он не нужен тем кто работает с java, с#…
Простая причина, это почти либа при которой можно девелопить вещи имея стандартные средства для микросервисов, быстро изучается, тяжело отстрелить себе ногу…
а синтаксис и питоновцы, хотя самому нравится питон… могу предположить что go еще более строгий и минимум скрывает своих задвигов за сахаром, как это делают другие интерпретируемые языки
о он не нужен тем кто работает с java, с#…

в данном случае Go упрощает деплоймент и поддержку приложений.
Каким образом?
Деплой? Очень простым: положил бинарь, и оно работает.
Докер-компоуз — не лучший инструмент для прода.

Ну и, в конце концов, вы не разрешили проблему. Вместо .NET-рантайма вы на сервер просто тащите докер. Один фиг на 1 зависимость больше, чем у Go.
Докер-компоуз — не лучший инструмент для прода.

Как раз наоборот. Надо же и оркестровать сервисы, и вот этим всем заниматься.

Ну и, в конце концов, вы не разрешили проблему. Вместо .NET-рантайма вы на сервер просто тащите докер. Один фиг на 1 зависимость больше, чем у Go.

Допустим. Чем это плохо-то?
Как раз наоборот. Надо же и оркестровать сервисы, и вот этим всем заниматься.


Еще раз, docker-compose — не лучший инструмент для прода. Он очень плохо ведет себя под нагрузкой, он не умеет live-reload, он не умеет адекватного управления ресурсами и т.д. и т.п. Более того, docker-compose — всего лишь управлялка над голым докером, который очень не рекомендуется выставлять наружу.
В конце концов, вам дали божественный k8s, а вы на прод голый докер суете. Стыдно должно быть. Я понимаю, что вы, в конце концов, разработчик, а не админ/девопс. А продом должен таки рулить тот, кто умеет его готовить.

Допустим. Чем это плохо-то?


Плохого-то ничего. Это просто опровержение вашего высказывания, что .NET-приложение деплоится не сложнее Go-шного. Ну вот говорю: сложнее. Для вашего случая всегда +1 шаг деплоя относительно Go.

Заметьте, я не говорю, что это прям усраться проблема. Я просто говорю, что N+1 шаг всегда больше, чем N.
Ну ок, трудно с этим спорить. Но на практике разницы я не вижу, что там, что там жмешь кнопку в тимсити и оно полетело в релиз.
тем что не надо обновлять/патчить тот же JVM на всех серверах, где это ранится. А если докер — то тут надо следить за тем, что используют программисты и обновлять какой-нибудь базовый image.

Деплоймент будет гораздо быстрее — компилятор быстрее, плюс не нужно тянуть кучи зависимостей типа docker image с OS, сам docker image будет максимально маленький.
И все в этом духе.
тем что не надо обновлять/патчить тот же JVM на всех серверах, где это ранится. А если докер — то тут надо следить за тем, что используют программисты и обновлять какой-нибудь базовый image.

Обновлять рантайм — м? В дотнете рантайм не обновляется, если проект сделан под core 2.0, то на нем и запускается всю жизнь, пока лид не решит сделать судьбоносное решение и обновить версию. Что будет сделано путём исправление одного dockerfile. Не вижу, чем должна быть проблема.

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

Вот не уверен, что компилятор Go быстрее csc.
Обновлять рантайм — м? В дотнете рантайм не обновляется, если проект сделан под core 2.0, то на нем и запускается всю жизнь, пока лид не решит сделать судьбоносное решение и обновить версию. Что будет сделано путём исправление одного dockerfile. Не вижу, чем должна быть проблема.

То есть security upgrades не принято делать в .NET?

Вот не уверен, что компилятор Go быстрее csc.

Можете протестировать. Но я там еще написал, кроме компилятора.
То есть security upgrades не принято делать в .NET?

Видимо, нет.

Я вообще не припомню уязвимости в .net, которые нужно было бы закрывать.

Можете протестировать. Но я там еще написал, кроме компилятора.

А к остальному у меня нет комментариев.
> Я вообще не припомню уязвимости в .net, которые нужно было бы закрывать.

Идеальный .net.
Вот java мы недавно апдейтили. У нас просто принято делать апдейты безопасности (даже не критические, критические мы сразу же обновляем) периодически.

Не, всё-таки бывают: https://www.cvedetails.com/product/43007/Microsoft-.net-Core.html?vendor_id=26


Не знаю, может они образ дефолтный обновляют… Я лично пользуюсь microsoft/dotnet:2.0-runtime, возможно они просто под этот тег пушат рантайм с исправленными уязвимостями, не готов сказать. Кого-то более мелкого версионирования чем 2.0, 2.1. и т.п., по версиям билда там или закрытым уязвимостям я не встречал.

Я вообще не припомню уязвимости в .net, которые нужно было бы закрывать.


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


Не на всех задачах из JVM или .NET можно выжать сравнимый с Go перфоманс. При этом их использование сильно не всегда это рентабельно в сравнении с Go.

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

Если в текущем проекте на дотнете вас все устраивает, откуда вообще может взяться желание переписать его на Go?

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

На чём можно сделать лучше — это выбор «идеального мира». В реальном ищут предпочтительный по соотношению «цена/качество» вариант.
Из того, что я видел, обычно начинают выжимать производительность на самом дотнете, а не инициируют переписывание на что-то другое. В ход идет unsafe, SIMD и вот это всё.
Вот и я о том же, что заради одного перфоманса на другой стек не переползают, обычно, ровно до тех пор, пока совокупность проблем не становится неразрешимой внутри локальной экосистемы.
Go и Kotlin — практически диаметрально противоположные языки. Go создан так, чтобы было удобно компилятору. А Kotlin — чтобы было удобно программисту.
только вот со структурами как то странновато сделано в плане наследования.
хотя по своему тоже фича
Это самая необременяющая реализация типа ООП над структурами Си. При относительной дешёвости достигается гораздо лучшее чтение кода.
я все равно не до конца понимаю почему такое странное наследование.
то есть вроде бы откуда уши растут(методы) понятно, но все же.
Наследование странное, вероятно, потому, что его нет… Композиция есть, наследования нету.
а почему не сделали просто добавлением полей в потомке?
вложенность выглядит несколько противоестественно.
Потому что нет никакого потомка, вероятно. Наследования нет, а значит и потомка нет. Есть встраивание типа, и оно именно то, чем кажется.
в некотором роде таки есть.
и в принципе понятно как это работает.
наверное при таком подходе всё даже логично.
ИМХО, достаточно нетривиальное поведение.

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

Так ведь с Го невозможно не разобраться, он ведь такой простой, что прочитав за день книжку с основными моментами можно прод писать.
Проблема в том, что многие executives действительно в это верят.
Почему гоферы так радуются единому бинарнику, преподнося его как огромный плюс? (Или почему так не делают любители других компилируемых технологий, вроде C++?) Деплой же обычно автоматизирован, а динамическую линковку когда-то тоже преподносили как решение некоторых проблем. Каков здесь истинный практический смысл?
1. Потому что Go отъедает рынок у python, php, ruby и пр, где нет единого бинарника.
2. Потому что иногда приходится возится с бинарником напрямую, и когда это один файл — это удобно.

Да, в проекте, где CI уже настолько автоматизирован, что нет в нём никаких правок, это преимущество утрачивается.
Потому что Go отъедает рынок у python, php, ruby и пр, где нет единого бинарника.

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


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

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

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


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

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


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

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

И я про то же, Go — убийца Node.JS, и конкретно у нее он нишу и отжимает.

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


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

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

Это, кстати, заблуждение — думать что между сайтами-визитками и хайлоадом ничего нет по середине. В моём понимании, большинство — это куча разнообразных проектов с многомиллионными бюджетами, но при этом не предполагающих особых нагрузок. Если посмотреть правде в глаза, то даже средне нагруженных проекты от силы 1% от веб-приложений наберётся (сайты-визитки я в расчёт даже не беру)
Я бы сформулировал, что если не предполагается нагрузка выше 10 rps в среднем за сутки, и хотя бы 50 rps в пиковые часы, то даже смотреть в сторону Go — совершенно нецелесообразно. Понятно, что запросы сильно разные бывают и цифры ориентировочные, но порядок примерно такой.

А я и не говорю, что посредине ничего нет. Я говорю про то, что у Go ниша — где-то между 10rps в пике и хайлоадом. И в ней он чувствует себя достаточно уверенно (и дело не только в многопоточности).

Я бы сформулировал, что если не предполагается нагрузка выше 10 rps в среднем за сутки, и хотя бы 50 rps в пиковые часы, то даже смотреть в сторону Go — совершенно нецелесообразно.


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

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

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

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


сильные стороны Go — модель асинхронности

Модель асинхронности Go мне, кстати, не нравится, в Erlang/Elixir гораздо интереснее и, на мой взгляд, удобнее.

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


Вот именно потому «микросервисы» и взлетают. Смотри, есть непосредственно фронт (веб-морда или клиентское приложение), есть бэк, который отдает данные этому фронту и принимает от него запросы. И «развесистую бизнес-логику», конечно, можно держать прямо в этом бэке, но делать так не принято. Именно в той вещи, в которую смотрит фронт, что-то тяжелое считать не принято. Для того, чтобы считать что-то тяжелое, есть java-приложения, очереди, мемкеши и прочие относительно автономные вещи, которым самое место за гошным беком. Основная ниша Go — это вот эти инфраструктурные вещи, прокси, если хотите, назначение которых — маршрутизация, аггрегация уже готовых датасетов, некоторые конвертации данных и тому подобное. А для того, что обрабатывает «развесистую бизнес-логику», есть Java (и сотни других инструментов), которая является значительно более «нежным» относительно нагрузки инструментом.

Да, на Java/C# и т.д., да на любом языке «общего назначения» можно и инфраструктурные вещи писать, только делать это чувствительно больнее, чем на Go. И вот там его ниша. При том, что порог вхождения в язык, на самом деле, действительно низкий. Любой мало-мальски адекватный специалист во «взрослом» и «многоцелевом» энтерпрайз-языке может позволить себе изучить Go как дополнительный инструмент для ограниченного круга задач. Вот реально, понять Go проще, чем «вкурить» тот же Java Stream API (а это сильно один ИЗ инструментов из джентльменского набора java-девелопера).

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

А вот в момент, когда часть этого портала нужно будет «отзеркалить» или «смаппить» в виде портала, смотрящего наружу… Или если надо будет отдать простенькое API для запроса состояния какого-то элемента системы… Или если вы дорастете до распределенного решения…

Понимаете, ASP.NET — специалист для того, чтобы тупо проксировать наружу один REST-запрос, скорее всего, тупо поднимет еще один инстанс ASP.NET. Вы же согласны, что полученный оверхед принято называть «конским».

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

Модель асинхронности Go мне, кстати, не нравится, в Erlang/Elixir гораздо интереснее и, на мой взгляд, удобнее.


Кхм, несколько разные инструменты.Прям калибр разный, и для разных задач.

Да и в принципе, вот эта подача вида «язык Go умеет простой деплой и приятную модель асинхронности, а вот простой деплой уже 3 месяца умеет C#, а в эликсире прияная асинхронность». Давайте уже «в лоб», не говорить, что фича А встречается в языке Б, а фича В встречается в языке Г. Собственно, изначально при разработке Go так и сказали «в языке нет ничего усраться-прорывного и уникального, и вы тут не найдете ни одной фичи, которую нельзя бы было встретить в других языках». Давайте покажем другой язык с превосходящей комбинацей фич.
Однако императив проще в понимании, поэтому функциональное программирование в массах «не взлетает».

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

Тут вопрос в чем.

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

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

Совсем нет. Функциональный подход — "опишите конвейер преобразований".
Соответственно, каждый этап самостоятелен и не лезет в детали реализации других этапов, а просто принимает входные данные и выдаёт свой результат.


Только людей с математическим складом ума сильно меньше.

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

Функциональный подход — «опишите конвейер преобразований».


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

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

Вы потеряли нить разговора.

Началось с «императив проще для понимания». Это вы отрицать перестали и стали упирать на то, что «правильнее».

Правильность — это вещь, вокруг которых можно миллион копий поломать. Проще (в итоге) написать корректный код — да, вероятно, в функциональном подходе. Но проще в понимании — императив.

Я ничего не отрицал, потому что изначально не понимаю, чем описание последовательности действий (процедурное программирование) проще, чем описание конвейера?.. Понятно только, что ООП (описание объектов и их взаимодействия) уже точно сложнее.

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

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


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

Как раз-таки математика у людей с детства, и понять функции совсем нетрудно. У меня знакомый преподает, он никак не может объяснить детям, что значит "присвоить результат функции". Не знакомы они с этим.


Это вам только кажется, что императивщина такая простая. Мои друзья-непрограммисты до сих пор выносятся с записи i = i + 1. И хоть я 100 раз говорил, что равно — это присваивание, а не равно собственно, не понимают. Видимо, отклонения в развитии. Или неправильные шотландцы.


Вот вы сами, вам приятнее руками цикл for писать, или всё же let foo = a.map(|x| x + 1)? Тут ведь страшная формула вместо простого и понятного "создай переменную со значением 0, создай переменную с длиной массива а, создай буфер размера с эту переменную, присвой в первый элемент буфера значение первого элемента массива а, увеличь первую переменную на единицу, проверь, что её значение меньше длины массива а".


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

Мои друзья-непрограммисты до сих пор выносятся с записи i = i + 1. И хоть я 100 раз говорил, что равно — это присваивание, а не равно собственно, не понимают. Видимо, отклонения в развитии. Или неправильные шотландцы.

Или они просто работают в бестиповом лямбда-исчислении, а там у любой функции есть неподвижная точка, даже у succ.


Не удержался от глупой шутки, не обращайте внимания.

Вот вы сами, вам приятнее руками цикл for писать, или всё же let foo = a.map(|x| x + 1)? Тут ведь страшная формула вместо простого и понятного «создай переменную со значением 0, создай переменную с длиной массива а, создай буфер размера с эту переменную, присвой в первый элемент буфера значение первого элемента массива а, увеличь первую переменную на единицу, проверь, что её значение меньше длины массива а».


let foo = a.map(|x| x + 1)? Тут ведь страшная формула вместо простого и понятного


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

Классическая императивщина:
a = 0
while a < 10 {
… do something…
a = a + 1
}

Вы меня простите, конечно, но вот это «let foo = a.map(|x| x + 1)» вообще ни разу не понятнее человеку «со стороны». Считать это простой и очевидной конструкцией — в вас говорит профдеформация.

У вас претензии к синтаксису или к семантике?


Кстати, ваш цикл не эквивалентен тому выражению.

У меня претензии к высказыванию о том, что «let foo = a.map(|x| x + 1)» проще и ближе в понимании неподготовленному человеку, чем «for i := 0; i < 10; i++» или, допустим, «for i < 10».

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


foo = map (\x -> x + 1) a

Против, ну, например,


List<T> foo;
for (size_t i = 0; i < a.size(); ++i)
    foo.push_back(a[i] + 1);

Второе правда проще для неподготовленного человека?


И я, кстати, ошибся, когда писал второе, написал a вместо a[i]. Да, компилятор бы поймал сразу, но всё же.


Есть ещё, кстати, такая вещь, как выражение намерения. Когда я вижу map, мне сразу ясно, что там поэлементное отображение некоторого контейнера. Когда я вижу foldr, я сразу понимаю, что это правая свёртка. Вижу maximum — поиск максимума. Вижу all — проверка, что все элементы удовлетворяют предикату. Вижу find — поиск элемента по предикату. Вижу filter — ну, сами понимаете.


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


И примерно поэтому в плюсах, например, есть std::accumulate, std::find_if, std::transform, в C++11 добавили std::any_of/std::all_of/std::none_of (чтобы проверять предикаты), и так далее. И ряд людей (некоторые из которых про всю эту функциональщину и не слышали толком) рекомендует пользоваться этими примитивами вместо того, чтобы каждый раз фигачить цикл. Наверное, это тоже на что-то намекает.

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

foo = map (\x -> x + 1) a

Против, ну, например,

List foo;
for (size_t i = 0; i < a.size(); ++i)
foo.push_back(a + 1);

Второе правда проще для неподготовленного человека?

Давайте пример из Go тогда приведем:

var foo []TypeName
for _, item := range a {
    foo = append(foo, item)
}


Блин, ну честно, в самом деле:

map (\x -> x + 1) a версус range a )))

Понимаете, map, foldr, all, find, filter — это все-таки конструкции, которые могут быть очень лишними в некоторых контекстах. Да и порядок исполнения не всегда очевиден.

map (\x -> x + 1) a версус range a )))

Версус _, item := range a тогда уж.


Почему там :=, а чуть ниже =? Что вообще значит foo []TypeName? Я такого указания типизированного списка вот ещё вообще ни в одном языке не видел.


Что за append? Мой мозг вот стриггерился на то, что вы присваиваете всему foo целиком, например.


Понимаете, map, foldr, all, find, filter — это все-таки конструкции, которые могут быть очень лишними в некоторых контекстах.

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


Да и порядок исполнения не всегда очевиден.

Зачем вам порядок исполнения в map? Кстати, тоже важная компонента описания намерения — map чистый, порядок неважен, можно хоть параллельно элементы вычислять. Важен порядок свёртки? Ну так поэтому есть foldr и foldl по отдельности.


А вот когда мне в плюсах надо обойти контейнер с конца в начало и сделать это циклом (например, потому, что контейнер не даёт никаких итераторов, и я не могу переписать код в терминах reverse iterator'ов), я начинаю немного нервничать и по 5 раз перепроверять граничные условия, корректность индексации и всё такое.

Почему там :=, а чуть ниже =?


Потому что := — сокращенная запись от var имяПеременной ТипПеременной.

чуть ниже =


Потому что чуть ниже переменная не объявляется.

Что вообще значит foo []TypeName?


var имяПеременной []ТипЭлементаСлайса. В чем, собственно, проблема?

Что за append? Мой мозг вот стриггерился на то, что вы присваиваете всему foo целиком, например.


Все верно делаем, местечковая фича.

А вот когда мне в плюсах надо обойти контейнер с конца в начало и сделать это циклом (например, потому, что контейнер не даёт никаких итераторов, и я не могу переписать код в терминах reverse iterator'ов), я начинаю немного нервничать и по 5 раз перепроверять граничные условия, корректность индексации и всё такое.


Вот поэтому в Go и нет богатого выбора контейнеров.

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


Я не зря не писал foo = (\x -> x + 1) <$> a, чтобы не было вопросов о том, что такое <$>.


Вот поэтому в Go и нет богатого выбора контейнеров.

Контейнер — это и какая-нибудь матрица из сишного мира, в том числе.

Контейнер — это и какая-нибудь матрица из сишного мира, в том числе.


В обсуждении выше под контейнерами подразумевались сущности, как минимум, предоставляющие итераторы.
Вы меня простите, конечно, но вот это «let foo = a.map(|x| x + 1)» вообще ни разу не понятнее человеку «со стороны». Считать это простой и очевидной конструкцией — в вас говорит профдеформация.

Нет, эквивалентом будет


int[] result = new int[a.length];
for (int i = 0; i < result.length; i++)
{
   result[i] = a[i] + 1;
}

Да, это намного менее понятно. Если вам не нравятся вертикальные палки, можно взять JS: let foo = a.map(x => x + 1).

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

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

а вот ООП и ФП, как раз любому не подготовленному человеку воспринимается очень тяжело, так как уже надо думать не в духе что вижу, то и получаю, а надо думать совершенно другими абстракциями и в голове еще удерживать что там эта шайтан машина делает…

Помню натыкался на статьи, где на западе в универах давали ФП первым языком (вроде схему, но это не точно), вместо устовяшихся C/Pascal/Java/Python, и результаты ничуть не хуже. Могу поискать пруф, если нужно, но пока могу сказать, что это чисто вопрос привычки.


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

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

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

С программистами все проще, хотя людям из универа которым вдалбливали ООП и они им упаровались, иногда очень сложно объяснить каррирование и калбеке, с ходу они тяжело читают такой код.
А вот тем кто упаровался ФП, очень тяжело объяснить подходы ООП.

Сужу по своей практике.
С программистами все проще, хотя людям из универа которым вдалбливали ООП и они им упаровались, иногда очень сложно объяснить каррирование и калбеке, с ходу они тяжело читают такой код.
А вот тем кто упаровался ФП, очень тяжело объяснить подходы ООП.

С этим не могу спорить. Собственно, я с того и начал: синдром утенка. Если бы императивщину так не совали навязчиво, такого бы и не было. Думаю, ситуация будет меняться ближайшие 30 лет. Собственно, она уже изменилась настолько, что в насквозь императивные С++ просовывают std::variant и прочие ФП штуковины.

Ну, в плюсы просовывают variant потому, что в бусте оно было уже лет 20, наверное.

Я к тому, что паттерн-матчинг это дефолтная фича, а во всех мейнстрим языках оно только появляется или появилось. В том же C# только в 7 версии пару лет назад. А он ведь еще считается достаточно активно изменяемым.

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


Есть наркомания на макросах, но её переносом в стандарт и не пахнет.


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

Я говорил про шаг в нужную сторону, не про реализацию.

Так-то ждем. Но в шарпах например, появился, а это уже точно мейнстрим.

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

я вот сам больше упаровался по ООП, в функциональном программировании пока не силен. Хотя после 1 попытки изучения Хаскеля… ну что я могу сказать, я в восторге, но пока я склоняюсь к мнению Дядюшки Боба, то есть, они могут сосуществовать вместе.
Конечно. Кстати, неплохая ссылка в тему, спорная, но полезная: github.com/cblp/funlangs

Странная табличка.


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


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


Гардов для паттерн-матчинга не указано, а в идрисе без них очень больно. Есть with-matching, но это костыль для этих целей.


Universe polymorphism в хаскеле выражается через kind polymorphism, если я правильно понимаю.

Спасибо за ссылку.

Что касается golang, то его синтаксис, его подход далек от простоты, все же это язык для программистов. Но именно для программистов он прост как 2 рубля.

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

Божечки… И сюда таблицу Сыроветского притащили.

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

Ага, только это иллюзия понимания… Потом человек попадает на сайд-эффект и "весело" проводит неделю за отладкой в полном непонимании как работает эта шайтан машина.

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

Это просто ваше мнение или есть какие-то подтверждения, кроме "Очевидно"?

Наверное мое мнение и своя личная статистика, не более.

А вдруг затык в том, что Вы непонятно объясняете функциональщину? xD

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

Хотя те вещи которые я объясняю они на мой взгляд читабелнее.
Но факт остается фактом… Моя не очень сильная компетенция в данном вопросе может родить непонимание.

Так вещи — это конкретные конструкции. А начинать надо с идей и предпосылок.
Например, мне не встречалось людей, у которых возникали проблемы с пониманием Linux pipes, а это по сути реализация одной из основных идей ФП. Хотя тут можно возразить, что не так много людей знакомы с Linux pipes, но концептуально это всё равно достаточно просто.
Имхо, проблема ФП в том, что многие авторы книг и статей перебарщивают с математической терминологией, чем создают иллюзию, что всё ппц как сложно.

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

Допустим нам надо решить проблему 2+2. Мы можем думать в формате ООП и скормить объекту x,y конструктору, но можем передать аргументы в функцию, а можем просто из функции без параметров вернуть 4.

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

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

Это довольно спорный тезис. Как же разносторонность мышления?


Допустим нам надо решить проблему 2+2.

Плохой пример. В данном случае, нет смысла даже в функции, это просто константа.


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

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

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

Но скорее я сейчас говорю о том как быстрее и легче решить задачу.

Воспринимать это как объект(ООП), как процесс(ФП), как просто должное и не городить абстракции вокруг задачи(императивное...)?

Это довольно спорный тезис. Как же разносторонность мышления?


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

Ради интереса возьмем размышления такого адепта как Егор Бугоенко и его отношением к ООП, но ведь во многих вопросах он прав, но чисто в технических нет(от сюда и так много споров и негодований), какое решение мы применим, скорее всего такое которое нам ближе)
Воспринимать это как объект(ООП), как процесс(ФП), как просто должное и не городить абстракции вокруг задачи(императивное...)?


Маленькая поправочка, не «императивное», а процедурное. ООП-языки в подавляющем количестве случаев императивны.
ООП языки во многом дают возможность решать проблемы императивно, даны инструменты… я к тому за что топит Егор Бугаенко в поисках идеальных ОО языков программирования. Но мыслить как ООП программист и как он будет делать другой вопрос.
Хотя я убеждаюсь в том что лучше мешать ООП и ФП, пока мое субъективное мнение.
Но с другой стороны, легко объяснить не процесс формирования цифр, передачи параметров, а скорее проще объяснить состояние инкаплсулированного стейта в объекте, при всем при этом, легко в голове почти у каждого среднестатистического человека укладывается ограниченный контекст, особенно если предметная область еще не близка.
проще объяснить состояние инкаплсулированного стейта в объекте, при всем при этом, легко в голове почти у каждого среднестатистического человека укладывается ограниченный контекст

Вы рассматриваете какие-то очень тривиальные примеры, оторванные от реальной практики. Спору нет, ООП даёт быструю иллюзию понимания. А осознание того, что оно ложно чуть менее, чем полностью, приходит только с годами опыта.
Попробуйте какие-нибудь простенькие паттерны типа абстрактной фабрики или моста объяснить человеку со стороны.

Ну так ООП и ФП направлены… не верно пишу и говорю, все это направлено на упрощение, на легкую поддерживаемость, не все практику ООП и ФП могут дать легкость понимания кода. Но все мы боремся со сложностью…

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


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

Это вы по опыту говорите или, ну, так?


В ФП с эффектами и их разделением всё очень хорошо.

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

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


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


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

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

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

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

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

К самому языку, опять же, ортогональны. Таких уязвимостей на любой язык натаскать можно. Тем более в контексте противопоставления 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

Уязвимость в стандартной библиотеке. Покажите мне уже язык, в котором НЕ находили уязвимости в стандартной библиотеке.
Не первый и не последний софт с уязвимостями. Выпустят патч и всего делов. А вот вы например уверены, что ваши программисты предотвратили переполнение буфера во всех местах. valgrind`ом каждый раз софт прогоняют? На Эльбрусе пробовали запускать, для аппаратного отсечения уязвимостей?
На эльбрусе нет смысла прогонять, мы под него не пишем. У нас используется статический анализ тремя тулзами (мерзкоPVS, cppcheck, clang), перед тем как отправлять на прод мы смотрим valgrind на предмет утечек и чтений\записи мимо. На самих узлах supervisord, который в случае падения вышлет нам полный отчет с core-dump'ом на почту. Плюс мы используем grafana-over-zabbix чтобы в real-time видеть что происходит.
Я же говорю, вы приятное исключение. С таким осознанным подходом можно писать на чём угодно хорошо. Даже для Go вы выключили бы статическую линковку и всё было бы хорошо.

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


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

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

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


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

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


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

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

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

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


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

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

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

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

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

Красава, например, ППКС.
Тут нечего обсуждать, такого никогда не будет.
Новый гейтс?
64 Гигов хватит всем?
Я про то, что не видно тенденций к тому, что всё запускаемое будет статическим.
Когда «наступит до поры до времени» вы сможете пересобрать без статической линковки, go просто собирает так по умолчанию.
В серьезных проектах на C++ периодически сталкивался с ситуациями (особенно в *nix системах) когда программа «ведет себя странно». В результате анализа выяснялось что по тем или иным причинам на runtime загружается не та версия библиотеки (не из того каталога к примеру). В этом случае монолит — спасение.
С другой стороны иногда осознанно хочется поделить на несколько частей, для компактности обновления, к примеру.
Что есть смысл все свое таскать с собой буквально я не ставлю под сомнение) Удивляет то, что это входит в TOP-5 фич Go, хотя решение очень распространенное.
Видимо, дело в нише, где Go конкурирует со скриптовыми языками, где нет ни Java-jar'ов, ни сборок в стиле .NET
Данный TOP-5 вообще слабо коррелирует с моим пониманием.
Чего стоит хотя-бы наличие одномоментно 2-х пунктов:
Многопоточность из коробки — в языке не просто сделать многопоточность, а очень просто.
Быстрый язык — в виду компилируемой природы и ограниченности синтаксиса вам будет сложно выжирать много памяти и процессорного времени в ваших программах.
Если бы только здесь) Об этом пишут почти в каждой статье, то есть удаляют достаточно много внимания такой, казалось бы, инфраструктурной штуке. Ну какая же разница, грубо говоря: папку копировать или файл?
Я может непонятно выразился.
Дело в том, что если многопоточность реализуется легко, то несколько потоков будут намного более активно выжирать и память и процессор, нежели 1 поток. (на самом деле в Go потоков будет столько-же сколько ядер процессора, а остальная параллельность будет реализована программно)

По факту именно в языке Go столкнулся с тем, что при простейшей разработке подразумевающей распараллеливание процессор выжирается полностью. В результате программа выполняется за значительно меньшее время. Хотя если пересчитать нагрузку на процессор и время выполнения, то видно, что реальное время вычислений на CPU сопоставимо с другими языками, но они используют процессор на 50% (к примеру) и работают в 2 раза дольше.
GOMAXPROCS и всё в ваших руках, регулируйте в нужную сторону.
Мне показалось, что вы привели пример того, что здесь какая-то особенная статья. А я продолжаю о своем)
Любая статья на Хабре — мнение автора различной квалификации. Для оченки качества этого мнения даны различные инструменты.
Оценивать не могу и не горю желанием, а вот докопаться до истины интересно) Ваша версия про сравнение с нишевыми аналогами кажется мне пока наиболее убедительной.
Укажите ваше понимание, я смогу подправить ТОП, если соглашусь.
Не ТОП5, а ТОП1.
Когда нет карт, приходится делать вид, что у тебя есть козыри.

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

Скорее всего исчезает проблема в решении зависимостей от разных версий библиотек
Зависимости можно положить в подкаталог, например.
Если они действительно решили отказаться от зависимостей, то смысла в подкаталоге нет.
В таком случае, нужно было бы окромя сборщика мусора в бинарник добавлять и линковщик.
Вы конечно правы, но это уже детали реализации. Меня заинтересовала реакция сообщества, будто этот подход решает какую-то наболевшую проблему других технологий. Вот я и спрашиваю: «Неужели раньше нельзя было взять с собой зависимости, просто разместив их в подкаталоге?»
Можно, но можно было забыть скопировать ещё один новый подкаталог. Что регулярно происходит при работе с Dockerfile.
Я не утверждаю, это скорее предположение.
Другие языки или создание такой архитектуры подгрузки библиотек была связана с малым количеством оперативной памяти, по этому скорее отдавали предпочтение загрузить либу с версией 1.0 и 1.1 один раз, вместо того что бы таскать за собой зависимости. Это естественно накладывает своих проблем.

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

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

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


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

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

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

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

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


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

Чем именно это проще? Команда копирования как была одна, так и останется.
Чем именно это проще? Команда копирования как была одна, так и останется.
Мы тупые разработчики нового поколения, когда мы одной командой копирования копируем один файл, мы не стрессуем.
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.

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


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

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

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

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

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

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

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

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

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

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

Ну хорошие в Go дефолты — это отлично. Но чтобы это было киллер-фичей технологии...)
У 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 принято (вполне обоснованное) мнение, что при обновлении рантайма один хрен есть смысл пересобрать приложение, а потому, ввиду небольшого размера этого рантайма носят его с собой прямо внутри бинаря.

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


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

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


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

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


На самом деле, дофига чего может поломаться. Даже 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-бинарь), и ровно ее и собирают.
Это сюда каким боком? Использовать технологию неправильно и говорить, что она в этом виновата — это такая себе методика сравнения


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


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

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

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

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

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

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


Один нюанс: билд-сервер воспроизводит боевое/близкое к боевому окружение и в нем собирает пакет бандл, который после тестов деплоится «в бой».
Это уже зависит от того, где гоняются тесты. А так — достаточно, чтобы завёлся подходящий SDK.
Java/C#/OtherVM detected!

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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


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

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


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

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

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


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

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

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


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

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

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

Рантайм рантайм — сборщик мусора, планировщик goroutine, etc. Рантайм.
Все это будет в вашем одном бинарном файле.
«Неужели раньше нельзя было взять с собой зависимости, просто разместив их в подкаталоге?»

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

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

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

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


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

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


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

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

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


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

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


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

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

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

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

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

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

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

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

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

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


Ребят, по большей части:
а) пересобирать бинари — не ваша забота;
б) именно с точки зрения простоты сборки к go меньше всего претензий (все сведется к паре команд), поверьте, это проще, чем C-шный бинарь пересобрать. Прямо в разы.
Вся речь именно о том, что если везде использовать системы статической сборки, то все бинарники станут «навроде AppImage» и пересобирать их, если что — станет заботой админа.
Смотрите, встречается 2 задачи: «хорошо работать в качестве части системы» и «хорошо работать на любой системе». Задачи не то, чтобы ортогональны, они противоположны. И усидеть на двух стульях нельзя.

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

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

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

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

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

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

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

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

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
Я не согласен с вашим категорическим тоном, но согласен с замечанием. Утащил ваше замечание в статью.