Go
Comments 749
+16

Модули же завезли с 1.11 версии. В дальнейшем и от GOPATH хотят избавиться

0

С этим есть нюансы.


  • Некоторые утилиты для go generate и средства работы с кодом для IDE до сих пор в некоторых случаях работают не так хорошо, как с пакетами в GOPATH, а то и вовсе не работают. С этим постепенно справляются, но тем не менее.
  • Не слишком понятно, почему требуется "v" в начале имени тега.
  • Возможно, только у меня, но принудительный вендоринг иногда выкидывает поддиректории из репозиториев, из которых подключается несколько библиотек (например, если из github.com/a/b импортируются /c и /d, в папке vendor может быть github.com/a/b/c, то /d не будет), происходило с gogo-proto

В целом удобно, т.к. гвоздями прибитая структура пакетов не есть хорошо, но GOPATH удобный, когда через go get устанавливаются исполняемые файлы. И поломанные утилиты вроде stringer печалят конечно.

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

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

Конечно можно обойтись одними ошибками (в виде кодов возврата и т.д.), но оверхеда становится очень много.
-21
Когда нормальная работа подсистемы невозможна, то она должна быть отключена. Для этого нужно ее соответствующим образом запрограммировать (в виде процесса или потока). А исключительные ситуации удобны для разработки библиотек, а в обычном коде ошибки надо обрабатывать, а не перебрасывать их «вышестоящему коду».
+10
а в обычном коде ошибки надо обрабатывать

Далеко не все ошибки пришедшие «снизу» могут быть обработаны на данном слое абстракции. Часто их надо пробрасывать наверх.

И в целом подход «если вам не нравится что-то в нашем божественном языке — вы просто неопытный быдлокодер» — он малоконструктивен.
-9
Я по возрасту ближе к Пайку и занимаюсь промышленным программированием более тридцати лет, поэтому разделяю его подход к разработке языка. И я не считаю неопытных программистов быдлокодерами.
+4
Слишком глуп, чтобы придумать вменяемый аргумент? Аппелируй к возрасту!
-1
Глупое предположение. Как можно апеллировать к авторитету в интернете? Ключевое слово «промышленный».
Такой софт живет десятки лет и переживает смену многих программистов. Для такого софта тотальная огороженность и упрощенность Go очень подходит.
+2
Как можно апеллировать к авторитету в интернете?
Не знаю. Вы зачем-то пытаетесь и сразу два раза. Сначала пытаетесь повысить свой авторитет при помощи авторитета Пайка методом сравнения, а потом, зачем-то, аппелируете своим авторитетом. Увы, авторитета у вас нету, вы просто пустослов, потому это выглядит смешно.
+3
Вы сказали две вещи:
1) «Я старый»
2) «Я программирую давно».

Первое — апелляция к возрасту, второе — к авторитету. Ни одного конструктивного аргумента вы не предъявили.
0
Ещё он сказал
3) «Я программирую не какую-то хрень, а именно промышленную хрень»
0
И в go используются и реализованы оба подхода… Ошибки это просто значения а для исключительных ситуаций есть `panic, defer, recover`
+2

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

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

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


Тут ваши слова можно формализовать. Есть некие события, которые должны быть инициированы на уровене вложенности x, а обработаны на уровне вложенности y, где x>y. Если прокидывать эти сообщения явно, через возвращаемые значения, то, чем больше x-y, тем больше лишних действий надо описать явно. Очевидно, что на уровне x и на уровне y код будет осмысленный, а на уровне z, таком, что x>z>y, это будет код "пересылки".


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


Так может, старый добрый препроцессор нас спасет? Спасает же в C? Ну или хитроумные IDE с кодогенерацией, как это принято в Java.


Предложнения по добавлению сахара, появляются чуть ли не каждый день: https://github.com/golang/go/issues/21155 Пока отвергают, но думаю шанса отвертеться нет. Рано или поздно какой-то вариант появится.

0
Очевидно, что на уровне x и на уровне y код будет осмысленный, а на уровне z, таком, что x>z>y, это будет код "пересылки".

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

0

Для этого в принципе нужна хорошая система типов, по типу Хаскеля. Т.е. это ещё более глубокий вопрос.

0
Типа Option не будет, пока не будет generics. Потому что без них он мало осмысленный.
0

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

0
Допустим вы пишете класс, описывающий конфиг приложения. Конфиг должен быть всегда. Что делать, если файл конфига есть, но прочитать его не получатеся?
0

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

0
Вот тут был мой пример из очень старого проектика. Как раз про конфиг.
0
Уронить приложение из-за невозможности прочитать файл? Пользователи скажут спасибо
0
Паника != уронить. Паника — это штатный механизм, который можно обработать.
И что вы предлагаете делать, если конфиг не читается? Загружать пустой дефолтный и пытаться работать? Ну вариант конечно, но на мой взгляд совсем не очень. Я бы лично в такой ситуации выкидывал панику с записью в лог (или консоль если это консольное приложение).
+1
Паника != уронить

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

Что касается вопроса «что же я предлагаю», я ответил чуть ниже: habr.com/post/434446/#comment_19557444
0
Нет, «уронить» — это сделать так, чтобы программа упала, совсем (ну, как грубый пример, преднамеренно написать код так, чтобы программа убивалась OOM killer в определенный момент).
Паника — это что-то вроде глобального исключения, которое можно поймать и обработать. Штатный механизм для исключительных ситуаций.

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

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

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


И что же может сделать вызывающий код, в обсуждаемом случае? Выкинуть панику? :)

Например сделать MessageBox::show(error.GetText()). Или взять конфиг по-умолчанию. Или еще что-нибудь. Вызывающий код знает, что с этим сделать. А может запаниковать, да. Он пусть решает. Хорошая статья в тему.

0
> Панику нельзя обработать кроме как залоггировать и упасть, о чем я уже говорил. Попытка продолжить выполнение после паники — UB.

When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates.

Recover is useful only when called inside deferred functions. Executing a call to recover inside a deferred function stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic. If recover is called outside the deferred function, it will not stop a panicking sequence.


> Например сделать MessageBox::show(error.GetText()). Или взять конфиг по-умолчанию.

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

0

И все же: у вас остается программа в неконсистентном состоянии. Если у нас был код типа


db.Update(reading: true);
var file = ReadFromFile();
db.Update(reading: false, filename: file.name);

Если ReadFromFile() паникует, мы не выполняем последнюю строчку и остаемся в неконсистентном состоянии. Если же выполняем, то чему будет равен file.name?


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

библиотека, работающая с JSON, которая имеет зависимость на GTK чтобы показывать сообщение об ошибке в мессадж боксе? Ну даже не знаю, что про это можно сказать :)


Заполняем там же структуру дефолтными значениями например.

Ну это и есть "молчаливое" поведение, о чем в статье выше расписано. Вы читали?


Если это рекомендуемое поведение го, то спасибо, не надо такого.

0
> Если у нас был код типа

А зачем так делать? О_О
Не нужно так делать никогда, запись в базу должна быть атомарной, а не размазанной вот так.
Прочитали файл — пишем в базу. Не прочитали — не пишем вообще ничего.

> библиотека, работающая с JSON, которая имеет зависимость на GTK чтобы показывать сообщение об ошибке в мессадж боксе? Ну даже не знаю, что про это можно сказать :)

Ну всяких примеров можно много разных насочинять. Я про «действовать по ситуации», по-моему, пару раз уже тут говорил.
0
Деньги упали на счет клиента — пишем в базу.
Не упали — не пишем ничего.

Правильно я вас понял?
0
Что ответить на ваш вопрос, опишите сначала полностью весь кейс.
Если вам нужно записать что-то из файла в базу, то очевидно что вам нужно прежде всего открыть и прочитать этот файл перед записью, а не в процессе.
0
А вот в этом и дело: нету всего кейса. Вы пишете библиотеку, и про весь кейс будут знать только её пользователи.

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

Ну если нету кейса (== нет ТЗ, как минимум в голове разработчика), то о какой разработке может идти речь?
Чет мне сейчас тот анекдот про японскую пилу и суровых сибирских лесорубов вспомнился.
+1

Нету всего кейса. Неизвестно, кто, откуда и в каком контексте будет вас дёргать.


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


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

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

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

Возвращаясь в контекст го — вот типовой пример — golang.org/pkg/net/http/#Server — инициализируем структурой с настройками. Если что-то не так — то ListenAndServe вернет ошибку, и типовая сферическая инициализация выглядит как-то так:

s := &http.Server{
	Addr:           ":8080",
	Handler:        myHandler,
	ReadTimeout:    10 * time.Second,
	WriteTimeout:   10 * time.Second,
	MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())


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

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

0
Ну вот у вас есть «работал в двух режимах: онлайн и replay» — на мой взгляд, вполне достаточные требования, от которых можно дальше что-то конкретное делать, а не изобретать универсальный обработчик всего.
А функция, которая загружает данные и не должна по идее знать о том, как и где эти данные используются. Смогла загрузить — отдала вызывающему. Не смогла — отдала код ошибки. А вызывающий уже сам решает что со всем этим делать. Если мне память не изменяет, это один из паттернов проектирования.
0

Ну, да, всё так. Только вместо кода ошибки Either, но не суть.


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

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

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

0
Эм, вообще-то знаю :)
Например в случае конфига какого-нибудь сервиса — надо падать, если мы не можем его получить или он кривой. Потому, что без конфига или с кривым конфигом работать этому сервису будет как-то сложно.
Или же мы можем навернуть тут абстракцию, и сначала попытаться загрузить конфиг с локального ресурса, потом посмотреть, например, в какой-нибудь etcd, или еще что-то такое. Но в любом случае, конфиг нам нужен.

Другой пример — ну скажем, условный s3cmd. Мы можем посмотреть на типовые локации конфига, и если там ничего нет — используем дефолт. Есть критические опции, без которых запуск утилиты не имеет смысла. Если их нет — тогда падаем с соотв. ошибкой.

0
Только это не конфиг, это данные. И, опять же, на уровне функции, которая получает, условно, путь к данным и возвращает какой-то массив с ними, вы решить это не можете.
0
Мы же вроде весь тред про конфиги начинали? Ну не суть.
На уровне этой функции нам и не нужно что-то решать и вообще знать что делать в случае ошибки (пытаться загрузить альтернативные данные, пытаться загрузить данные через минуту или забить). Она вызывается с путем до данных, пытается их по этому пути получить, и возвращает результаты. Все. Вся остальная логика в вызывающем коде. Можно еще навернуть там таймауты и попытки повторного запроса, но это уже опционально.
-1
А зачем так делать? О_О
Не нужно так делать никогда, запись в базу должна быть атомарной, а не размазанной вот так.

Ну а как тут сделаешь атомарной, если там паника. Даже если какой-нибудь try finally блок делать, он по идее не выполнится.

Прочитали файл — пишем в базу. Не прочитали — не пишем вообще ничего.

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

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

Ну я вот спрашиваю про конкретную ситуацию, что делать предлагается. Ситуацию вы вполне знаете, требования простые: чтобы я мог прочитать конфиг, и сделать кто-то, если с конфигом что-то не в порядке.
-1
> Ну а как тут сделаешь атомарной, если там паника. Даже если какой-нибудь try finally блок делать, он по идее не выполнится.

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

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

Какой стейт и зачем? Опишите кейс более подробно.

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

Я бы сделал именно так как уже говорил — ошибка в лог/консоль и аварийное завершение работы. Плюс в CLI еще, возможно, добавил бы опцию для генерации дефолтного конфига. Но запуск с нечитаемым/испорченным конфигом — это аварийная ситуация.
В ситуации чисто десктопного GUI приложения — да, выкидывать окошко с ошибкой. Тут уже можно поиграть на тему независимого error helper'а, запускаемого при аварийном завершении основной программы (или на тему засылки во что-то, что само покажет окошко с мессагой, ну dbus например).
0
Открыли файл, прочитали его (или его блок если файл большой), записали в базу.
Если хреново открыли/прочитали, записи в базу не будет.
Ну в самом деле, это такие элементарные вещи, что как-то даже странно не знать их.

Как не будет? Мы должны до записи сказать типа «мы начали читать файл», а после сказать «мы больше не читаем файл».

Какой стейт и зачем? Опишите кейс более подробно.

В данном случае БД. Например, у нас несколько инстансов, и если один инстанс начал читать файл, то он выставляет флажок, чтобы другие инстансы понимали, что файл читается, и не трогали его.

Я бы сделал именно так как уже говорил — ошибка в лог/консоль и аварийное завершение работы.

Я не хочу завершение работы, я хочу обработать ситуацию. Но это видимо не go-way?
0
> Как не будет? Мы должны до записи сказать типа «мы начали читать файл», а после сказать «мы больше не читаем файл».

И почему это может вызвать проблемы?
Ставим флажок «я начал читать файл». Открываем — не открывается — по деферу сбрасываем флажок. В базу ничего не попадает.

> В данном случае БД. Например, у нас несколько инстансов, и если один инстанс начал читать файл, то он выставляет флажок, чтобы другие инстансы понимали, что файл читается, и не трогали его.

Посмотрите вот тут — github.com/minio/minio/blob/master/docs/shared-backend/DESIGN.md#architecture — очень неплохой пример как надо делать такие вещи.

> Я не хочу завершение работы, я хочу обработать ситуацию. Но это видимо не go-way?

Я вас где-то заставляю делать так? Делайте как вам нужно.
0
И почему это может вызвать проблемы?
Ставим флажок «я начал читать файл». Открываем — не открывается" по деферу сбрасываем флажок. В базу ничего не попадает.

Дефер будет вызван в случае паники?

0
Ок, а что будет в случае другой паники во время обработки этого дефера?
0

Вторая паника будет добавлена к описанию первой, а в остальном никакой разницы, одна паника или две: https://play.golang.org/p/Of3KiMh4Tdg — пока не вызвали recover() паника продолжит раскручивать стек и выполнять defer-ы.

0

Да, конечно, defer вызывается при любом выходе из функции, хоть return хоть panic.

0
Как не будет? Мы должны до записи сказать типа «мы начали читать файл», а после сказать «мы больше не читаем файл».


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

В данном случае БД. Например, у нас несколько инстансов, и если один инстанс начал читать файл, то он выставляет флажок, чтобы другие инстансы понимали, что файл читается, и не трогали его.


Чуть раньше было про конфиг, ага. Ну да ладно, Г-дь вам судья.

Понимаете, тут все несколько проще: один инстанс открывает файл на чтение и… никаких флажков, оно блокируется на уровне ФС, ресурс занят. Читает файл, закрывает файл (освобождает ресурс). Все.

Я не хочу завершение работы, я хочу обработать ситуацию. Но это видимо не go-way?


В случае с поломанным конфигом хотеть вы можете много чего, а нужно вам всегда тупо налить логов и упасть.

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

В случае с поломанным конфигом хотеть вы можете много чего, а нужно вам всегда тупо налить логов и упасть.

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


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

Никакого запрета на проброс вверх там нет.

if err := someFuncCall(); err != nil {
    return err
}


Вот это совершенно идиоматичный Go-код. Так выглядит «проброс наверх».

Замените `return err` на обработку ошибки, и это будет тоже идиоматичный Go-код (не рекомендованный, к слову, в библиотечных пакетах).

Хотите впрямую заигнорить, `_` вам в помощь.

Единственная фишка языка в плане «должен решать на месте» — это необходимость явно указать, что вы хотите с ошибкой сделать. Нельзя в сигнатуре main написать `throws Exception` и положить на обработку ошибок как таковую.

И вот из-за этого весь сыр-бор.

и есть прокидывание ошибки, которое оказывается не go-way.


Поверьте, совершенно нормальный Go-way, прямо идиоматичный даже.

Единственная реальная трабла обработки ошибок — это многословность. Но это решается, оно уже в бете.
0

Тогда я согласен в сами, и не согласен с автором исходного утверждения (а транзитивно — вы тоже с ним не согласны).

В таком случае подождем его ответа, либо обсуждение исчерпано.

Спасибо за диалог.
0
Если ReadFromFile() паникует, мы не выполняем последнюю строчку и остаемся в неконсистентном состоянии. Если же выполняем, то чему будет равен file.name?


В случае, если ошибка с чтением произошла на этапе чтения конфига, очевидно, надо срать в лог и крашиться.
0
Зависит от того, насколько критичен конфиг. Eсли не можете загрузить конфиг, можно использовать дефолтный конфиг и WARN в лог или stderr вывести. Если конфиг критичен — то остановить старт.
+1
Ну представим, что вы пишете библиотеку, и не знаете, насколько критично отсутствие файла.
0
Библиотека не должна принимать как параметр файл с конфигурацией.

Когда я пишу какой-нибудь сервис, который имеет конфигурационный файл, я делаю следующее:
1. Создаю дефолтный конфиг.
2. Если конфиг файл не был определен — использую дефолтный конфиг.
3. Если конфиг файл определен — читаю конфиг файл и переписываю дефолтный конфиг (тот, что был создан сначала).
4. Если, когда конфиг файл определен, но что-то идет не так — файла нету, он не читаемый или конфиг не валидный — останавливаю сервис. Тогда упадет только один инстанс, а все другие будут раниться с правильной конфигурацией (пропагация деплоймента остановится).
0
Библиотека не должна принимать как параметр файл с конфигурацией.

Ну как это не должна? log4rs, Microsoft.Extensions.Configuration.Json, cfg4j и все прочее…


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

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

0
Ну как это не должна? log4rs, Microsoft.Extensions.Configuration.Json, cfg4j и все прочее

Ах интерпрайз.
IMO, библиотека должна работать с конфигурационным объектом, а не файлом.

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

Я наверное не понял контекст, ну не падайте раз так, WARNING в лог тогда. Для юзер приложений это может и лучше, для backend — лучше падать.
+1
Ах интерпрайз.
IMO, библиотека должна работать с конфигурационным объектом, а не файлом.

А объект откуда возмьется?

Я наверное не понял контекст, ну не падайте раз так, WARNING в лог тогда. Для юзер приложений это может и лучше, для backend — лучше падать.

Ну мое мнение, что нужно прокидывать наверх до тех пор, пока не встретится код, который знает, что делать с ошибкой. А наше обсуждение различных костылей как раз и показывает, что попытка вместо этого порешать всё «на месте» имеет те или иные недостатки.
+1
А объект откуда возмьется?


С конфиг файла, но тут уже вы решите что делать с нечитаемым файлом или его отсутствием, а не полагаться на разработчика библиотеки (вернет ли он ошибку или панику?).
Кстати, вот тут ваше мнение расходиться со второй репликой (про прокидование на верх).
Второе, чем больше библиотек, тем больше конфиг файлов? А потом еще разный формат — один хочет yaml, второй toml, третий ini.
0
С конфиг файла, но тут уже вы решите что делать с нечитаемым файлом или его отсутствием, а не полагаться на разработчика библиотеки (вернет ли он ошибку или панику?).

Если библиотека бросает панику — то надо руки такому разработчику вынимать.


Кстати, вот тут ваше мнение расходиться со второй репликой (про прокидование на верх).

Не расходится. В идеале паник не должно быть вообще нигде, кроме main. Либо делаем что-то, либо прокидываем ошибку выше, всё. Кодекс самурая я линковал.


Второе, чем больше библиотек, тем больше конфиг файлов? А потом еще разный формат — один хочет yaml, второй toml, третий ini.

Поэтому делайтся генерик — интерфейс (например, в случае выше это Microsoft.Extensions.Configuration), а потом делаются разные реализации (Microsoft.Extensions.Configuration.Json, Microsoft.Extensions.Configuration.Xml, Microsoft.Extensions.Configuration.Toml, ...)

0
Поэтому делайтся генерик — интерфейс (например, в случае выше это Microsoft.Extensions.Configuration), а потом делаются разные реализации (Microsoft.Extensions.Configuration.Json, Microsoft.Extensions.Configuration.Xml, Microsoft.Extensions.Configuration.Toml, ...)


Выглядит как какая-то жесть и попытка «впихуть невпихуемое».
0

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


Даже если в 99% случаев использования данная библиотека действительно должна тупо читать свой конфиг из файла /etc/библиотека.yml, то она должна предоставить в своём публичном API вспомогательную функцию DefaultConfig(), которая считает этот файл и вернёт значение, подходящее для передачи DI-параметром основному конструктору/инициализатору этой библиотеки. В этом случае эти 99% кода будут содержать что-то вроде:


something := some.New(some.DefaultConfig(), mylog, mydb)
0
Ну хорошо, так и есть. Теперь мы ей передали строковую константу, файл такой есть, а она его открыть не может. что дальше эта библиотека должна «на месте» решить, не прокидывая ошибку вверх?
0

Вы не поняли, DefaultConfig возвращает не имя файла с конфигом, а значение содержащее сам конфиг. Откуда это значение получено — из файла, или из другого места — библиотеке дела нет. Использовать DefaultConfig или что-то другое — решает вызывающий код (т.е. main, которые единственный знает текущий контекст выполнения, откуда брать конфиги, куда писать логи, и как подключиться к БД).


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


Например, если для библиотеки является штатной ситуацией работа без конфига (точнее, с использованием дефолтного конфига прошитого в самой библиотеке), и отсутствие в системе /etc/библиотека.yml это типичная ситуация, то DefaultConfig() может вообще игнорировать все ошибки и возвращать nil, который библиотека трактует как "взять дефолтный конфиг". Как по мне — это немного грязно, DefaultConfig стоит хотя бы вывести в лог все ошибки кроме отсутствия файла.


Но в более типичном/общем случае это делается немного иначе, просто не в одну строку:


someConfig, err := some.Defaultconfig()
if err != nil {
    // мы в main(), тут log.Fatal можно
    log.Fatalf("failed to load config: %s", err)
}
someThing := some.New(someConfig, log, db)
0

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


let config = Config::default();
let config = match config.merge(Config::from_file("some/path/config.json")) {
   Ok(config) => config,
   Err(ParsingError(e)) => config.merge({timeout: 2000}),
   Err(e) => panic!("Unexpected error {:?}", e);
}

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


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


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

Какую строку Вы назвали "последней"? Последняя создаёт someThing, но дальше Вы пишете опять про конфиг, так что я несколько запутался. Если Вы говорили таки про строку с someThing, то там никто не падает, вызов some.New() в этом примере не может завершиться с ошибкой — это довольно типично для простых конструкторов. Если бы там могла возникнуть ошибка (напр. если он как-то проверяет переданный конфиг на валидность), то он просто вернул бы someThing, err.


если не смогли, но произошла ошибка парсинга, то взять дефолтный конфиг

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


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

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

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

Только библиотека знает, валидный ли файл конфига или нет. Как вы это сделаете «еще до инициализации библиотеки»?
0
package main

import "libA"

type config struct {
    LibA libA.Config
    ...
}

func main() {
   var cfg config
   if err := c.loadConfig(*configFile); err != nil {
       log.Fatal(err)
    }
    ...
} 

А loadConfig уже парсит конфиг файл и валидирует его в соответствии со структурой.

0
Только библиотека знает, валидный ли файл конфига или нет. Как вы это сделаете «еще до инициализации библиотеки»?

Вы мой пример кода понять пытались вообще? Похоже, что нет. Ладно, попробую объяснить на пальцах, без кода:


  1. Дано: есть некая библиотека, достаточно сложная, чтобы ей мог понадобиться собственный конфиг-файл. Тем не менее, хочется избежать создания жёсткой связи между библиотекой, которая может оказаться частью любого приложения выполняющегося в любых условиях, и конкретным конфиг-файлом на диске.
  2. Чтобы разорвать эту связь, мы модифицируем функцию-конструктор/инициализатор библиотеки так, чтобы она принимала параметром структуру, описывающую её конфигурацию, вместо того, чтобы самостоятельно считывать и парсить конкретный конфиг-файл. Таким образом, ответственность за считывание и парсинг конфиг-файла переносится на вызывающий код, обычно это main() конкретного приложения, знающего условия в котором оно выполняется, и решающего, откуда и как получить конфигурацию для этой библиотеки.
  3. Тем не менее, предполагая, что большинству приложений подойдёт подход по умолчанию, заключающийся в считывании конфигурации из конкретного файла, мы добавляем в библиотеку отдельную, опциональную, независимую вспомогательную функцию, которая умеет исключительно считать этот конфиг-файл и вернуть структуру с конфигурацией, необходимую для инициализации библиотеки.
  4. В результате, main() имеет возможность проинициализировать библиотеку в два шага: сначала вызвав вспомогательную функцию для считывания конфиг-файла, и потом передав структуру с конфигурацией в функцию инициализирующую библиотеку. На обоих шагах могут возникнуть ошибки, которые main() может обработать по своему разумению.

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

0
Дано: есть некая библиотека, достаточно сложная, чтобы ей мог понадобиться собственный конфиг-файл.

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

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

Соответственно, всё остальное уже следствие из неверной посылки.
0
К конфигам чего, простите, предоставляет доступ сторонняя библиотека?
0
Ну вы же вот прям у себя в коде с match обработали ошибку.
Я могу точно такой же код на Go написать.
0

Ну так, смысл в том, что у нас есть наш app, и зависимый пакет config. Так вот, ошибка происходит в последнем, и у него есть два способа:


  1. Пробросить ошибку в app, который соответственно по ней сделает матч и примет какое-то решение
  2. Решит проблему "на месте", и сделает что-то друоге: вернет конфиг по-умолчанию, вернет nil, ко всему этому напишет в лог, запаникует и т.п.

Я всегда считал, что правильный вариант — 1, но как выше говорилось, "go way" в том, чтобы следовать варианту 2 в той или иной форме.

0
А зачем вам отдельный пакет config? Очень часто это просто ненужно.
Опять же, как я делаю:
1. В текущем пакет есть структура `Config`.
2. Структура `Config` содержит конфигурацию все других пакетов.
3. Все обработка конфиг файла в пакете `main`.

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

Затем, чтобы не писать тыщи строк загрузки/валидации/парсинга/опциональных значчений/автоподгрузки изменений с разных файловых систем/черта в ступе, а иметь всё из коробки…

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

Ну как не должна. Как вы, например, сделаете, что у вас есть сервис (MVC контроллер, к примеру), который берет параметр из конфига, и при изменении конфига в ФС через DI автоматически прокинуть имзенения в контроллер, чтобы последующие вызовы его методов использовали уже новый конфиг?
0

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

0
А кто будет писать все эти «самообновления»? В моем случае, за меня уже написал майкрософт, надо просто подключить библиотеку, и дать ей файлик. А на выходе я получу распаршеное значение, которое умеет во все эти самообновления.
0
Кхм, поищите написанный «самообновления» под Go. Если это действительно часто бывает нужно (на самом деле, нет, конечно), то наверняка есть пара проектов на github'е, которые это умеют.
0
Затем, чтобы не писать тыщи строк загрузки/валидации/парсинга/опциональных значчений/автоподгрузки изменений с разных файловых систем/черта в ступе, а иметь всё из коробки…


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

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

Ну как не должна. Как вы, например, сделаете, что у вас есть сервис (MVC контроллер, к примеру), который берет параметр из конфига, и при изменении конфига в ФС через DI автоматически прокинуть имзенения в контроллер, чтобы последующие вызовы его методов использовали уже новый конфиг?


Ну, во-первых, вы сейчас какую-то жесть описали.

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

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


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


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

Вы неправильно поняли. Не библиотеке нужен конфиг, а приложению. Библиотека является тем, что парсит конфиг, и умеет во все умные штуки.

0
Это достаточно базовй функционал, для библиотеки работы с конфигами.


Вот я сейчас одного не понимаю, зачем вам библиотека для работы с конфигами. Применения я вижу, собственно, 2:
1. У вас 100500 приложений, работающих с одной и той же конфигурацией (отличная иллюстрация к книге «Первый опыт в программировании: как делать не надо»)
2. Я боюсь даже представить, в каком виде хранится ваш конфиг, что для того, чтобы его распарсить, нужно что-то уровня SOAP2 (это в другую книжку: «Лучшие практики энтерпрайза: 10 самых оверинжениренных хреней в истории человечества»).

Но я не собираюсь руками json'ы парсить


configData, err := ioutil.ReadFile("/path/to/config")
if err != nil {
    log.Fatal(err)
}
var config map[string]interface{}
config, err := json.Unmarshall(configData, &config)
if err != nil {
    log.Fatal(err)
}


Вот это вы собираетесь в отдельную библиотеку выносить? Простите, leftpad'ом запахло.

Или вы туда еще 5 строчек работы с fs.notify принесете? Ну, как бы, да, так существование библиотеки будет более осмысленным (на самом деле нет).

Или вы хотите обработку стейта вашего приложения в зависимости от нового конфига в рантайме в библиотеку унести? А нафига, собственно, если эта библиотека будет нужна ровно одному приложению (если, конечно, у вас не 100500 совершенно одинаковых приложений)?

Я отдам файл библиотеке, которая вернет мне объект, который всё это умеет.


Реализация всего этого удовольствия займет у вас строк 50… Реально в библиотеку понесем? Отраслевым стандартом объявим, поместим его рядом с остальными 14-ю?

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


Понимаете, приложениям бывает нужен конфиг, тут да. Но трабла в том, что каждому приложению, как правило, нужен свой конфиг (на то они и разные приложения). А писать отдельную библиотеку, которая будет отдавать конфиг для одного приложения — а нахрена она за пределами этого приложения нужна?
0
Есть такая гошная библиотека — github.com/spf13/viper

Так вот там все — парсинг всех форматов, перезагрузка конфига при изменении и тд.
Я ее как-то выпилил из одного сервиса — нам нужен был просто парсер yaml и все, так размер бинарника уменьшился с 10 MB до 4MB.
0
Выглядит как отличная библиотека. Я бы заиспользовал. О чем-то таком я и говорил, да.

Я ее как-то выпилил из одного сервиса — нам нужен был просто парсер yaml и все, так размер бинарника уменьшился с 10 MB до 4MB.

Ну тут вариант два: либо вы изначально взяли не тот инструмент и заменили его на правильный, и это хорошо, либо вы реализовали свой ограниченный и скорее всего нестабильный вариант подмножества этой библиотеки, и тогда это уже не очень. В последнем случае должен спасать LTO.
+1
Выглядит как отличная библиотека. Я бы заиспользовал. О чем-то таком я и говорил, да

У нас с вами явно разные предпочтения.

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

Это был сервис, который достался мне в наследство.
`viper` использует `gopkg.in/yaml.v2` для парсинга yaml файлов. Я просто взял этот парсер и сам распарсил конфиг файл в структуру (вот как выше qrKot показал, 4 строки). Что тут может быть нестабильного? Потому что мне не нужно > 20 транзитивных зависимостей.
0
У нас с вами явно разные предпочтения.

DRY во все поля. Стараюсь соблюдать. Если есть библиотека для Х, не надо писать Х руками. Даже если это падлефт.
0
Тянуть 51 зависимость ради 5 строк кода — замечательно.
0
Если есть библиотека для Х, не надо писать Х руками. Даже если это падлефт.

А вот это действительно антипаттерн в Go.


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


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

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

Свои строки, свои конфиги, своё всё… Где-то такой паттерн я уже встречал…

Каким таким образом го убирает все ошибки тоже непонятно. Больше похоже на то, что автор забьет на обработку граничных случаев (что сократит код раз в 10), а программисты на проекте которые нарушат представления автора о том, как все это должно работать окажутся сами виноваты, потому что «не надо это так использовать».

Есть языки, в которых этот X написать самостоятельно — только нарываться на лишние проблемы.

Любой home made код хуже, чем проестированный, проверенный временем специализированный код библиотеки. Мне как пользователю пофиг, сколько там зависимостей, все равно LTO вырежет все, что не используется. А вот надежность, и сэкономленное время сильно важнее.

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

Мимо, никто в Go не делает свои строки, очень много есть в стандартной библиотеке, свой парсер yaml/ini тоже никто в своём уме писать не станет. Но и ради leftpad тоже никто стороннюю библиотеку подключать не будет.


Любой home made код хуже, чем проестированный, проверенный временем специализированный код библиотеки.

Это не так. Опенсорсные библиотеки (а мы ведь о них говорим, да?) в основной массе и есть типичный home made код. Причём, как правило, более универсальный, функциональный и сложный, чем нужно для текущего проекта, что зачастую с лихвой перекрывает "протестированность и проверенность временем" и приводит к тому, что работает это менее надёжно, чем своя минималистичная реализация.


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


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


В качестве конкретного примера, в моём текущем проекте (50k строк) подключены 22 библиотеки, из них ровно половина это либо наши собственные опенсорсные, либо дополнения стандартной библиотеки Go (из golang.org/x/…). Из оставшихся 11 часть это (относительно) официальные библиотеки (драйвер DB, SDK сервисов вроде AWS, Stripe, etc.), без них остаётся 6, из которых только одна достаточно тривиальная, без которой можно было бы обойтись. При этом в этих 50к строк есть ровно один настоящий велосипед на 1600 строк (по ряду причин пришлось сделать свою очередь сообщений, и перед тем как решиться писать велосипед я честно неделю старался заюзать существующий NATS Streaming Server, и до сих пор не рад, что пришлось таки написать этот велосипед).


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

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

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

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

Есть, но она копеечная. Если про тот же дотнет мы говорим, то на время компиляции зависимости вообще не влияют, а добавление того же Configuration добавляет чуть больше 100КБ на диске.

image

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


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

Действительно ли это такая страшная цена за качественное, проверенное решение?


Вы не то в цену добавляете. Цена — это не место на диске, и не время компиляции. Цена — это зависимость вашего кода от движения левой пятки стороннего разработчика. В случае импортов из стандартной библиотеки или из x, особых проблем нет — это поддерживается. В случае leftpad'ов — это всегда плохая идея.
0

Цена библиотек вовсе не в килобайтах и времени компиляции. Если разрабатывается прототип — цена библиотек нулевая. Но в нормальных проектах каждая зависимость создаёт дополнительные риски и увеличивает нагрузку на поддержку проекта.


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


Помимо этого использование избыточно функциональных библиотек нередко приводит к трате лишних ресурсов на выполнение ненужного этому проекту кода, что отрицательно сказывается на скорости его работы и требованиям к ресурсам (RAM/CPU). Ещё одна проблема связана с тем, что быстро подключив навороченную библиотеку мы нередко добавляем в проект "бесплатные" фичи — то, что проекту не нужно, но что мы получили вместе с библиотекой (возвращаясь к примеру с конфиг-файлом такой фичей является обновление конфигурации при изменении файла на лету), что тоже удорожает поддержку (не нужные, но реализованные фичи приходится поддерживать) и в целом ведёт проект в сторону bloatware.


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

0
Библиотеки надо отслеживать (как минимум на предмет выхода новых версий, исправляющих проблемы с безопасностью)

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

их надо обновлять (что нередко требует внесения изменений в свой код)

На куче проектов, что я видел, использовались версии библиотек на 2-3 мажорных версии меньше последнего стейбла. Потому что обычно разнца только в добавлении новых фич, которые не нужны особо, т.к. затраты на обновления кода и риски развала проекта перевешивают. Собственно, любое обновление библиотек всегда рассматривается с точки зрения стоимости переписывания/вероятности развала проекта в уже работающих сценариях. Безопасность библиотек вообще никак не рассматривается, ибо в них и уязвимостей-то нет. Если не верите, самый популярный пакет — Json.Net, попробуйте в нем найти релиз, который исправляет какую-то проблему с безопасностью. Если найдете — то наверное нужно признать, что сообщество просто более безответственное.

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

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

если возникают проблемы (а они всегда возникают, даже с отлаженными за 10 лет библиотеками)

Да нет, с большими библиотеками проблем не возникает. Упомянутый Json.Net решает все сценарии. Бывает, не хватает фич, но фича != баг, и система расширения библиотеки обычно предусматривает возможность приткнуть свои классы для добавления этой функциональности.

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

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

Помимо этого использование избыточно функциональных библиотек нередко приводит к трате лишних ресурсов на выполнение ненужного этому проекту кода, что отрицательно сказывается на скорости его работы и требованиям к ресурсам (RAM/CPU).

Ну это уже странные штуки пошли. Библиотека обычно ничего такого страшного не делает, если это не ORM с километровыми запросами. Хотя там, где важны каждые 1-2% производительности дотнет обычно не используют, мб в Го разница между 98 и 100 мс на ответ является клиентообразующей.

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

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


Видимо, не следите.

CVE самого дотнета выходят как патчи на винду


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

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


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

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


Т.е. принят подход «работает — не трожь». Вероятно, и убежденность в том, что фиксов уязвимостей под .net-библиотеки нет, растет оттуда же? «риски развала проекта» важнее, чем отсутствие уязвимостей в коде… Ну, ок…

Если не верите, самый популярный пакет — Json.Net


По какому, собственно, критерию, самый популярный? Самый популярный лично у вас?

Не поленился, сходил на NuGet (я же в правильном месте ищу?). В выдаче первые 3: NUnit, Newtonsoft.JSON, EntityFramework…

А вот тут про уязвимость

И тут.

Цитируем вас: «нужно признать, что сообщество просто более безответственное. „

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


Отличное решение, такЪ победимЪ.

Да нет, с большими библиотеками проблем не возникает.


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

Хотя там, где важны каждые 1-2% производительности дотнет обычно не используют, мб в Го разница между 98 и 100 мс на ответ является клиентообразующей.


Ну, допустим, не 1-2% “зазора производительности» для дотнета нормальными считаются, а, скорее, 15-25%. Там, где важны 1-2%, юзают C. А Go ценят больше за утилизацию ресурсов.

Это наоборот плюс же.


В чем, простите, плюс?

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


С тем же успехом используемые фичи могут начать вести себя иначе, и ваш код ничего не узнает.
0
Не поленился, сходил на NuGet (я же в правильном месте ищу?). В выдаче первые 3: NUnit, Newtonsoft.JSON, EntityFramework…

А вот тут про уязвимость

И тут.

Цитируем вас: «нужно признать, что сообщество просто более безответственное. „

Благодарю, любопытная информация.

Отличное решение, такЪ победимЪ.

Да нет, не очень хорошо, но так вот есть.

В чем, простите, плюс?

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

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

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


Они «занимают место» — это, зачастую, малозначимо, но оно таки минус.

«Плюс» того, что «этим можно воспользоваться»… Кхм, без пересборки вашего приложения — нет, нельзя. Т.е. не плюс.
0

По поводу безопасности Вам уже ответили с конкретными примерами, но вообще-то просто здравый смысл должен был подсказать, что проблемы с безопасностью регулярно обнаруживают в коде, любом коде, и библиотеки не являются и не могут являться исключением. Иллюзия того, что в библиотеках всё хорошо, чаще связана с тем, что авторы большинства опенсорс библиотек не напрягаются делать формальные уведомления "ааа, мы закрыли дыру, срочно обновляйтесь", многие даже changelog не ведут или пишут туда просто "fixed something". И это — ещё одна причина регулярно обновляться, даже если никто не бегает и не кричит "волки!".


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

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


Библиотека обычно ничего такого страшного не делает

Речь о том, что в библиотеках код более общего назначения, который содержит избыточный для вашего проекта функционал. Это значит, что для реализации этого избыточного функционала библиотека выделяет больше памяти для хранения данных нужных этому функционалу, выполняет больше логики в коде для поддержки этого функционала, и, в результате, жрёт больше ресурсов и работает медленнее. Насколько — зависит от конкретной библиотеки. Для многих библиотек это может быть не критично, но полно примеров проектов, в которых накопилось очень много такого "избыточного" кода, и которые дико тормозят и/или требуют дикое количество ресурсов, хотя сами не делают ничего достаточно сложного, чтобы оправдать это. На практике эта цена становится заметна либо при подключении ненужных тяжёлых библиотек вроде упомянутого Вами ORM, либо при бездумном подключении библиотек на каждый чих (что и является темой текущего обсуждения).


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

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

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

То есть, свои библиотеки вы пишете сразу заведомо безопасными с полностью future-proof API?


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


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


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

0
То есть, свои библиотеки вы пишете сразу заведомо безопасными с полностью future-proof API?

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

0
Ну, а я предпочитаю ленивые вычисления: если библиотека вот прямо сейчас решает мою проблему, и решает её существенно эффективнее, чем решал бы её я сам (включая время на написание кода), то воспользоваться этой библиотекой будет заведомо эффективнее, чем реализовывать то же самое с нуля самому. А как она там API меняет или что-то ещё — посмотрим.

Поэтому даже если мне нужно что-то очень простое, вроде разделения строки по сепаратору, я возьму boost в плюсах или специальную библиотеку split в хаскеле.
0
тут недавно был случай внедрения вредоносного кода в зависимость очень популярной js библиотеки.
0

Это не проблема философии управления зависимостями.


Но не уверен, что надо в очередной раз начинать про чистые функции, монаду IO и вот это вот всё.

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

Типичный strawman.


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


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

+1
Вот это вы собираетесь в отдельную библиотеку выносить?

А где валидация? Где обработка отсутствующих случаев? Где переопределение аргументов с командной строки? Где генерация хелпа?


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

Поэтому есть вещи вроде таких.

0
А где валидация?


В типах.

Где обработка отсутствующих случаев?


В инициализации.

Где переопределение аргументов с командной строки?


Нигде. Аргументы командной строки ортогональны конфиг-файлу.

Где генерация хелпа?


В сторонних генераторах хелпов?

Поэтому есть вещи вроде таких.


Каким боком, простите, парсилка аргументов командной строки относится к хот-релоаду конфиг-файла?
0
В типах.

Это в этих типах валидация?


var config map[string]interface{}

Чё-т проиграл прямо даже.


В инициализации.

Можете носом ткнуть? Я её не вижу.


Каким боком, простите, парсилка аргументов командной строки относится к хот-релоаду конфиг-файла?

А хот-релоад мне самому не нравится, но это другая история.

0
Это в этих типах валидация?


Нет, вот в этих:

type Config struct {
   DebugMode          bool
   HotReloadEnabled bool
   MaxWorkUnits       int
}


и в подобных им.

Можете носом ткнуть? Я её не вижу


Рекомендуемый подход:

func NewConfig() *Config {
    var conf Config
    conf.DebugMode = false
    ... тут заполняем дефолты ...
    return &conf
}


0
Я что-то этого всего не увидел в вашем исходном предложении, о котором вы спросили, стоит ли это выносить в библиотеку.
0
Библиотека парсера JSON уже есть. И она умеет работать со структурами.

Паниками она не кидается, как и любая порядочная библиотека. Она либо возвращает корректную структуру, либо возвращает ошибку.

Какая, собственно, вам еще библиотека «работы с конфигурациями» нужна?

Собственно «обрабатывать ошибки на месте» — это вообще не про «молча обработать ошибку и как-то замести проблему под ковер». Обработка на месте а) предполагается в коде приложения (3rd-party библиотека как раз ошибку должна возвращать), б) означает необходимость совершить хотя бы какие-то действия с полученной приложением ошибкой (на крайний случай явно ее проигнорировать). Вот к такой обработке и подталкивает подход «ошибка — это значение».
0

Согласен. Единственное, что неудобно, что Go никак не форсит необходимость обработки ошибки. Как товарищ выше сказал, вместо Either<Result, Error> передается (Maybe<Result>, Maybe<Error>). При этом на вид отличить функцию, которая может и не может вернуть ошибку нелегко.

+1
При этом на вид отличить функцию, которая может и не может вернуть ошибку нелегко.


Не понял. Как нелегко?

Смотрим в сигнатуру:

`func ReturnInterfaceWithoutError() interface{}` — эта возвращает просто что-то.
`func ReturnInterfaceAndError() (interface{}, error)` — эта возвращает что-то и опциональную ошибку.
Т.е. у второй нужно (в идеале) проверить, не вернулась ли ошибка, прежде чем в результат смотреть.

После этого вы делаете так:
`result := ReturnInterfaceAndError()`и получаете ошибку от компилятора, который вежливо вам подскажет, что так делать нехорошо.

И вам придется переписать так: `result, err := ReturnInterfaceAndError()` а потом еще и предпринять что-то с этой полученной ошибкой:
if err != nil {
  // ... здесь что-то нужно сделать...
  // предпринять какие-то действия, что-то намутить...
  // например, прокинуть ее вверх
  return nil, err
}


А можно проигнорить, но явно проигнорить:
`result, _ := ReturnInterfaceAndError()`
0

Если раньше функция была `func ReturnInterfaceWithoutError() interface{}, а в новой версии добавили error, компилятор ругнется в местах использования?


И вам придется переписать так: result, err := ReturnInterfaceAndError() а потом еще и предпринять что-то с этой полученной ошибкой:

Проглядел, перечитал, нашел. Ну, хорошо тогда :) Спасибо

0
И она умеет работать со структурами.

А, кстати, как, если дженериков или рефлексии (что рантайм, что компилтайм) нет?


Вот к такой обработке и подталкивает подход «ошибка — это значение».

С этим никто не спорит. Только в Go нет способов выразить это типобезопасно.

0
А, кстати, как, если дженериков или рефлексии (что рантайм, что компилтайм) нет?

encoding/json использует рефлексию.

0
Рефлексия есть.

Только в Go нет способов выразить это типобезопасно.


Вы, видимо, не работали с Go.
0
Рефлексия есть.

Да, нагуглил. Спасибо, тут я был неправ, ситуация проясняется.


Вы, видимо, не работали с Go.

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

0
Ну так, смысл в том, что у нас есть наш app, и зависимый пакет config.


У вас граф зависимостей поломался. config от app не зависит, это app зависит от config же.

но как выше говорилось, «go way» в том, чтобы следовать варианту 2 в той или иной форме.


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

Криво написал. конечно же app зависит от config.


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

Это прямо противоречит


Подход Go подталкивает к обработке ошибок, а не на отсылку их выше
0
Он подталкивает. И «взять ошибку и прокинуть выше» — это тоже обработка. Он просто заставляет сделать с ошибкой хоть что-нибудь.
0
Вот прямо ваш же код возьмем и перепишем, если я правильно распарсил ваши намерения:

config := conf.Deafult()

if err := config.FromFile("some/path/config.json"); err != nil {
   // ... вот тут паникуем, страдаем, фиксим, обрабатываем
   // и пофиг чем вообще занимаемся...
}

service, err := serv.NewAwesomeService(&config)

// ... дальше остальная муть
0
Ну как это не должна? log4rs, Microsoft.Extensions.Configuration.Json, cfg4j и все прочее…


Очевидно, вы считаете это лучшими примерами того, как нужно делать?

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


Поломанный конфиг — это не любой чих. Это заведомо некорректная рантайм-конфигурация. Это сервис, который, например, пишет не в ту базу, или возвращает отладочные результаты на запрос… И вы предлагаете его пустить в прод?
0
Очевидно, вы считаете это лучшими примерами того, как нужно делать?

Конечно. Лучше иметь пакет для инкапсуляции какого-то поведения, чем не иметь.


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

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

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


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

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

Ну а если поломанный конфиг не влияет на работу сервиса от слова «совсем»… Хм, а нафига вам вообще конфиг?
0
Если мы не смогли распарсить текстуру какого-нибудь модного меча в Скайриме, это повод не подгрузить меч и, возможно, продолжить работу дальше (остается на совести разработчика). Но текстура меча — это вполне себе отдельная сущность, которую можно считать частью конфигурации меча, не Скайрима. Если Скайрим не сможет прочитать свою конфигурацию (да-да, целиком), он ровно так же упадет, и это правильно.

Да, но и для чтения конфига мода, и для чтения конфига меча используется одна и та же либа :) Но в одном случае надо падать, а в другом — «забить» на ошибочный мод и работать дальше. Соответственно, это был пример того, что на уровне библиотеки нельзя принять правильное решение, она не знает, насколько это критичный конфиг. Решение одно — прокинуть ошибку наверх, пусть там разбираются. Собственно, мы в этом уже сошлись, поэтому не вижу смысла на этом задерживаться.

В контексте же сферы применения Go

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


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

Но в одном случае надо падать, а в другом — «забить» на ошибочный мод и работать дальше.


Фоллбек-логику, в смысле, вы тоже предлагаете в стороннюю библиотеку отдать? И пусть игрок вместо меча с розовым фаллоимитатором в руках бегает. А чо, прикольно же.

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


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

Я думал, цели го более широки, чем только лишь веб-серверы.


Чуть-чуть более, но не настолько, насколько вы представляли. Go — достаточно нишевой и строго утилитарный язык.
0
Хм, вы твердо уверены, что текстовый конфиг игры и bitmap-текстуры игровой модели (которые вы приводили как пример выше) должны парситься одной и той же библиотекой? У нас, видимо, разные с вами понятия о здравом смысле.

Ну возьмите вместо текстуры просто JSON с описанием характеристик меча. Текст-описание-урон, вот это всё.

Фоллбек-логику, в смысле, вы тоже предлагаете в стороннюю библиотеку отдать? И пусть игрок вместо меча с розовым фаллоимитатором в руках бегает. А чо, прикольно же.

В приложение. И не с розовым фаллоимитатором, а с черным квадратиком, tooltip missing, дефолтным мечом или чем-либо еще.
image

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

Вообще ни разу не предлагал возвращать кусок конфига :D
0
Вообще ни разу не предлагал возвращать кусок конфига :D


А что вы предлагали?

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

Итак, остаются нерешенными 2 вопроса:
1. Зачем вам логика парсинга JSON в конструкторе этого самого «меча»?
2. Чем случай «json-файл с конфигом отсутствует» в вашем конкретном случае отличается от «json-файл с конфигом не читается» или «json-файл с конфигом содержит некорректные данные»?

Вот смотрите, у вас же, видимо, на весь «МойСкайрим_v1.0Beta» несколько больше одного конфига? У вас же, надеюсь, конфиг самой игры, содержащий разрешение экрана, режим работы, настройки управления и т.д. и конфигурация ресурсов игры — это разные файлы? Причем, вероятно, на каждый «убер-меч» конфиг свой? Или вы враг себе и игроку?

Ну вот объясните мне, что, собственно, должна ваша библиотека по загрузке конфигурации делать такого, чего не может стандартная json-парсилка, которая возвращает инстанс объекта + nil в случае успешного парсинга, и nil + инстанс ошибки с описанием проблемы в случае фейла?
0
1. Зачем вам логика парсинга JSON в конструкторе этого самого «меча»?

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

Чем случай «json-файл с конфигом отсутствует» в вашем конкретном случае отличается от «json-файл с конфигом не читается» или «json-файл с конфигом содержит некорректные данные»?

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

Вот смотрите, у вас же, видимо, на весь «МойСкайрим_v1.0Beta» несколько больше одного конфига? У вас же, надеюсь, конфиг самой игры, содержащий разрешение экрана, режим работы, настройки управления и т.д. и конфигурация ресурсов игры — это разные файлы? Причем, вероятно, на каждый «убер-меч» конфиг свой? Или вы враг себе и игроку?

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

Ну вот объясните мне, что, собственно, должна ваша библиотека по загрузке конфигурации делать такого, чего не может стандартная json-парсилка, которая возвращает инстанс объекта + nil в случае успешного парсинга, и nil + инстанс ошибки с описанием проблемы в случае фейла?

Мержить конфиги, например. Ну или тот же релоад, как уже упоминалось. Чтобы поправили в конфиге значение, сохранили, а у вас в игре без перезагрузки сразу обновились характеристики.
+1
Ну, должен же меч как-то быть загружен. Обычная плагинная система — сначала скайрим идентифицирует моды, в частности «меч», потом говорит всем модам «загружайся», ну и дальше что-то пошло. Процесс загрузки состоит как раз из парсинга конфига, определения характеристик, и вот этого всего.


Хм, у вас «архитектура протекла». Процесс загрузки самой игры состоит из инициализации состояния игры в соответствии с объектом конфигурации. Не с json-файлом конфигурации, а с объектом конфигурации. Т.е. инициализация состояния и загрузка конфига из файла — это разные вещи, и чем меньше между ними связность, тем лучше (гуглить tight coupling).

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

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

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

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

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

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

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


Зачем, простите, вам в инициализации стейта игры код работы с файловой системой? Вот объясните мне, зачем вам os.Open() в одной строке и for _, mod := range mods { mod.Load() } в соседней? И чтобы дальше всенепременно было if len(sword) > 172 { return }?

Зачем вы смешиваете домен приложения с доменом системной ФС?

и не вызывать код загрузки конфига


Вам все равно его вызывать. И именно он должен сначала открыть файл на чтение.

file, err := os.Open("/path/to/file")
if err != nil {
    return nil, err
}


Все, остальное не выполнится.

А в случае, если файл есть, только библиотека знает, валидный он или нет.


Есть такое понятие, как «домен приложения». «Валидность JSON» — да, может проверить библиотека. Более того, она это и делает. Именно для этого `err := json.Unmarshall(bytesToParse, &configObject)`. Если `err != nil `, дальнейшие операции с конфигом бессмысленны, и вам по барабану, это файл поломатый, или JSON не валидный. Оно все равно не парсится.

А вот у самого приложения есть «домен», и валидность конфигурации в пределах этого домена вам никакой парсер не подскажет. Это просто невозможно, т.к. требования у каждого приложения свои.

Если вам нужна валидация по схеме… Ну, видимо, JSON — неверный выбор. Видимо вам BSON нужен, или SOAP.

чтобы было понятно, что мы не можем вставить валидацию на своей стороне


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

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


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

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

Мержить конфиги, например.


Это, простите, нахрена вообще?

Ну или тот же релоад, как уже упоминалось.


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

Чтобы поправили в конфиге значение, сохранили, а у вас в игре без перезагрузки сразу обновились характеристики.


Представил: для изменения разрешения в нашей игре зайдите в папку C:\Program File\Nasha Krutaya Igra\ и исправьте значение в 185-й сверху строчке на нужное вам.

UPD: Ввиду участившихся случаев краша приложения при исправлении конфига руками предоставляем список допустимых значений для указания в 185-й сверху строчке.

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

UPD: Конфигурацию нашей игры рекомендуется редактировать в Notepad++, т.к. Блокнот при сохранении меняет переносы в файле на \n\r, а это ломает наш парсер.

Ну вот чушь же, признайте.
0
Хм, у вас «архитектура протекла». Процесс загрузки самой игры состоит из инициализации состояния игры в соответствии с объектом конфигурации. Не с json-файлом конфигурации, а с объектом конфигурации. Т.е. инициализация состояния и загрузка конфига из файла — это разные вещи, и чем меньше между ними связность, тем лучше (гуглить tight coupling).

Да нет. все верно. Игра ничего не знает про файлы мода, он сам решает, что ему надо загрузить и как. Например, в WoT есть мод XVM, у него есть свой конфиг, про который игра ни сном, ни духом.

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

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

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

Ну это вы уже не в ту степь пошли. Щас еще античиты начнем разбирать :D

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

Верно.

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

Мы уже сошлись, что прокидывать ошибку — хорошо, и вы не согласны с оригинальным комметарием про «решаем на месте». О чем спор-то? :)
0
Валидность конфига легко решается соответствием типов. Если мы не смогли валидный JSON десериализовать в ожидаемую структуру, то валидация не прошла. Десериализацию в структуры библиотеки поддерживают.


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

Итак, зачем же вам отдельная универсальная сторонняя библиотека работы с конфигами?

Ну это вы уже не в ту степь пошли. Щас еще античиты начнем разбирать :D


Не, это мы все еще в той степи. Подключаемая библиотека может валидировать json-конфиг, но ровно до уровня валидности самого json и соответствия структуре. Валидация на уровне соответствия бизнес-логике вашего приложения в 3rd-party библиотеки — неправда.

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


Ронять надо не всю игру. Ронять надо загрузчик конкретного мода. Он не должен что-то возвращать, если что-то пошло не так. Он должен маякнуть наверх «что-то пошло не так» и сдохнуть, как джентльмен)
0
Вот, собственно, десериализацию с структуру поддерживает прямо стандартная JSON-парсилка в Go.

Итак, зачем же вам отдельная универсальная сторонняя библиотека работы с конфигами?

Как я уже говорил, в том же примере выше из дотнета в качестве типов нужных полей можно указать вместо T тип IOption<T>, который будет поддерживать автоподгрузку (при получение myOption.Value будет получаться не значение на момент десериализации, а то, которое в текущий момент занесено в конфиг). Пару раз пользовался этим функционалом, довольно удобно. Линк


Не, это мы все еще в той степи. Подключаемая библиотека может валидировать json-конфиг, но ровно до уровня валидности самого json и соответствия структуре. Валидация на уровне соответствия бизнес-логике вашего приложения в 3rd-party библиотеки — неправда.

Правда. Достаточно декларативно описать требования к полям/структуре, а библиотека с помощью этих атрибутов всё проверит. Декларативно > императивно.


Ронять надо не всю игру. Ронять надо загрузчик конкретного мода. Он не должен что-то возвращать, если что-то пошло не так. Он должен маякнуть наверх «что-то пошло не так» и сдохнуть, как джентльмен)

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

0
Как я уже говорил, в том же примере выше из дотнета в качестве типов нужных полей можно указать вместо T тип IOption, который будет поддерживать автоподгрузку (при получение myOption.Value будет получаться не значение на момент десериализации, а то, которое в текущий момент занесено в конфиг). Пару раз пользовался этим функционалом, довольно удобно. Линк

play.golang.org/p/-iiZnh5uhy — оно?

Прямо стандартная json-парсилка. Заполняешь дефолтами, анмаршаллишь. Те поля, которые есть в файле, оверрайдятся, остальные остаются «как есть».

Достаточно декларативно описать требования к полям/структуре, а библиотека с помощью этих атрибутов всё проверит. Декларативно > императивно.


«Декларативно», в конечном итоге, разворачивается во вполне себе императивный код.

А моё мнение, что это вызывающий код должен решить, что делать.


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

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


В каком месте оно вызывает глобальный хендлер?

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

У вас форматирование сломалось.


play.golang.org/p/-iiZnh5uhy — оно?

Нет. Пример:


class MyConfig 
{
   public IOption<int> X {get;set;}
}

const string filename = "appconfig.json";
var config = Config<MyConfig>.From(filename);
Console.WriteLine(config.X.CurrentValue); // 5
File.WriteAllText(filename, "{X: 10}"); // перезаписываем файл
Console.WriteLine(config.X.CurrentValue); // 10

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


«Декларативно», в конечном итоге, разворачивается во вполне себе императивный код.

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


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

Вызывающему коду нужна любая информация об ошибке, а не прыжок через голову в панику. О чем я уже много раз говорил.


В каком месте оно вызывает глобальный хендлер?

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


Выше это описывалось как go-way, и "решение проблемы на месте", о чем мы вроде бы уже говорили.

0
Смысл в таком сценарии, что мы меняем файл, и наша структура автоматически подцепляет изменения.


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

Вызывающему коду нужна любая информация об ошибке, а не прыжок через голову в панику. О чем я уже много раз говорил.


Ну вот, собственно, encoding/json именно это и делает: возвращает десериализованное значение и пустую ошибку, либо nil и ошибку. Собственно, любой модуль/библиотека должны вести себя рово так же. Паники и прочее — это механизмы уровня приложения. Пакет не должен инициировать паник.

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


Ни одна приличная 3rd-party библиотека панику не кидает.

Выше это описывалось как go-way, и «решение проблемы на месте», о чем мы вроде бы уже говорили.


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

В связи с этим в Java вы можете к сигнатуре метода присобачить throws Exception и просто «забить» на все то, что внутри пошло не так. В Go «забивать» придется явно, и не в сигнатуре функции, а в том месте, которое вернуло ошибку. Это и называется «обработка ошибок на месте».
0
Смысл такого сценария — примерно околонулевой, если честно. Меняете значение в структуре, сериализуете в файл — получаете ровно аналогичный результат

Эмм, меняет конфиг кто-то снаружи. Как мне после этого получить новое значение?


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

Ну это да, я уже давно прочуствовал пользу Result<T,E> относительно исключений, её мне приводить не надо, действительно удобно :)

+1
Ну это да, я уже давно прочуствовал пользу Result<T,E> относительно исключений, её мне приводить не надо, действительно удобно :)

Только в Go это не T | E, в Go это (Maybe T, Maybe E). Это уже не так удобно, мягко скажем.

0
Хм… видимо, от ситуации зависит.

Если nil-значение считается валидным — то все норм, работать с nil-значением.

Если же nil-значение не считается валидным. Ну, примерно так же, как если вам dotnet-библиотека вместо вменяемого результата молча гуано возвращает. Я бы предложил либо патч в библиотеку послать с иправлением косяка, либо выкинуть каку и поискать нормальную библиотеку, которая возвращает ошибку, если что-то пошло не так.

А в dotnet'е не так принято?
0

В дотнете вообще эксепшны, там вся эта логика неприменима.


В русте у вас именно что T | E, и всего два варианта, вместо четырех.

0
Вся эта логика — набор соглашений уровня языка, по сути.

Собственно, исходя из соглашений, в Go у вас тоже два варианта — ошибка есть или ошибки нет.
+1

В существенно меньшей типобезопасности и выразительности.


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

0
Эмм, меняет конфиг кто-то снаружи. Как мне после этого получить новое значение?


Эмм, перезапустить приложение с новым конфигом? Эмм, явно пнуть приложение с указанием перечитать конфиг?

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

Ну это да, я уже давно прочуствовал пользу Result<T,E> относительно исключений, её мне приводить не надо, действительно удобно :)


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

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

+1
Нет, граблей никаких нет


Аж поперхнулся…

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

А закрывать/открывать ресурсы за вас, видимо, тоже будет сторонняя библиотека? И вероятность получить неконсистентное состояние приложения при наличии более одного конфига — тоже норм?

всё работало отлично на разных ОС на разных ФС.


А, ну если у вас все работало, тогда, конечно же… Пишем Хейлсбергу, Эккелю и Веннерсу, что они нифига не понимают в настоящей разработке, и пусть свои рекомендации куда-нибудь в другое место несут, а не в dotnet.

Ой, погодите, а кто же эти люди?..
-1

Ну давайте посмотрим с другой стороны.


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

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


Вполне возможно, например. Ну, либо оно legacy.
0
Смысл в таком сценарии, что мы меняем файл, и наша структура автоматически подцепляет изменения.


Не забудьте, что вам еще нужно пересоздать все обьекты, которые зависят от конфига (сделать новые коннекты, etc). Я предпочитаю делать перезагрузку всего приложения — например смотреть на конфиг, если изменился, то релоад приложения.

Хотя сейчас, когда мы используем kubernetes, смысла в этом уже нету — либо рестартануть pod (если конфиг замаунчен как раздел) или просто смотреть kubernets API на изменения конфига. В таком случае умение библиотеки изменять структура после изменения файла бесполезно.
0
Не забудьте, что вам еще нужно пересоздать все обьекты, которые зависят от конфига (сделать новые коннекты, etc). Я предпочитаю делать перезагрузку всего приложения — например смотреть на конфиг, если изменился, то релоад приложения.

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

0
Например, коннекты к базе? Или вам не нужно будет рестартить http сервер, конфиг которого изменился?
0
Не нужно. Потому что контроллеры живут как правило per call, создаются DI контейнером, и легко получают новые параметры на все новые запросы.

То же про коннекты. Если пул, можно подписаться на событие, и обновить коннекшн стринг. Если нет пула, то ничего делать не надо, всё заработает из коробки.
0
Не нужно. Потому что контроллеры живут как правило per call, создаются DI контейнером, и легко получают новые параметры на все новые запросы.


Есть еще масса параметров http сервера, когда нужно будет его рестартануть (ключи, конфиг tls, etc).

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


Не только обновить коннекшн стринг, а закрыть старые и создать новые коннекты. А что, вы создаете новый коннект к базе на каждый запрос?
0
Есть еще масса параметров http сервера, когда нужно будет его рестартануть (ключи, конфиг tls, etc).

Я про конфиг уровня приложения говорю.

Не только обновить коннекшн стринг, а закрыть старые и создать новые коннекты.

Старые коннекты закрывать не надо, их надо обработать. Новые открывать по новым правилам.

Как предлагается рестартом сервера это решать, я не понимаю. Вот у нас есть 2 запроса, один — завершающийся старый со старыми параметрами, другой — новый с новыми.

А что, вы создаете новый коннект к базе на каждый запрос?

Если у меня 1рпц, то почему бы и нет, если это облегчит подобные сценарии.
+1
Я про конфиг уровня приложения говорю.


А что, http сервер уже не часть приложения?

Как предлагается рестартом сервера это решать, я не понимаю. Вот у нас есть 2 запроса, один — завершающийся старый со старыми параметрами, другой — новый с новыми.

Делать нужно graceful stop приложения (то есть не принимать новых коннектов, а только завершать старые), это должно быть уже у вас реализовано, а потом уже создавать новые коннекты, обьекты и тд — после этого принимать новые запросы.

Если у меня 1рпц, то почему бы и нет, если это облегчит подобные сценарии.

Вы только что сами придумали себе кучу проблем, вместо того чтобы просто релоаднуть приложения когда конфиг изменился (это можно уже отследить).
0
А что, http сервер уже не часть приложения?

nginx — не является.

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

Делать нужно graceful stop приложения (то есть не принимать новых коннектов, а только завершать старые), это должно быть уже у вас реализовано, а потом уже создавать новые коннекты, обьекты и тд — после этого принимать новые запросы.


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

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

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

Он не откидывает новые соединения, он исключен из балансировки.
Если у вас только один инстанс (что случай только для балансера), то дейлайте как это сделано в nginx — старые процессы обрабатывают только текущие запросы, а все новые идут на новые.

Нет, это вы предлагаете ронять целое приложение просто чтобы обновить одно значение из конфига.

Если у вас HA, то нету никаких проблем тут. Та же стратегия во время деплоймента новой версии, только вместо релоада — рестарт.
0
Он не откидывает новые соединения, он исключен из балансировки.
Если у вас только один инстанс (что случай только для балансера), то дейлайте как это сделано в nginx — старые процессы обрабатывают только текущие запросы, а все новые идут на новые.


Предлагаете делать кластер просто чтобы конфиги перегружать?

Если у вас только один инстанс (что случай только для балансера)

Какой балансер на 1..10 рпс?

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

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

Предлагаю всегда иметь HA, иначе это уже не серьезно.

Толку с ваших стратегий по обновлению конфига, если у вас упал один инстанс и уже даунтайм.
HA это про доступность сервиса.

0
Предлагаю всегда иметь HA, иначе это уже не серьезно.

О, пошли разговоры про «серьезность».

Ладно, удачи в вашей нелегкой работе.
0
Ну я же написал еще «толку с ваших стратегий по обновлению конфига, если у вас упал один инстанс и уже даунтайм».
0
Может с того, что он не падает просто так?

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

ОС я так понимаю вы тоже не обновляете?
0
Это не надежда, это статистика.

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

Ага, а еще у вас уязвимостей в библиотеках нету. /s

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

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

А это уже зона ответственности PaaS-платформы, которой вы пользуетесь. В моем случае это был Azure. Там проблем бтв тоже не было.
0
В моем случае это был Azure. Там проблем бтв тоже не было.

azure.microsoft.com/en-us/status/history

А это уже зона ответственности PaaS-платформы, которой вы пользуетесь.

Я ее разрабатываю.
Но не все пользуются облачными платформами, не у всех 1 rps нагрузка и так далее. Вы тут подобрали специальные условия и представляете свое решение как единственно правильное учитывая только ваши условия.
0
Не, все совсем иначе.

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

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

Результат, собственно, перед вами.

Рад был пообщаться.
+1
Да нет, как раз вы добавляли условий, потому что вас не устраивали ответы и вы пытались подогнать все под свою позицию. При этом вы будете игнорировать безопасность, высокодоступность, чтобы только подогнать все под ваше решение.
0
Библиотеку чего, простите, в контексте работы с конфигом?
0
Видимо, вы считаете, что лучше запустить сервис с некорректным конфигом?
0
Возвращать nil вместо объекта конфигурации?


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

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

Собственно, вы упираете на «файл конфига есть, просто при чтении возникла какая-то ошибка». Чем, простите, с точки зрения корректности конфигурации, отличается ситуация «файла конфига нет» от «файл конфига не читается» или «в файле конфига какая-то труднообъяснимая ересь»? Результат с точки зрения сервиса в целом один: конфига нет.
0
А вы, видимо, предлагаете возвращать заведомо некорректную конфигурацию и с ней работать?

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

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

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

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


Да, очень часто её нужно просто прокинуть наверх, но, тем не менее, хоть это и самый частый вариант, делать его вариантом "по умолчанию" (как очень часто используют исключения) это плохая идея, именно потому, что позволяет вообще не задумываться об обработке ошибок до самого последнего момента. И да, если разработчик будет механически везде вставлять if err != nil { return err } то это будет ровно то же самое — он не задумывается об обработке ошибок, и просто тупо набирает бессмысленный код. Тем не менее, сама необходимость этот код набирать, и потом читать на ревью — увеличивает вероятность, что кто-нибудь всё-таки задумается, насколько этот код корректен в случае ошибки конкретно в этом месте.


Конечно, всем хочется набирать меньше тупого шаблонного кода, но нельзя это делать ценой потери такого полезного качества языка как подталкивание программистов задумываться о корректной обработке ошибок. Более детально почитать про это можно в описании нового синтаксиса обработки ошибок в Go2 через check/handle.

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

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

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

в любом ЯП хорошая идея делать дефолтным вариантом самый частоиспользуемый.

И да, если разработчик будет механически везде вставлять if err != nil { return err } то это будет ровно то же самое — он не задумывается об обработке ошибок, и просто тупо набирает

Так он так и будет делать. В идеале, сделает сниппет, который по ctrl+ec будет вставлять этот код.

Тем не менее, сама необходимость этот код набирать, и потом читать на ревью — увеличивает вероятность, что кто-нибудь всё-таки задумается, насколько этот код корректен в случае ошибки конкретно в этом месте.

Люди — не идиоты. Например «баннерная слепота» туда же, люди автоматически игнорируют знакомые идиомы и паттерны. Это хорошо видно в исследованиях влияния подсветки кода на скорость распознавания и понимания.
0

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


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

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

В тысячный раз над этим задумываться перестаешь. Проверенно типовыми throw new Exception() и прочими фрагментами.

0

Никто не в силах помешать делать глупости или писать код бездумно. ЯП может просто способствовать более правильному отношению либо потакать менее правильному. Смысл — зачем это делается именно так — я пояснил. Конкретная реализация — как сделано сейчас, или как в будущем будет сделано через handle/check — не имеет принципиального значения, пока сохраняется изначальный смысл. Придумаете более классную реализацию — welcome, сейчас как раз идёт обсуждение handle/check, и у Вас есть реальный шанс повлиять на дизайн языка.

0

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


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

+1

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

0

Ну возможно. Если бездумно лепить unsafe в расте то можно добиться похожего эффекта, как сниппет для IDE.

0
> Но и подчёркивание, и комбинацию для сниппета надо набрать. А значит — задуматься, какой из типичных сниппетов или подчёркивание подходит для обработки текущей ошибки.

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

Возможно, тут «хотели как лучше» ©, но…
0

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

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


Вот именно так и «идиоматично» для Go.

Шаг #1: получаем конфиг (config, err := conf.GetConfigFromFile('/file/path/here'))
Шаг #2: смотрим на err, если оно не nil, предпринимаем действия по обработки (заметьте, именно в вызывающем коде)
Шаг #3: если конфиг все еще считается валидным, создаем инстанс сервиса (service, err := serv.NewInstance(&config)), скармливая готовый конфиг внутрь. Если сервис не смог «подняться» — смотрим в ошибку, принимаем решение, что делать дальше (опять же на стороне вызывающего кода)
-20
Итого:
1. Нет подсветки кода в документации. — Ну нет и нет, это не тянет на недостаток.
2. Политика. — Высосано из пальца. Может быть так, а может быть иначе. Неявное разделение есть в любом языке и проекте.
3. Пакеты и GOPATH. — уже на дворе 1.11 и это исправили.
4. Обработка ошибок в Go. — скоро завезут гораздо более приятную.

Итого вкусовщина на основе частично-устаревшей информации. Go есть за что поругать, но оригинальные авторы статьи даже не заморачивались этим вопросом.
+12
Политика. — Высосано из пальца. Может быть так, а может быть иначе. Неявное разделение есть в любом языке и проекте.

Зато есть весьма явное высказывание о Golang от Роба Пайка о том для кого и зачем делался язык.


Пакеты и GOPATH. — уже на дворе 1.11 и это исправили.

Вы во всех своих проектах уже на модули переехали?


Обработка ошибок в Go. — скоро завезут гораздо более приятную.

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

-10
Зато есть весьма явное высказывание о Golang от Роба Пайка о том для кого и зачем делался язык.

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

Мы уже готовы переехать и сделаем это в ближайший месяц. Но пока и от GOPATH не сильно страдаем и vendor mode используем и как-то нормально.

Про обработку ошибок я упоминаю только потому, что довольно скоро пол этой статьи потеряет актуальность. Тем не менее в статье это подаётся как истина последней инстанции.
0
Я правильно понимаю, что в языках с tuple и exception можно использовать и подход Go и ловить исключения?
0
Можно, но на выходе возврат ошибки вторым параметром делают редко, ибо как-то получается без этого обходиться.
+3

Обычно для обработки ошибок в языках, где для этого не принято использовать исключения используются типы-суммы, например Result из Rust или Either из Haskell.

+2
Зато есть весьма явное высказывание о Golang от Роба Пайка о том для кого и зачем делался язык.

Я кстати не очень понимаю, почему всех так задевает, что Роб Пайк отзывается о молодых программистах в Google, как о не самых умных и опытных :)?

Программирование — очень бурно развивающаяся профессия и с большим количеством новичков, причём зачастую без профильного образования или без образования вообще. Даже Rust, который не такой сложный, как, например, Haskell, как-то не получил такой популярности, как Go.

Можно возразить, что «настоящие программисты» никогда не будут писать на Go, потому что он «для дураков», но это я бы оставил на совести того, кто так думает. Да, безусловно, доступность Go это и недостаток, потому что привлекает много новичков без опыта. Но и достаточно высококвалифицированные программисты тоже его используют, и с этим спорить достаточно сложно (хотя, например, тот же Docker я хорошим примером не считаю).
+7
Rust, который не такой сложный, как, например, Haskell
Только все наоборот — Rust сложный язык, предназначенный для решения сложных задач в ограниченных ресурсах по памяти и производительности, а базовый Haskell (если не углубляться в редко используемые темы, вроде GADT) очень простой, с минимумом необходимых для работы концепций.
+1
Только сейчас куда ни ткни, везде то профункторы (lens), то попытка прикинуться завтипизированным языком (singletons).
0
Раст намного более привычный, чем хаскель. Во-первых он императивный. Во-вторых С-подобный. Кроме лайфтаймов в нем нет ничего особо непривычного.
+1
Там есть алгебраические типы, их же гошники не способны освоить!
+1
Вся продукция HashiCorp есть оправдание существования Go! :)
+7

Проблема в том, что в ответ на почти любые претензии о косяках или явном примитивизме получаешь ответ "Ты ничего не понимаешь, малыш! Так надо!". Помню как на вопрос почему вообще допустили такой косяк как non-nil nil interfaces, я получил ответ именно в таком стиле, с жонглированием значения термина "указатель". Ссылку, к сожалению, уже не найду. Ответ был не от Пайка если что.

+12
такие как Rust,… — тоже выдают предупреждения, где это возможно

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

+3
Чего лично мне не хватает в Go:
1. Значений для аргументов функций и свойств структур по умолчанию.
Согласитесь, было бы гораздо удобнее писать так:
type Qwerty struct {
  A int = 10
  B string = "cdef"
}

func Zxc(A string, B integer = 456) {
}

2. Более прямой инициализации свойств «вложенной» структуры при наследовании.
Сейчас надо сделать как-то так:
type A struct {
  One int
}
type B struct {
  A
  Two int
}
...
B{
  A{
    One: 1,
  },
  Two: 2,
}

-10
Согласитесь, было бы гораздо удобнее писать так:

Писать — да. Читать — нет. А легко читать намного важнее.


Более прямой инициализации свойств «вложенной» структуры при наследовании.

Так можно делать, просто не надо смешивать инициализацию по имени и по позиции: https://play.golang.org/p/5Qihnbmv1Gi

+2
Писать — да. Читать — нет. А легко читать намного важнее.
В очень многих языках есть подобный синтаксис. Почему бы и нет? Зато сейчас приходится писать отдельную функцию для инициализации этих полей значениями по умолчанию.
Так можно делать, просто не надо смешивать инициализацию по имени и по позиции:
Я имел в виду такое:
B{
  One: 1,
  Two: 2,
}
-3
В очень многих языках есть подобный синтаксис.

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


Я имел в виду такое:

Может добавят в будущем, а пока можно так:


var b B
b.One = 1
b.Two = 2
0

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


  • В Go нет такой ситуации, как, например, в C, когда в переменной может храниться что угодно и надо принудительно проинициализировать нулём, пустой строкой и т.п.
  • Как быть с типами, которые не позволяют создавать константы?
    • Инлайновое объявление какой-нибудь структуры или слайса превратит код в лапшу (и убьёт удобство)
    • Если позволять в качестве значений по-умолчанию вставлять изменяемые переменные, то непонятно, в какой момент вычислять дефолтное значение

По-второму всё довольно очевидно потому что в Go нет наследования, а есть композиция. И если понимать, что это именно вложенный самостоятельный объект, а не классическое наследование, то получается, что прямее некуда.
Например, можно делать так:


type B struct {
    *A
    Two int
}

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

+17
ээ… и это все?

I've gotten pretty familiar with it.


Если это действительно так, то не упомянуты вещи страшнее отсутствия привычных исключений и лапшевидного реагирование на ошибки:
— дедлоки каналов, по невнимательности\неопытности заруинит приложение и вообще отобьет охоту иметь дело с го
— интерфейсы (они вообще развязывают руки для любого извращения)
— горутинами можно злоупотреблять, компилятор не выдаст предупреждение, если в какой-то горутине не закрывать дескриптор ресурса (файл, соединение, етс.)
— йота — странный модификатор констант, оно мне больше напоминает «финт для продвинутых», пользы от наличия enum больше.
— неоднозначная однозначность… вроде как навязывают единый стиль, даже gofmt все в едино сводит, и даже в последних версиях пустые строки научился удалять, но опять же возможность делать одно и тоже несколькими способами вносит разногласия:

if err := f(); err != nil {
...
}
err := f()
if err != nil {
}
v1 := new(s)
v2 := &s{}


и всякое такое…

особенно раздражает возможность инициализировать переменную в любом месте… хоть через `var`, хоть через `:=`, кроме того область видимости переменной может быть лишь в пределах условия\цикла (тоесть одно и тоже имя для переменной использовать многократно в разных конструкциях в пределах одной функции)…

И куча другого субъективного, как по мне, го:
1. позволяет писать синтаксически запутанный код
2. порог вхождения не так низкий…
3. и хочется и колется… но продолжаю писать на го…

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

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

+1
дедлоки каналов, по невнимательности\неопытности заруинит приложение и вообще отобьет охоту иметь дело с го

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


интерфейсы (они вообще развязывают руки для любого извращения)

Интерфейсами злоупотребляют в тех случаях, когда по каким-то причинам думают, что func f(interface{}) == template typename<T> f(T), а если так не делать (а так делать прямо не советуют в официальных ресурсах), то и проблем не будет


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

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


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

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


возможность делать одно и тоже несколькими способами

Строго говоря, не одно и то же:


// область видимости err ограничена внутренностью if и не перезаписывает переменную err извне
if err := f(); err != nil {  
    // ...
}

err := f() // область видимости err ограничена функцией, и дальше не может быть переопределена
if err != nil {
    // ...
}

v1 := new(int) // так сделать можно
v2 := &int     // так сделать нельзя
+2
Но незакрытые дескрипторы вызовут проблемы и если вы оставите их открытыми в любом другом языке.

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


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

0

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


В том же Go проблемы с горутинами и ресурсами, в основном, связаны не с тем, что кто-то забывает вызвать Close (к тому же финалайзеры вызываются, когда os.File собирается GC, например. эдакий недетерминированный RAII), а из-за того, что условие выхода из горутины не определено.
Про таймауты на http-соединения забывают намного чаще. А это, в принципе, мало зависит от языка.


Можно возразить, мол, не во всех языках есть "легкие треды" и будете правы, там где приходится иметь пул потоков — он забьётся и всё колом встанет, а в Go будет расти память на висящих горутинах, пока процесс не умрет по OOM.

-1
а в Go будет расти память на висящих горутинах

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

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

0
> Дедлоки в принципе возможны везде, где есть асинхронность, и в Go, в отличие от многих других экосистем, есть рейс-детектор в поставке по-умолчанию и отладка таких вещей намного проще, чем не в Go.

Не везде. Например в pony нет дедлоков за счёт специальных аннотаций типов.
0
кроме того область видимости переменной может быть лишь в пределах условия\цикла (тоесть одно и тоже имя для переменной использовать многократно в разных конструкциях в пределах одной функции)…


А скоуп-то вам чем помешал? Да, собственно, покажите мне язык, где сделано иначе?
+3
Обрабатывать ошибки в Go еще не удобнее, если необходимо выполнять разные условия в зависимости от ее типа.

1. В большинстве случаев просто возвращается ошибка с текстом (fmt.Errorf, errors.New). В этом случае необходимо ее парсить.
2. В библиотеке есть внутренние «типизированные» ошибки (var errInternal = errors.New(«package: internal»). Но они не экспортируются и так же приходится парсить.
3. Экспортирует внутренние ошибки, сравнение по значению (err == io.EOF)
4. Проверять ошибку на соответствию интерфейсу (Timeout)
5. Возвращает свою структуру (net.OpError) с доп. контекстом. Делаем swtich err.(type).

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

Но этим грешат и сторонние пакеты, так и подходы в других языках.
0

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


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

+2
Что мне не нравится в Го:

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

— непонятная ситуация с менеджером пакетов. Почему нельзя, как в PHP, просто сделать composer install? Зачем устроили чехарду с GOPATH? Вот удовольствие, всю иерархию папок в /tmp создавать. И непонятно как выложить пакет на гитхаб. Самый работающий вариант — просто скачать зависимости и засунуть в папку.

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

— невозможно нормально вызывать код на Си, так как у Го есть свои потоки, которые параллельно что-то там делают и мешают коду на Си. Почему нет функции для отключения всей этой ерунды, когда нужно? Пытаешься навесить ограничения через seccomp() и все отваливается. Ну и сам CGO так себе — надо как-то извращенно в комментариях указывать инклуды и флаги компилятора. Причем ведь, небось, не портабельно и не с любым комилятором это работает.

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

Учитывая отличную поддержку go to definition во всех редакторах/IDE для Go — это никогда не является проблемой, мне давно всё-равно в каком файле написано объявление, если я могу туда моментально попасть нажав gd на идентификаторе в vim.


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


Зачем устроили чехарду с GOPATH?

Затем, что традиционные подходы к управлению пакетами не устраивали. Сейчас эта проблема уже (почти) в прошлом, поддержка модулей уже добавлена, осталось дождаться пока подтянутся утилиты вроде gorename чтобы работа вне GOPATH не создавала неудобств.


и надо постоянно вспоминать, как именно пишется

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


невозможно нормально вызывать код на Си

Это общая проблема всех языков, которые не Си. По крайней мере всех, на которых мне это приходилось делать.

-1
Иииии… Приз за самый странный список претензий к Go уходит господину с ником Sabubu.

По идее пакет — это папка


И не по идее тоже.

но правила не говорят ничего о том, в какой файл надо помещать то или иное определение.


Собственно, кто вам мешает их написать и строжайшим образом придерживаться?

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


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

Сделали бы каждый файл отдельным пакетом тогда


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

непонятная ситуация с менеджером пакетов


go get = git clone. Куда уж понятнее, чесслово?

Почему нельзя, как в PHP, просто сделать composer install?


Чоэта нельзя? dep init && dep ensure? Да там сильно больше одного варианта, если честно.

Зачем устроили чехарду с GOPATH?


Чтобы не повторять историю с node_modules, вероятно.

Вот удовольствие, всю иерархию папок в /tmp создавать.


Честно говоря, не самое подходящее место для GOPATH, имхо.

Самый работающий вариант — просто скачать зависимости и засунуть в папку.


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

Половина функций с маленькой буквы, половина с большой, и надо постоянно вспоминать, как именно пишется та или иная.


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

невозможно нормально вызывать код на Си, так как у Го есть свои потоки, которые параллельно что-то там делают и мешают коду на Си


Ну вот это вообще странная претензия. Из языка A сложно работать с кодом на языке B. Покажите, пожалуйста, где это не так.

Почему нет функции для отключения всей этой ерунды, когда нужно?


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

не с любым комилятором это работает


Ну, как бы, у Go официально один компилятор…
+2
> Собственно, кто вам мешает их написать и строжайшим образом придерживаться?

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

> В теории, мне рассказывали, что есть такая штука, как go to definition или, на крайняк, текстовый поиск в пределах папки

Это неудобно, а go to definition в моем редакторе, может быть, нету.

> go get = git clone. Куда уж понятнее, чесслово?

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

> Честно говоря, не самое подходящее место для GOPATH, имхо.

Ну мне надо загрузить код, скомпилировать, прогнать тесты и удалить. /tmp для этого самое логичное место. А редактировать код я, конечно, буду в Виндоуз, а не Линукс.

> Импортированная извне — с заглавной (а перед ней еще имя пакета, обычно), своя внутренняя — со строчной.

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

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


Ну, т.е. «синдром утёнка» в чистом виде? «Я вот привык к языку X, а Go не X, поэтому Go — говно»?

Это неудобно, а go to definition в моем редакторе, может быть, нету.


Кхм, для Go есть две известные мне IDE-шки, в обеих go to definition есть. Возможно, вам стоит просто изменить редактор?

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


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

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


Ну, собственно, в таком подходе можно и в /tmp. Однако не совсем понятно, почему не вкорячить GOPATH в пределах вашего хомяка и после сборки/прогонки тестов достаточно будет просто вычистить GOPATH/src.

А редактировать код я, конечно, буду в Виндоуз, а не Линукс.


Вот тут я реально не понял, где тут причина для гордости, и почему «конечно».

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


Собственно, не пишите большие модули. К тому же вам никто не запретит иметь неэкспортированные методы (все) в модуле. А экспортировать их, создавая врапперы с заглавной буквы. Самый простой подход, к слову.
0
Ну, т.е. «синдром утёнка» в чистом виде? «Я вот привык к языку X, а Go не X, поэтому Go — говно»?

Нет, это парадокс блаба


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

Го, очевидно, менее мощен чем другие языки, потому что это было требованием, заложенным в него Пайком.


Ну, собственно, в таком подходе можно и в /tmp. Однако не совсем понятно, почему не вкорячить GOPATH в пределах вашего хомяка и после сборки/прогонки тестов достаточно будет просто вычистить GOPATH/src.

Что если разным тестам нужны разные gopath?


Вот тут я реально не понял, где тут причина для гордости, и почему «конечно».

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


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

А при чем тут размер? Или у вас модули на 10 типов и все?

0
Го, очевидно, менее мощен чем другие языки, потому что это было требованием, заложенным в него Пайком.


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

Что если разным тестам нужны разные gopath?


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

А если у вас есть несколько разных проектов…

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

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

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

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

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


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

А при чем тут размер? Или у вас модули на 10 типов и все?


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

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


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

Ну как это. Если я хочу протестировать работу приложения со стейблом, бетой и ночником одной и той же библиотеки, как это сделать?


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

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


Ну вот, например, в Go не принято иметь «большие модули». И «большие интерфейсы» тоже, например…

"Большой" понятие растяжимое.


В библиотеке на 20-30 типов почти наверняка описанная ситуация уже произойдет.

0
Ну как это. Если я хочу протестировать работу приложения со стейблом, бетой и ночником одной и той же библиотеки, как это сделать

Контроль версий, просто создаете новую ветку с новой версией завендоренной библиотеки.
0
Ну как это. Если я хочу протестировать работу приложения со стейблом, бетой и ночником одной и той же библиотеки, как это сделать?


Ну, собственно, если уж на то пошло, то это, как раз, делается элементарно. Средствами git. Дело в том, что вас никто не обязывает пакеты в gopath go get'ом тянуть. Их можно просто git'ом положить. А потом просто git checkout делать… И это, стоит признать, для подобных сценариев скорее в плюс Go-экосистеме.

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


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

В библиотеке на 20-30 типов почти наверняка описанная ситуация уже произойдет.


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

Но это, собственно, вкусовщина, которая, к тому же, неплохо решается средствами IDE.
0
Вопрос в том, удобно ли на винде разрабатывать?

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


На плюсах мне удобнее под линуксом. Потому что KDevelop (или CLion), и потому что системный пакетный менеджер со всеми нужными мне библиотеками.


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

+1
Читаю очередную статью о недостатках Go и со всем согласен. А где статьи о плюсах Go? Интереса ради бы почитал. Не считаю язык хорошим, но понять бы, что другие в нём находят.
UFO landed and left these words here
+5
Так можно сказать про любой язык:
легко писать, легко читать, легко собирать, легко деплоить на сервер, легко и очевидно вообще все.

Вот только на поверку оказывается, что иногда проще даже на Rust написать, чем на Go с его вырвиглазной и нечитабельной обработкой ошибок, странными naming conventions (вкупе с отсутствием нормальных IDE кроме жидбрейновской), и возможностью выстрела в ногу с горутинами. В Rust безопасность и эффективность кода получается выше, при этом код не сильно менее читабелен.
-2
иногда проще даже на Rust написать


Вы же понимаете, что ключевое слово — «иногда». Прям, я бы сказал, сильно иногда.
0
при этом код не сильно менее читабелен.

Прикладной код вообще не менее читабелен, а скорее более.


Я в соседней статье попросил показать мне конкретные недостатки моей реализации телеграм-клиента на расте, из-за которой непонятно, что происходит. Пока что тишина...

0
Мне судить сложно, я в обоих технологиях на уровне джуна. Сужу по опен-сорцу.
0
Несколько лет назад не знал про Радио-Т, но сейчас умпутун там главный проповедник Go
0
Повзрослел человек, сменились ценности. Вместо интереса процесса стал важнее простой и надежный результат.
+5

Если коротко, то на нём просто, быстро и приятно писать надёжный и читабельный код. Если не так коротко, то (в случайном порядке):


  • простой язык, что даёт возможность без труда выучить его "от и до", на 100%
  • гарантия обратной совместимости Go1, что даёт возможность тратить минимум времени на поддержку старого кода
  • отличный вспомогательный инструментарий, как "из коробки", так и сторонние утилиты (тут конкретику можно долго перечислять, из основного: тесты, документация, управление пакетами, мощные линтеры, кодогенерация, детектор race conditions, …)
  • очень простая кросс-компиляция и отличная портабельность стандартной библиотеки, что сильно упрощает разработку кросс-платформенных приложений
  • один бинарник, часто статический, что упрощает деплой как микросервисов, так и кросс-платформенных утилит
  • строгая статическая типизация с выводом типов, что позволяет получить соответствующие плюшки без того, чтобы вручную постоянно уточнять все типы в коде
  • горутины и каналы, что даёт возможность писать обычный синхронный код (как правило без мьютексов и коллбэков) и при этом обрабатывать миллионы параллельных соединений задействуя все CPU, имея при этом на порядок реже типичные для многопоточного кода сложные проблемы с дедлоками и race conditions
  • интерфейсы (которые, к сожалению, многие не понимают), которые позволяют проще и элегантнее проектировать код
  • defer, что упрощает контроль за освобождением ресурсов
  • отсутствие наследования, что тоже способствует написанию более ясного кода
  • регулярки RE2, что гарантирует предсказуемое время их выполнения
  • безопасный, в плане отсутствия ручного управления памятью…
  • … но при этом достаточно низкоуровневый, чтобы можно было при необходимости вручную оптимизировать код вплоть до выделения памяти
  • единый стиль оформления кода везде, что упрощает чтение кода и экономит время на согласование стиля в каждом проекте
  • мощная стандартная библиотека, что даёт возможность обходиться без дополнительных больших и сложных фреймворков в большинстве проектов
  • под линух есть возможность писать аналог обычных скриптов на Go (т.е. выполнимый файл с исходником на Go а-ля ~/bin/something.go, который можно запускать как обычную команду something.go)
  • очень быстрая компиляция, вроде бы мелочь, но как посмотришь на C++ то становится ясно, что не такая уж это и мелочь
  • в отличие от некоторых других отличных языков — на нём легко найти работу
0
быстро и приятно

ну с приятностью я б поспорил. Для меня Go это необходимое зло
+2
> строгая статическая типизация с выводом типов

Честно, вывод типов в go достаточно слабый.

Простейший пример на go.
Простейший пример на rust.

В аргументе c функции call указаны все необходимые типы для колбэка. При этом, в коде на go всё равно приходится их указывать, тогда как в коде на rust в этом уже нет необходимости.
0

Да и типизация из-за отсутствия дженериков иногда нуждается в подпорках

0
Если я правильно понял, то в Rust это не вывод типов, а то как работают замыкания — типы (сигнатура) определяется при первом использовании замыкания.

UPD: хотя да, это вывод типа анонимной функции, но не потому что в аргументе c функции call указаны все необходимые типы для колбэка.
0
> UPD: хотя да, это вывод типа анонимной функции, но не потому что в аргументе c функции call указаны все необходимые типы для колбэка.

А почему?
0

Тип анонимных функций определяется при первом использовании. Потому, если я все правильно понял.
Вот у вас функция: println!("{}", call(10, 10, |a, b| a + b));, а вот тут первое использование — c(a, b).
a и b имеют тип i64, потому у этой анонимной функции будет тип Fn(i64, i64) -> i64, а не потому что в сигнатуре where F: Fn(i64, i64) -> i64.


let example_fn = |a| a;
println!("{}", example_fn(String::from("Yo"))) // вот тут уже будет тип Fn(String) -> String

let example_fn2 = |a| a;
println!("{}", example_fn(2)) // вот тут уже будет тип Fn(i64) -> i64
0
Конечно, если не указывать тип у переменной example_fn, то типы в замыкании будут выведены из его аргументов.

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

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

Надеюсь понятно объяснил.
0
Вот пример того, о чём я написал:

fn main() {
    let example_fn: &Fn(String) -> String = &|a| a;
    // example_fn Принимает только String
    println!("{}", example_fn("Hello, world!".into()));

    let example_fn2: &Fn(i64) -> i64 = &|a| a;
    // example_fn2 Принимает только i64
    println!("{}", example_fn2(64));
}

0
Сейчас я еще больше запутался, чем тогда это отличается от Go?
```go
func main() {
example_fn := func(x string) string { return x }
print(example_fn(«hello»))

example_fn2 := func(x int) int { return x }
print(example_fn2(10))
}
```

Как раз в первом варианте (тот что с callback), там выводится тип анонимной функции по первому использованию и после этого выведенный тип удовлетворяет сигнатуру функции **call**.

Можете кинуть ссылку на документацию по этому?

UPD: как раз в Go тип переменной вывелся из присваиваемого значения.
0
> Сейчас я еще больше запутался, чем тогда это отличается от Go?

На расте этот случай будет описан так.

fn main() {
    let example_fn = |x: String| -> String { x };
    println!("{}", example_fn("hello".into()));

    let example_fn2 = |x: i64| -> i64 { x };
    println!("{}", example_fn2(10));
}

Тут вывод типа у переменных example_fn, example_fn2. Это равносильно примеру го.

В примере выше вывод типа происходит у аргумента замыкания. В примере с функцией call, так же вывод типа происходит у аргумента замыкания за счёт того, что в сигнатуре функции явно указан тип аргумента замыкания.
0
В примере выше вывод типа происходит у аргумента замыкания. В примере с функцией call, так же вывод типа происходит у аргумента замыкания за счёт того, что в сигнатуре функции явно указан тип аргумента замыкания.

Можете дать ссылку на документацию?


Потому что в примере:


let example_fn: &Fn(String) -> String = &|a| a;
println!("{}", example_fn("Hello, world!".into()));

Вы же указали тип функции и тип аргумента функции, вот же он &Fn(String). Функция example_fn ждет первым аргументом String. Если там не String — то ошибка компиляции.


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

0
То, о чем я говорю
fn call<F>(a: i64, _b: i64, c: F) -> i64
where
    F: Fn(i64) -> i64,
{
    c(a)
}

fn main() {
    let example_fn = |a| a;
    println!("{}", call(10, 10, example_fn));

    println!("{}", example_fn(String::from("hello"))); // ошибка
}


Тип example_fn определился при первом вызове.
0
Ладно, вот ваш же пример, но без вызова:

fn call<F>(_a: i64, _b: i64, _c: F) -> i64
where
    F: Fn(i64) -> i64,
{
    42
}

fn main() {
    let example_fn = |a| a;
    println!("{}", call(10, 10, example_fn));

    println!("{}", example_fn(String::from("hello"))); // ошибка
}


Вызова нет? Нет. Тип example_fn определён? Определён. Выводом типов, ровно из-за того, что мы передаём example_fn в функцию call с явной сигнатурой.

Запускаем. Компилятор нам любезно сообщает об ошибке о несоотвествии типов, он же в example_fn ожиадет получать i64, а мы ему String подсовываем:

error[E0308]: mismatched types
  --> src/main.rs:12:31
   |
12 |     println!("{}", example_fn(String::from("hello"))); // ошибка
   |                               ^^^^^^^^^^^^^^^^^^^^^ expected i64, found struct `std::string::String`
   |
   = note: expected type `i64`
              found type `std::string::String`


И прошу заметить что вызова с i64 не было, но он уже требует i64!
0
Да согласен.
Я все время ссылался на эту доку, про то, что вы писали так и не нашел.
+1
Читаю очередную статью о недостатках

«Есть всего два типа языков программирования: те, на которые люди всё время ругаются, и те, которые никто не использует.
© Бьерн Страуструп»


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

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

0
Все очень просто,
отрицание, равнодушие, принятие

У меня так с каждой технологией, с начала хаю, потом становится ровно, а потом слезть не могу, а с Go я сейчас между равнодушием и принятием
+12
За что я люблю язык Go:
1. На него есть спрос у бизнеса.
2. За него хорошо платят.
0
Хорошо — это в среднем по палате дешевле, чем за Java и C++, но дороже, чем за PHP.
+1
но ведь с PHP можно уйти на JS и мимикрировать под фуллстека — и будут платить совсем хорошо!
0
Совершенно точно, что это НЕ будет длиться долго, если не повторится бум базвордов, как несколько лет назад. Нам критически нужны новые базворды от авторитетных компаний: что-нибудь сравнимое с tree-shaking, SPA, PWA, typescript, RxJS, войны css-препроцессоров, несколько мажорных изменений в ES, и самое главное — больше короткоживущих фреймворков: оптимально, чтобы фреймворки устаревали за полгода-год. Только так можно сохранить оплату за формошлепство на текущем уровне, только так мы можем спасти индустрию от наплыва умников из других языков.

Если большая тройка фреймворков будет продолжать жить еще год-два, зарплаты у фронтов неизбежно упадут на уровень зарплат пхпшников пятилетней давности.
0
Лучше уходите на JS client-side, не надо пилить на нем серверную часть. Тогда я хотя бы не буду смотреть на вас с осуждением.

Ну, либо просто уходите)
0
не нуачо? Разве не были убедительными аргументы продвигателей ноды, о том, что джс за время войны браузеров стал ого-го какой язык, и надо всего-то присобачить к нему библиотку врапперов системных вызовов и что-то няшное для работы с БД? Все поверили, теперь вот радуемся результату.
0
А что не так с нодой? Накатываем typescript и радуемся жизни :)
+2
С нодой… с нодой почти все не так, имхо.

Хотя, вижу, вы тоже на позитиве.

0
Нет, к сожалению, я трезв.
У ноды есть проблемы (на мой личный взгляд), куда же без них. Но не сильно критичные, и писать, скажем, на es6/esm (а в es7 еще много вкусного сахара везут) или даже на ts вполне приятно.
0

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

0
Разве кто-то говорил о плюсах? Вы спросили: «сколько». Я вам ответил. Плюсы там в другом, впрочем, как и минусы.
0
> if err != nil {

Эм, а я тупо делал как-то вот так О_о.
По-моему, что-то такое в getting started написано было, и я не очень понимаю, почему все продолжают эти портянки тянуть в каждой статье про go :(

func (c *Config) load() {
    file, err := ioutil.ReadFile("config.json")
    failOnError(err, "Cannot open conf file")
    err = json.Unmarshal(file, &c)
    failOnError(err, "Cannot decode json")
}

func failOnError(err error, msg string) {
    if err != nil {
        log.Fatalf("%s: %s", msg, err)
        panic(fmt.Sprintf("%s: %s", msg, err))
    }
}
+3
Потому что panic — не универсальная реакция на любую ошибку.
+1
Ну добавьте еще хелпер anythingOnError() с нужными действиями и все.
Есть еще подходы вот вроде такого.
Моя непонятка была в куче портянок с «if err != nil {» в почти каждой статье, тогда как один из базовых принципов разработки — это вынос портянок однотипного кода в функцию.
0
Так в том-то и дело, что обработка ошибок — очень редко однотипный бойлерплейт, чаще всего каждой ошибке — свой подход (разве что их можно логать все, в остальном они различны).
0
Можно сделать кастомные ошибки, вон в той статье примерно описан принцип.
Можно хандлить этот самый err в хелпере и делать что нужно в зависимости от ошибки.
0
Так в том-то и дело, что обработка ошибок — очень редко однотипный бойлерплейт, чаще всего каждой ошибке — свой подход

Почти всегда на практике обработка ошибок — однотипный бойлерплейт и тогда очень иногда это не так. Посмотрите любую либу на гоу.
0
Если у вас *ошибки* обрабатываются однотипным бойлерплейтом — у меня для вас плохие новости. Ошибки должны быть правильно обработаны в каждом конкретном случае, иначе от них нет вообще никакого толка (можно тогда в любой ситуации panic делать).
0
у меня для вас плохие новости

Доктор, скажите прямо, какой диагноз?

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

То есть код, который сотни раз подряд делает однотипное return err — плохой?

0
Зависит от контекста. Если это библиотека — скорее всего нет. Если это приложение — скорее всего да. Но тут есть ещё много нюансов.
0
То есть куча
if err != null {
  return err
}

в библиотеке — это, по вашему, норм?
0
Ну добавьте еще хелпер anythingOnError() с нужными действиями и все.

Но ведь если там не будет паники, то код продолжит выполнение, так что такой прием подходит только для паники
0
«Пишите код» :)
Вы правда хотите чтобы я вас начал учить как писать код на go? :)
Вот чесслово, в документации и нескольких статьях все принципы и концепции прекрасно описаны, все раскуриваются за пару-тройку вечеров и потом пишется как надо под ваши потребности.
Я не говорю, что обработка ошибок в частности и язык вообще идеальны. Есть вещи, которые лично мне не нравятся. Но все эти особенности можно просто знать и писать код с их учетом.
ЗЫ:
Вот не понимаю людей «а почему в языке Х сделано не так как в моем любимом У? язык Х — говно».
0
А самое главное, почему просто не иcпользовать любимый Y. Так как будто все вокруг заставляют использовать Х. :)
0

Я писал на го полгода. Я лишь говорю, что ваш совет о anythingOnError не работает

0
Это был дженерик совет :)
Конечно же обработчики нужно делать под конкретно требования проекта и знания разработчика. Благо способов больше одного.
0
Я понимаю, что это был дженерик совет. Но он не помогает избавиться от ифов. Вам нужно использовать или лапшу ифов или панику, другого пути нету.
0
Если это ошибки, генерируемые третьесторонними или встроенными функциями — то смотреть по месту/требованиям. Иногда можно игнорить err и работать только с возвращаемым значением.
Если вы генерите свои ошибки, то тут уже можно сделать интересно.
Но в целом да, совсем от if'ов не уйти, но можно заметно сократить их количество.
А в других ЯП разве не так же? :)
Я лично вот сейчас играюсь со штуками вроде вот такой в TS:

type Failable<V, E> = {
    isError: true;
    error: E;
} | {
    isError: false;
    value: V;
};


Ничего не напоминает? :)
0
Новую обработку ошибок уже везут, она уже в пути. Узбагойтесь уже)
0
Писал, очень давно. «Я был молод и мне были нужны деньги» (с)
Сейчас стараюсь избегать такого, благо с тех пор многое узнал и попробовал.
+5

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

+1
Да что ж вы все так убиваетесь? Вы ж так никогда не убьетесь! :)
А если серьезно — да не пиши ты на Го, раз все так плохо. Переходи на 1С — самое оно…
0
вот будет прикол, когда гугель похоронит го в пользу еще одной своей внутренней разработке, как похоронил уже сотни похожих проектов. смешно наблюдать толпу программеров, которые всерьез воспринимают этот ужас летящий на крыльях ночи ;)
+1
Желание гугля похоронить Go, как и мнение Роб Пайка относительно умственных способностей программистов на Go, уже не важны. Языку уже условно «18 лет» и он вправе вести жизнь отдельно от родителей с их мнением по поводу будущего своего чада. А также его умственных способностей.
0
возраст не имеет отношения к зрелости языка, как говорится — некоторые с возрастом становятся умнее, а некоторые просто старше. среди го тусовки наверное только Роб Пайк и еще несколько человек могут самостоятельно принимать решения о применении того или иного языка в своих проектах — остальным просто приказали или наняли по уже сформированному тз. мораль сей басни довольно проста — можно много бить себя пяткой в грудь и развешивать свое мнение на хабрах, но если гугель вдруг закроет го — то никто из этой тусовки не в состоянии будет ни финанировать, ни развивать, ни даже тупо поддерживать такой немаленький проект. более того — сверху им опять прикажут и все смирненько перейдут на питон/руби или nodejs…
0
возраст не имеет отношения к зрелости языка

Да, конечно я имел ввиду, что язык достаточно зрел, чтобы выжить даже при закрытии.
0
да, и насчет 18 лет — я бы был поскромнее. реальные проекты за пределами гугля появились всего пару лет назад. а большинство го-программистов, которых я видел были, в свое время, не в состоянии освоить c++/js/ruby. вот их и прибило к этому берегу ;)
0
Неосиляторы — это обратная сторона медали легкости входа.
-1
дык, так в том то и дело. если бы го реально сотавлял конкуренцию для nodejs/C++/ruby — так нет. пока наблюдается интерес только со стороны devops, большинство которых видят в го что-то типа bash с сокетами
+1
Вы рекрутёр? Откуда вы знаете какой интерес к Go-программистам?
Даже беглый поиск по вакансиям, показывает, что уже сейчас можно идти работать Go-программистам, не в devops отдел:
hh.ru/search/vacancy?text=Go&specialization=1.221&area=1&salary=&currency_code=RUR&experience=doesNotMatter&order_by=relevance&search_period=&items_on_page=20&no_magic=true&from=suggest_post

И второе. Go не конкурирует с Си++/JS. Только Python/Ruby в сфере бекэнда. Иногда, в особо запущенных случаях с Java/C#. Не бекэнд на Go делать не удобно, это не универсальный язык. Только бекэнд и утилиты командной строки.
-2
первое — я CEO. второе — го действительно не конкурент c++/js. IMHO, еще лет 20 надо довести до ума язык, инфраструктуру и самое главное — людей
-1
если бы го реально сотавлял конкуренцию для nodejs

nodejs говорите? habr.com/post/337098
Создатель Node.js: «Для серверов я не могу представить другой язык кроме Go»

0
Уже вот несколько лет, как я не работаю над Node, ну, где-то, примерно с 2012 или 2013


чел отвалил с разработки еще до версии 0.8.0, и потом уперся в полумертвый го. только вот с 2012 нода поменялась на 90% и продолжает меняться, а он фишку походу уже не сечет. в свое время нам хватило встроенного в ноду cluster модуля, когда нам нужно было распараллелить задачу, потом юзали такую штукенцию:

synsem.com/EMS.js

сейчас используется другое решение, но нигде нет go и все прекрасно пашет без хайпа и горутин.
0
Конечно же 18 лет — это метафора, говорящая о том, что уже как бы самостоятельный, но живёшь с родителями.
0
ага, и ничего не поделать с тем, что глухая бабушка на кухне смотрит в 100500 раз санта барбару. так сказать — полная аналогия ;)
0
Да, это так. Язык пока несовершенен, но в одной узкоспециализированной области чрезвычайно удобен.
0
ну да, докер и кубышка — вот и вся его область. даже не слышал о более менее серьезных го проектах за пределами devops. зрелость языка как раз и имеряется областью его применимости, а не левым возрастом.
0
Если вы не слышали, не значит, что их нет. Многие компании пилят тихонько бекэнд на Go (включая Яндекс) и рады. То, что они не пиарят это на каждом углу, ну пока не в таких объёмах, чтобы это как-то заметно просачивалось.

Но точно вам скажу, что я пилю бекэнд на Go, и в случае смены работы я найду достаточно мест, где смогу делать то же тамое.
0
как говорили классики — «пилите Шура, они золотые». странно думать уже о смене работы на таком «перспективном» направлении, но могу отметить, что опасения не напрасны ;)
0
справедливости ради, можно отметить, что и в PHP в свое время владывали разные компании, например мордокнига. но очень скоро поняли, что не в коня корм.
0
и что там мы видим? есть frontend аля Vue? есть desktop аля Electron? есть android/ios apps аля Xamarin? увы, я там вижу только дешевый хайп сырых либ и байдингов к C++. ни один вменяемый заказчик не заплатит ни цента за эту чушь.
0
А видим мы там, во-первых, совсем не только «докер и кубышка», и во-вторых, например, кучку утилит для работы с аудио, кучку других разных утилит (для css/sass например). Еще мы видим, например, syncthing (андроид клиент для которого написан на том же самом go), или minio. Можете даже сходить в раздел Financial или Game Development, или даже в GUI/Machine Learning.
А можете уточнить название конторы, где вы CEO? Терзают меня смутные сомнения…
0
а… утилиты… command line… так тут никто и не спорит. я уже говорил — тупо bash с сокетами. а целых два android приложения — это несомненная победа го над всеми языками сразу. ну и бесполезные сомнения пусть терзают дальше, так как конторами — не заведуем
0
Вы сейчас рассуждаете как 15 летний школьник, который вчера попробовал на React Native написать мобильное приложения и ничего не понимает в бэкэнд инфраструктуре.
0
так не в коня этот гавенный корм. научитесь готовить, а потом кормите.
0
о, люблю этот момент — когда оппонент не в состоянии ответить на весь спектр аргументов и выбирает только тот с которым он хоть как-то знаком. ok, возьмем backend — так предьявите невиданные доселе в мире c++/js бонусы, которые предоставляет go!
+3
ok, возьмем backend


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

так предьявите невиданные доселе в мире c++/js бонусы, которые предоставляет go (для бекенда)


Относительно C++ бонус достаточно очевиден. Go проще в разработке и поддержке. Давайте говорить правду: C++ может всё. Вот просто всё, без исключения. При этом, признайте уже, наконец, везде, где есть разумная по трейд-оффу возможность не использовать C++, C++ не используется.

По JS. Кхм… JS на бэкенде? Ну вот, в самом деле, вы серьезно? Полуторагигабайтный бандл непонятного однопоточного js-лапшеобразного кода с непонятными корнями и браузером внутри, чтобы это как-то работало… Вот это вы считаете нормальным для бекенда?
-1
так правда сурова — го не используется там, где уже давно и прочно царит C++/Python/Ruby/Node, а это практически все области ПО, включая бэкенд. и даже когда дело доходит до трейдофа, го не катит, так как писать на нем что-то большое просто невозможно, а для мелкого годится все что уже знает команда. JS на бэкенде — легко! и никаких проблем с проф уровнем программистов и их взаимозаменяемостью. про полтора гигабайта — закопайте уже phantom.
+1
git clone https://github.com/golang/go.wiki.git
cd go.wiki
git log FromXToGo.md |grep Dat|cut -d ' ' -f 8|uniq -c

...
8 2018
8 2017
28 2016
...


походу хайп затих еще в 2016 ;)
0
так правда сурова — го не используется там, где уже давно и прочно царит C++/Python/Ruby/Node, а это практически все области ПО, включая бэкенд.


Немножко суровой правды, как бы вы ее ни отторгали: Go используется.
-1
так никто и не спорит, докер и кубышка — его основная ниша на данный момент. и все это — пока гугель спонсирует движуху. зрелость языка есть сумма очень многих компонент, туда входит много чего, for ex.: кривая разивития, наличия стандартов, качественно разработанных библиотек, надежной поддержки, широта области применения, наличие проф. commnity и т.д. Так вот по совокупности факторов назвать этот язык зрелым пока не представляется возможным.
уверен что вы как яркий представитель community возьмете бразды правления в свои руки и выбьетесь со временем в топ ;)
0
есть frontend аля Vue?


Нет, конечно, Go не предназначен для фронта.

есть desktop аля Electron?


Нет, конечно, Go очень неудобен для гуя.

есть android/ios apps аля Xamarin?


Конечно же, нет, Go не предназначен для мобильных приложений.

Есть, конечно, усраться-универсальные языки, например Java, C++, C#. На которых можно всё. Только из-за этого «можно все» неизбежны компромиссы, при наличии которых именно каждое направление в отдельности может причинять некоторые неудобства.

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

ни один вменяемый заказчик не заплатит ни цента за эту чушь.


Платят. И даже больше цента.
-1
не можем, не умеем, не будем и даже не пртетендуем — что собственно и требовалось доказать. вернувшись к баранам имеем — 99% докер и еще какой-то хайп. а с таким раскладом — ему еще далеко до того как стать зрелым языком. может лет через 20…
0
И есть Go, который проектировался с одной целью — бекенд. И на нем удобно пилить бэкенд. Удобнее, чем на любом из «универсальных» языков.

Подозреваю, что у него нет библиотек уровня hibernate/entity framework/doctrine, подобные библиотеки позволяют не писать тонну кода.


Или есть такие библиотеки? Если нет, то в чем удобство go, перед универсальными ЯП?

0
Подозреваю, что у него нет библиотек уровня hibernate/entity framework/doctrine, подобные библиотеки позволяют не писать тонну кода.


Собственно, ORM'ы в Go не в моде. Да и в принципе, ORM как таковой полезен далеко не на всем спектре задач. Даже более того, полезность его ограничена сферой, требующей развесистой бизнес-логики поверх РСУБД. Собственно, «развесистая бизнес-логика» — отнюдь не вотчина Go. Там безраздельно царят Java и .NET, и никто их оттуда «вытеснять» и не собирается.

Если нет, то в чем удобство go, перед универсальными ЯП?


Понимаете, это из цикла «как что-то хорошее». В кеширующих серверах, балансировщиках, аггрегаторах, гейтах и шлюзах данных ОРМ не нужен. Более того, в ряде случаев он даже вреден…

И нет никакого «удобства перед универсальными ЯП». Есть удобство «в сравнении с каждым из универсальных ЯП». Вот эти «Go против всего мира» — это забавная олимпиадная дисциплина, но каждая задача подразумевает решение на определенном языке, а потому сравнение «1 язык против 100500 других» заведомо некорректно. Берется множество «более других языков» и поштучно сравнивается с Go. Если Go оказался предпочтительным в каждой сравниваемой паре на определенной задаче, Go хорош.

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

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

Я не ставил бы этот вопрос, если бы не ваше утверждение:


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



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

Вопрос был почти такой же, если не придираться к словам.
Вы ответили:


Там безраздельно царят Java и .NET, и никто их оттуда «вытеснять» и не собирается.



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

Уникальная фича никому не нужна, нужно то что упрощает разработку и уменшает ошибки.
Хотите сравнивать конкретный юзкейс с конкретным ЯП, пожалуйста, расскажите, чем Go удобнее чем Rust, для любой задачи.
Желательно с примерами кода на обеих ЯП.

-1
Хотите сравнивать конкретный юзкейс с конкретным ЯП, пожалуйста, расскажите, чем Go удобнее чем Rust, для любой задачи.


Ну это прям вообще элементарно. В любой задаче, адекватно ложащейся на концепцию горутин, Go будет лучше и проще. Round-robin балансировщики, кеширующие прокси, REST-серверы и подобные вещи.
0

Связка tokio+futures-await так же удобны в использование как горутины.
Правда, для await! пока нужно использовать nightly версию языка.




А так горутины удобны, да, но это говорить лишь об одном из критерий, в котором у go хорошее решение.


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


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




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

Голословное утверждение.
(в go может и удобно реализовать такие задачи, но не удобнее чем в ржавчине)

+2

Я уже почти 4 года программирую на Go: мне не нужно генерировать шаблонный, мне не нужны дженерики, уже давно есть удобный пакетный менеджер (сейчас еще модули завезли), компилятор очень сильно помогает (зависит с чем сравнивать, да?). В основном я пишу/писал web сервисы (нагрузка от 30к до 400к RPS), стрим процессинг с Kafka, тулзы для Kubernetes, etc.
Мне нравится Go, мне нравится вот это if err != nil, после Java я совсем по другому начал относится с ошибкам.


И я совсем не понимаю, почему пользователи Хабра в каждом посте про Go начинают бегать туда-сюда с криками насколько Go ужасный язык, при этом 80% из них на Go не программируют. Начинают сравнивать отдельные фичи Go с фичами других языков — а вот в Х яызке это лучше, а вот в Y языке другое лучше:
— Но я использую Go, мне нравится X фича и для моих задач Go подходит идеально. Тем более у нас есть опыт на Go.
— Ты не понял, Go говно, там if err != nil, вот тебе Rust/C++/Haskell/JavaScript там еще лучше. Монад ведь нету в Go? Воот, ерунда ваш Go.


В каждом посте овер 200 комментов, больше половины из которых вот такие бессмысленные срачи.

0
Правда, для await! пока нужно использовать nightly версию языка.


Ну т.е. в стейбле этого еще нет. И вы предлагаете nightly тащить на прод… Ну, как бы, Г-дь вам судья, конечно.

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

теряете например нормального менеджера пакетов, дженериков, АТД, паттерн матчинг, и помошника компилятора.


Чем вам пакетный менеджер Go не угодил?

Отсутствие дженериков — это, конечно, аргумент, но… а) не так больно, как вам кажется, б) используя ваши же методики аргументации — обещали «подвезти» в Go 1.6 => 2.0.

Это сложно назвать удобством, когда элементарного шаблонного кода надо генерировать…


Ну, как бы, за пару лет практики с необходимостью ручной генерации сталкивался пару раз. Есть, конечно, вещи вроде fasthttp или grpc, swagger и т.д. Но в fasthttp кодогенерация дает ощутимый прирост в производительности, а потому оправдана. А для grpc, swagger и прочих декларативных API-описаний, собственно, кодогенерация — это нормальный подход, везде так.

в go может и удобно реализовать такие задачи, но не удобнее чем в ржавчине


Вы же сами сказали, что в расте (пока еще) нет полноценной замены горутинам… Т.е. пока еще удобнее, а то, что будет «потом когда-нибудь»… Хм, мне код сейчас надо писать.
0
Отсутствие дженериков — это, конечно, аргумент, но… а) не так больно, как вам кажется, б) используя ваши же методики аргументации — обещали «подвезти» в Go 1.6 => 2.0.

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


Чем вам пакетный менеджер Go не угодил?

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


Ну, как бы, за пару лет практики с необходимостью ручной генерации сталкивался пару раз. Есть, конечно, вещи вроде fasthttp или grpc, swagger и т.д. Но в fasthttp кодогенерация дает ощутимый прирост в производительности, а потому оправдана. А для grpc, swagger и прочих декларативных API-описаний, собственно, кодогенерация — это нормальный подход, везде так.

Ок, видимо избегают его.


Вы же сами сказали, что в расте (пока еще) нет полноценной замены горутинам… Т.е. пока еще удобнее, а то, что будет «потом когда-нибудь»… Хм, мне код сейчас надо писать.

Мне кажется вы видете в горутин молотока.
Меня же интерисует не только молоток, но и другие инструменты.
Вы вот промолчали про АДТ, паттерн матчинг, про компилятор с продвинутым выводом типов.
Это далеко не все, но важные вещи.




Ещё раз напомню, что мы обсуждаем (чтобы далеко не уходить от темы):


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

Это неверно для любого языка, особенно на бэкенде.

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


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

Вы же, надеюсь, понимаете, что никто не кинется переписывать уже работающее Go-решение на Rust`е просто потому, что туда «уже скоро завезут фичу, которая сделает его конкурентноспособным»? Найтли-фичи == еще нет в языке (с точки зрения продакшена).

например не хватает репозиторий


Гитхаб вам чем не угодил, собственно?

Ок, видимо избегают его.


Хм, вы уверены, что у вас есть опыт энтерпрайз-разработки? Вот, например, в энтерпрайзе очень любят SOAP, а это всегда кодогенерация, что на Java, что на C#…

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


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

Вы вот промолчали про АДТ, паттерн матчинг, про компилятор с продвинутым выводом типов.


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

Это неверно для любого языка, особенно на бэкенде.


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

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


Гитхаб вам чем не угодил, собственно?

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


Вы же, надеюсь, понимаете, что никто не кинется переписывать уже работающее Go-решение на Rust`е просто потому, что туда «уже скоро завезут фичу, которая сделает его конкурентноспособным»? Найтли-фичи == еще нет в языке (с точки зрения продакшена).

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

Хм, вы уверены, что у вас есть опыт энтерпрайз-разработки? Вот, например, в энтерпрайзе очень любят SOAP, а это всегда кодогенерация, что на Java, что на C#…

А вы уверены, что прочитали мой коммент?
Речь о такого типа кодогенерацию:
https://github.com/clipperhouse/gen


В общем дело не только в кодогенерации, а в том что отсувствие дженериков, могут приводить к кодогенерацию в некоторых случаев, может приводить к дублированию, а ещё может приводить к interface{}...


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

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


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

И как примитивная система типов может быть удобнее?


Не уловил, откуда взялся такой вывод…

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


Я не уловил откуда у вас взялся обратное, пока ваши аргументы:


  • Собственно, «развесистая бизнес-логика» — отнюдь не вотчина Go. Там безраздельно царят Java и .NET, и никто их оттуда «вытеснять» и не собирается.

  • В Go система типов примитивна, и без изменения оной перечисленные вами фичи, мягко выражаясь, малоосмыслены.

  • Ну и про горутин конечно же, но почему то упорно игнорируете, всё остальное.
    Для вас, если в стабильной версии языка X, можно стартанут легкие потоки с go ..., и в языке Y с .and_then().and_then(), то автоматически можно игнорировать все удобство языка Y, логично!


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


Еще раз повторю, на всякий случай: «футуры» и «горутины» — разные вещи.

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


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

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


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

люди вон патчят JVM, что гораздо страшнее, и на продакшне.


Ну, как бы, сильно напоминает «ну и что, что я ковыряю в носу? Вот Паша вообще описался вчера». Ну, т.е. каким боком вообще патчи JVM относятся к обсуждению Go vs Rust?

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


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

И без этого сахара раст более конкрунтоспособен чем Go


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

В общем дело не только в кодогенерации, а в том что отсувствие дженериков, могут приводить к кодогенерацию в некоторых случаев, может приводить к дублированию, а ещё может приводить к interface{}...


И? В чем конкретно проблема того же самого interface{}?

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


Ну да, в Rust можно накрутить сборщик мусора, реализовать горутины и… получить еще один Go? Зачем?

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


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

И как примитивная система типов может быть удобнее?


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

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


У нас с вами разные наблюдения. Я видел старты на Go, Java, C#, Kotlin'е, видел даже на Dart'е. Кто у нас из «свежих» на расте «стартанул»?

Для вас, если в стабильной версии языка X, можно стартанут легкие потоки с go ..., и в языке Y с .and_then().and_then(), то автоматически можно игнорировать все удобство языка Y, логично!


Еще раз уточню, на всякий случай:

go func() и ".and_then().and_then()" — это разные вещи. Существенно разные.
0
Еще раз повторю, на всякий случай: «футуры» и «горутины» — разные вещи.

И?
Я не утверждал обратное, они решают одни и те же задачи с разными способами.
По сути футуры можно использовать как горутин, пример: выше описанная библиотека, которая в nightly, внутри использует футуры.


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

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


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

Это мягко говоря не ваше дело.


Ну, как бы, сильно напоминает «ну и что, что я ковыряю в носу? Вот Паша вообще описался вчера». Ну, т.е. каким боком вообще патчи JVM относятся к обсуждению Go vs Rust?

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


Ну да, в Rust можно накрутить сборщик мусора, реализовать горутины и… получить еще один Go? Зачем?

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


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

Интересно, откуда такие выводы?


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


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

В JS тоже достаточная система типов, по вашей логике.
Ну и как вы определяете какого уровня системы типов достаточная?


И? В чем конкретно проблема того же самого interface{}?

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


У нас с вами разные наблюдения. Я видел старты на Go, Java, C#, Kotlin'е, видел даже на Dart'е. Кто у нас из «свежих» на расте «стартанул»?

Тогда ваше утверждение:


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

Неверная.


Еще раз уточню, на всякий случай:
go func() и ".and_then().and_then()" — это разные вещи. Существенно разные.

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

0
В общем зеленых потоков есть(библиотеки), удобного интерфейса как в го нет, надеюсь так понятнее.
Пропишите +1 за удобство использование в Go, и сравните дальше.


Ну вот и я о том же, что работа с green fibers в Go удобнее. И, видимо, это достаточная фишка для того, чтобы Go был популярен. Не я говорю, практика показывает.

Вы упускаете то, что в продакшне делают вещи похуже, поэтому такой пример привел,


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

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


Совершенно правильным будет стараться не использовать nightly-фичи, не патчить JVM, не использовать грязные хаки до тех пор, пока это возможно. Ну, если вы, конечно, не Гугл, не Фейсбук, не Майкрософт, не (подставьте название вашей любимой миллиардной компании) — они себе и не такое позволить могут, у них есть ресурс и деньги. Вы себе поддерживать свои собственные грязные хаки позволить не можете — пупок развяжется.

и использовать nightly, если решение не сырая.


Собственно, оно на то и nightly, что решение еще сырое. Как только оно будет не сырое, оно в stable попадет. Вот тогда и используйте.

Интересно, откуда такие выводы?


Выводы, в основном, от того, что в проде этого решения еще никто не видел. Понимаете, практика применения крупными конторами — это бонус. Очень весомый бонус. Брать в прод что-то, по чему ни у кого нет экспертизы, и даже решение проблем загуглить негде — ну это такое себе (опять же, если вы не Гугл, Майкрософт, whatever)

Я уж не говорю о тулинге вокруг. Go таки успел обрасти «мясцом» в виде grpc-биндингов, swagger-генерацией и вот этими всеми вещами. В раст, пока еще, не подвезли.

Понимаете, я не говорю, что Раст плохой. Нет, он, вероятно, даже хороший. По крайней мере моя экспертиза в Rust'е (в виде чтения мануалов и пары хелловорлдов) не позволяет мне говорить, что Rust плох. У меня тупо нет на нем практики.

А вот то, что он, пока еще, не готов, говорить можно. Тем более, что вы сами это подтверждаете. Соответственно, спор, мягко выражаясь, бессмысленный. Да, в Go не все хорошо. И абстракции там текут (посмотрите хотя бы на слайсы), и дженериков временами не хватает (но редко, не настолько, насколько вам кажется), и обработка ошибок немного многословна. Но вся приколюха в том, что Go уже есть и работает, а Rust пока еще не готов. Да, собственно, и слабо верится, что он Go сможет вытеснить. Элементарно в нишу не пролезет. Rust больше в нишу c/cpp пытается вкорячиться, а Go туда и не претендует.

Ну и как вы определяете какого уровня системы типов достаточная?


Это же элементарно. Если ее достаточно для решения моей задачи, значит, она достаточная…

Насколько я понимаю, это общий интерфейс для всех типов(кроме скаляров кажетя?).


За что вы так не любите скаляры? Ровно так же пролазят в interface{}, как и все остальные.

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


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

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

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

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

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

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


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

Как по мне, это всё равно лучше чем тащит зелёные потоки в ядро языка.


Это вы сетовали, что в ядре Go ОРМ нет?))

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


Это не в Go. В Go любой код конкуррентен by design.
0
Ну вот и я о том же, что работа с green fibers в Go удобнее. И, видимо, это достаточная фишка для того, чтобы Go был популярен. Не я говорю, практика показывает.

Популярнее != удобнее
Интересно, начали сравнивать возможностей языка, когда оказалось они у Go мягко говоря примитивна, то переключились на попуряность.
Напомю, что Go появилось раньше(стейбл), и маркетинг гугла сильнее.


Выводы, в основном, от того, что в проде этого решения еще никто не видел.

3к звезд на гитхабе вам ни о чем не говорить?


Я уж не говорю о тулинге вокруг. Go таки успел обрасти «мясцом» в виде grpc-биндингов, swagger-генерацией и вот этими всеми вещами. В раст, пока еще, не подвезли.

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


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

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


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

Допустим.
Если есть интерфейс с методом add(a int), получается мы можеам использовать любой интерфейс с таким метом, верно?
Если так, это не может вызвать проблемы?


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

Я понимаю, что с интерфейсами всё можно решить, но его применение ограниченно, и вам всегда приходиться отталкиваться от этого.
Вот простой пример:


trait Foo {
    type Bar = u32 + f32;
    fn baz() -> Bar;
}

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

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


Это вы сетовали, что в ядре Go ОРМ нет?))

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


Это не в Go. В Go любой код конкуррентен by design.

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


Или вы о том, что на нем в любом месте легко можно писать горутин, что же, правильно, но на нем ещё можно в любом месте выстрелить себе в ногу(привет примитивная система типов).

0
Сначала это:

В общем зеленых потоков есть(библиотеки), удобного интерфейса как в го нет, надеюсь так понятнее.
Пропишите +1 за удобство использование в Go, и сравните дальше.


А теперь это:

Популярнее != удобнее


Вы уж определитесь…

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


Переключились в контексте «конкуррентноспособности Rust». Собственно, популярность — прямое следствие конкуррентноспособности. И с этим у Rust пока еще не очень. Я, в смысле, о том, что Rust просто еще не готов. Но, конечно, уже подает надежды )

Напомю, что Go появилось раньше(стейбл), и маркетинг гугла сильнее.


Rust — начало разработки 2006г. Официально представлен в 2010г.

Golang — начало разработки 2007г. Официально представлен в 2009г.

Не могу сказать, что прям офигеть какая разница в возрасте…

3к звезд на гитхабе вам ни о чем не говорить?


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

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


В чем суть драмы? Ну сунули, и что?

Если есть интерфейс с методом add(a int), получается мы можеам использовать любой интерфейс с таким метом, верно?


Мы можем использовать любой объект, реализующий интерфейс с этим методом (к слову, конкретно метод `add` в интерфейсе нереализуем, видимо, должен быть `Add`). Собственно, что должен делать этот метод? Видимо, прибавлять целочисленное значение к объекту? Ну вот к структуре либо можно прибавлять, и тогда все норм, либо нельзя, и тогда не норм. Но если не норм, то и метода этого у нее нет, а значит, она «не влезет».

Если так, это не может вызвать проблемы?


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

type Bar = u32 + f32;


Это, видимо, union? Или ограничение на допустимые типы?

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

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


Собственно, у вас так же.

Не совсем, это зависить от того как вы его применяете.


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

но на нем ещё можно в любом месте выстрелить себе в ногу(привет примитивная система типов).


Сложная система типов никак не защищает от выстрелов в ногу. Честно-честно.

Понимаете, есть два полюса: «примитивность» vs «комплексность». Примитивные — понятны, комплексные — «типа, безопасны». Вы просто размениваете логические ошибки на примитивной системе типов на ошибки абстракции на комплексной. Всего то и делов.
0
Сложная система типов никак не защищает от выстрелов в ногу. Честно-честно.

Пример 1, реальный.
https://doc.rust-lang.org/nightly/std/fs/struct.File.html
(этот тип позволяет работать с файлами)
Сложная система типов в расте (а именно, деструкторы и правила владения) гарантирует, что файл обязательно будет закрыт. Мне вообще не нужно думать про какие-то там defer f.Close() — оно просто работает.
(Формально, конечно, можно добиться утечки, но это надо сделать явно.)
Пример 2 — зависимая типизация (Тут где-то в комментах deadf00d ходит, он хорошо умеет ее впаривать)). Она позволяет выразить на уровне типов сколь угодно сложные инварианты, в том числе:


  1. Сокет, в который записали нечетное количество байт.
  2. Тройка из векторов, где у первого и третьего одинаковый размер, а второй длиннее.
    Она позволяет вообще гарантировать отсутствие паник в рантайме и доказать корректность программы.

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

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


Эталонно… В палату мер и весов, простите.

Давайте немного обсудим конкретно это ваше высказывание.

1. Про часть «в расте». Раст в этом плане не уникален. От слова «вообще». Конструкторы/деструкторы существуют практически во всех ООП-языках. А уж про RIA… Собственно, оно задолго до Rust придумано. Годах этак в 80-х прошлого века.

2. Конструкторы и деструкторы практически не имеют отношения к «сложной системе типов». RIA — это фича, основанная на «сроке жизни» объекта. Для того, чтобы это работало, необходимо быть уверенным, что деструктор будет вызван ровно в момент выхода переменной из области видимости. Т.е. это «стандарт де факто» в языках с ручным управлением памятью, в некоторых случаях неплохо работает в языках со счетчиком ссылок, вообще не работает в языках с GC. К системе типов отношения не имеет.

Она позволяет вообще гарантировать отсутствие паник в рантайме


Да-да, охотно верю…
0
Раст в этом плане не уникален

Естественно. Большая часть фич раста, и RAII в том числе, — баян. Даже довольно новаторские (например лайфтаймы) уже были в других языках. Но я и не говорил другого. Вы же сами предлагали сравнивать два конкретных языка, а не язык Х с объединением фич всех остальных язык.


  1. Действительно, деструкторы не относятся к система типов, они реализованы "сбоку" (по крайней мере в расте). Но лайфтаймы к системе типов относятся.

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


  1. Да-да, охотно верю…


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

-1
А то, что деструкторы не работают на языках с GC — это имхо топ-2 причина не использовать GC (первая — это производительность).


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

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

В общем и целом, до тех пор, пока GC-язык обеспечивает производительность/стабильность в пределах спецификации/ТЗ, писать будут на GC-языках. Просто потому что дешевле. Се ля ви…

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


Конечно, не нужны. В идеальном мире.

Тут поинт, как мне кажется, в чем:

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

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

И вот тут мы и подходим к ключевому моменту «популярности Go относительно Rust»: ошибки проектирования, как правило, обходятся ощутимо дороже, чем ошибки имплементации (в части исправления).

Теперь добавим чуточку «контекста» к этому высказыванию: agile-методики стремительно набирают популярность в своем стремлении «закопать» классический «водопад».

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

И вот именно в этой среде Go стремительно «набирает очки» за простоту рефакторинга и возможность по-быстрому «выродить» случаи глобальных пересмотров архитектуры в атомарные фиксы.

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

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

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

Я школьник)


Если что-то где-то «прибыло», то совершенно однозначно где-то что-то «убыло».

Прибыла необходимость (иногда) прописывать лайфтаймы, но в основном и без них всеп росто работает.


Ручное управление памятью сложнее
циклической конструкции

Опять же, по моему опыту (который, естественно, очень специфичный), подсчет ссылок нужен довольно редко (я использую его на пару с мьютексами), а ручное управление памятью — еще реже. В основном, мой код прекрасно ложится на деструкторы. И я не думаю, что добавление 100500 правил бизнес-логики приведет к циклическим ссылкак. Даже если приведет — можно конкретно для этих высокоуровненых объектов бизнес-логики включить GC (например, в Linux Kernel есть сборщик файловых дескрипторов). При этом большая часть проекта продолжит работать без него и не получит его минусов.
А по поводу его минусов — достаточно запустить CodeBlocks и CLion (TL;DR — на слабых компах, пока запустится clion или visual studio можно успеть установить CodeBlocks).

-1
Я школьник)


А, ну тогда волноваться рано. Все еще пройдет).

Прибыла необходимость (иногда) прописывать лайфтаймы, но в основном и без них всеп росто работает.


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

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

А по поводу его минусов — достаточно запустить CodeBlocks и CLion (TL;DR — на слабых компах, пока запустится clion или visual studio можно успеть установить CodeBlocks).


Ну это уже субьективщина. Тот же visual studio написан ни разу не на GC-языке, вполне себе C++. И я бы не сказал, что IDEA тормозит больше.

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

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


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

0
Значит, вы необходимость продумать задачу перенесли на сильно более поздний этап.


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

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

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

И разница тут не в «монументально продуманный продукт» против «фигак-фигак и в продакшен», потому что «оптимистичный подход» совершенно не страхует от говнокода в проде (есть еще сроки и «меняющиеся бизнес-требования»). Разница тупо в приоритетах и распределении планирования. Классика: «водопад» против «гибких методик». Т.е. вам нужен либо большой и консистентный продукт сразу без промежуточных итераций, либо минимально-работающая вещь с итеративными изменениями в функционале. В первом случае «водопад» и «мощная система типов», во втором — «аджайл» и «примитивные инструменты».
+1
Не совсем так, от необходимости думать вас никто не избавит, и суммарная «комплексность» задачи остается неизменной вне зависимости от языка. Разница в распределении нагрузки планирования.

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


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

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


Если бы я без ошибок всё делал, то мне бы вообще никакая типизация не нужна бы была, писал бы на голом лиспе (ну или на С).


И к слову о рефакторинге без страха — когда вы пишете тесты? Когда закончили проект и знаете, что дальше архитектуру и реализацию менять не будете, или по мере написания?

0
Неа, как раз наоборот. И проектирую я с ошибками, и пишу с ошибками. Просто цена ошибки оказывается, вопреки стереотипам про тяжеловесность типов, маленькой. Если я понял, что ошибся, то я сажусь и меняю типы, рефакторю, и делаю это без страха за то, что уже имеющийся код сломается. Рефакторить без страха — это вообще очень круто.


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

И к слову о рефакторинге без страха — когда вы пишете тесты?


Пишу, когда они имеют смысл.
+1
На каком из языков вы пишете

На плюсах и на хаскеле. Впрочем, плюсы — это не про рефакторинг без страха, так что остаётся только хаскель.


Ну и всякая там идрисня ещё, но за неё денег не плотют :(


чего из фич типизации этого языка вам не хватает

ADT и паттерн-матчинг, GADTs, тайпклассы (и как следствие дженерики параметрический полиморфизм, ага), DataKinds (тут сначала было долгое описание, что это такое, но потом я решил, что сослаться на конкретную фичу может быть проще), семейства типов, фундепы и вот это вот всё.


за исключением дженериков, конечно, т.к. именно они «рефакторингу без страха» помогают от слова «никак»

Почему?


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

-1
Впрочем, плюсы — это не про рефакторинг без страха


Ну вот, понимаете же, что не это главное, раз на плюсах пишете…

ADT и паттерн-матчинг, GADTs, тайпклассы (и как следствие дженерики параметрический полиморфизм, ага), DataKinds (тут сначала было долгое описание, что это такое, но потом я решил, что сослаться на конкретную фичу может быть проще), семейства типов, фундепы и вот это вот всё.


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

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


Кхм. Ну, собственно, у нас есть два сценария.

Первый — типичная коллекция, в которой нам тупо пофиг, что в нее сунули, т.к. с самими данными мы не работаем, просто сортируем, перекладываем и т.д. и т.п. В этом случае, собственно, нам более-менее достаточно иметь гошный interface{}, и при рефакторинге точно так же ничего не надо будет трогать.

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

Ну и про дженерики — ну, завезут скоро, чо придираетесь-то?
+1
Ну вот, понимаете же, что не это главное, раз на плюсах пишете…

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


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

Нет, интерфейсами она не перекрывается. Интерфейсами перекрываются максимум тайпклассы с одним параметром.


сетования на то, что Go не относится к семейству функциональных языков…

Мне бы всё это и в плюсах не помешало. Императивность или функциональность не настолько сильно связана с мощностью системы типов.


В этом случае, собственно, нам более-менее достаточно иметь гошный interface{}, и при рефакторинге точно так же ничего не надо будет трогать.

Ага, а в С при рефакторинге void* трогать не надо, во там безопасно и удобно всё!


При рефакторинге это надо будет как минимум проверить.


Ну и про дженерики — ну, завезут скоро, чо придираетесь-то?

Чо, прям с тайпклассами? Можно будет написать


foo :: MonadReader String m => m a
foo = do
  str <- ask
  ...

?

0
Пишу из-за производительности, имеющихся библиотек или имеющихся проектов. Во всех трёх случаях Go не подходит по очевидным причинам.


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

Мне бы всё это и в плюсах не помешало. Императивность или функциональность не настолько сильно связана с мощностью системы типов.


Ну, собственно, в C++ только хаскеля внутри и не хватает для полного счастья, ага… Чтобы уж точно гарантировать, что никто не осилил спецификацию языка целиком…

Ага, а в С при рефакторинге void* трогать не надо, во там безопасно и удобно всё!


В тупой коллекции — не надо. А зачем, собственно?

Можно будет написать (дальше ваш код)


Свят-свят-свят. Не можно будет, слава Б-гу. Зачем превращать язык в викторину «Угадайте, что делает этот код (из раздела Вопросы для Senior-разработчиков)».
+1
Очевидно, все три случая совершенно законно считать субъективными очевидными причинами?

Нет, они объективные. Субъективно только конкретное множество имеющихся проектов, необходимых библиотек и всего такого, и в рамках этого же аргумента кто-то может выбрать Go вместо плюсов или хаскеля. Или, не знаю, PHP.


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


Ну, собственно, в C++ только хаскеля внутри и не хватает для полного счастья, ага… Чтобы уж точно гарантировать, что никто не осилил спецификацию языка целиком…

А чего там, ядро хаскеля — то ли 70, то ли 100 страниц. Капля в море по сравнению с имеющейся спекой.


В тупой коллекции — не надо. А зачем, собственно?

Чтобы в типе коллекции явно торчал тип хранящихся там элементов, чтобы я точно поменял все связанные с ней места.


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

Удивлён, что с таким подходом не выкинули и функции — там тоже по функции не ясно, кто, где и когда её вызывает.

0
Нет, они объективные. Субъективно только конкретное множество имеющихся проектов, необходимых библиотек и всего такого, и в рамках этого же аргумента кто-то может выбрать Go вместо плюсов или хаскеля. Или, не знаю, PHP.


У вас очень странные представления об объективности и субъективности. Понимаете, объективная точка зрения — это такая штука, которая не зависит от субъекта, ее выражающего. И вы в качестве объективного аргумента представляете «имеющиеся (очевидно, у вас) проекты». Вы же понимаете, что у меня, например, проекты несколько другие, и сама идея перевести их на C++ может вызвать искреннее удивление у всех, кто в них задействован? В общем, считать аргумент «лично у меня такие проекты, для которых С++ лучше» — хм… странно как-то.

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

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


Правильно, потому что числодробилкам нужно ручное управление памятью и минимальный оверхед. Инструмент же под задачу выбирается, а не задача подгоняется под любимый инструмент. На С++, как правило, не пишут веб-сервисы, например (за исключением очень редких, зачастую эзотерических случаев). Да-да, есть и исключения, никто не отрицает. Есть же, в конце концов, операционная система на JS, это же не делает ее хорошей идеей…

А чего там, ядро хаскеля — то ли 70, то ли 100 страниц. Капля в море по сравнению с имеющейся спекой.


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

Чтобы в типе коллекции явно торчал тип хранящихся там элементов, чтобы я точно поменял все связанные с ней места.


В самом типе коллекции-то он вам зачем? Ну, собственно, если уж он вам так нужен, welcome! В Go ровно две коллекции — слайс и мапа, и там вы тип вполне знаете…

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

Я ровно про это и написал: отсутствие необходимых библиотек или имеющаяся кодовая база — это объективный факт. Просто для моих проектов это C++, а для ваших — Go.


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

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


И после этого вы будете утверждать, что элементов «оверинжиниринга» и «перегруженности» в С++ нет?

А я это где-то утверждал? :)


В самом типе коллекции-то он вам зачем?

Чтобы знать, что его можно сравнивать, хешировать или чего там ещё делать.


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

0
Я ровно про это и написал: отсутствие необходимых библиотек или имеющаяся кодовая база — это объективный факт. Просто для моих проектов это C++, а для ваших — Go.


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

Ну, т.е., чувствуете разницу? Если что-то лушче подходит для вашей ситуации — это субъективный факт.

Смотря насколько ручное.


Хорошо так ручное, прямо сильно ручное, я бы сказал.

Чтобы знать, что его можно сравнивать, хешировать или чего там ещё делать.


Чтобы его можно было сравнивать, хешировать или еще что-то там делать, это таки «сценарий #2», при этом оно вполне решается интерфейсами, или компараторами и прочим, прикрученным сбоку (вас же не смущает такой подход, вы же приветствуете implicit?)

чем если оно там в рантайме через interface резолвится.


Ну, собственно, не совсем так оно происходит…
+1
«Я в теплой куртке, мне тепло» — субъективное наблюдение. «Вы вышли на улицу в рубашке и шортах» — субъективно вам холодно.

«Для разных людей одновременно могут быть наиболее комфортными разные виды одежды.» — моё утверждение.


Это как-то, впрочем, так очевидно, что мне уже несколько некомфортно об этом спорить.


Чтобы его можно было сравнивать, хешировать или еще что-то там делать, это таки «сценарий #2», при этом оно вполне решается интерфейсами, или компараторами и прочим

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


вас же не смущает такой подход, вы же приветствуете implicit?

Где это я его приветствую?

0
«Для разных людей одновременно могут быть наиболее комфортными разные виды одежды.» — моё утверждение.


Изначально утверждение было другое.

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


Я ровно про это и написал: отсутствие необходимых библиотек или имеющаяся кодовая база — это объективный факт. Просто для моих проектов это C++, а для ваших — Go.


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

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

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


Ну, т.е., «в принципе задачу оно решает»…

Где это я его приветствую?


Тайпклассов, вроде, вам не хватало же? Собственно, оно позиционируется как одно из мегапреимуществ…
0
«Имеющаяся у вас кодовая база» и «для ваших проектов» в самой своей формулировке подразумевает субъективность мнения.

Эм, нет, факт обладания объективен. То, что у меня кодовая база на C++, не зависит от того, говорите обо мне вы или же я сам о себе говорю.


Ну, т.е., «в принципе задачу оно решает»…

Пока задача влезает в интерфейс.


У меня может быть тайпкласс Coercible a b, означающий, что a можно скастить в b (например, Coercible Double String). Как мне на интерфейсах это выразить, и как написать функцию типа


coerceTransitive : (Coercible a b, Coercible b c) => a -> c

?


Тайпклассов, вроде, вам не хватало же? Собственно, оно позиционируется как одно из мегапреимуществ…

А они разве неявные? :)

0
Эм, нет, факт обладания объективен. То, что у меня кодовая база на C++, не зависит от того, говорите обо мне вы или же я сам о себе говорю.


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

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

Ну а «уже имеющиеся проекты, которые никто не будет переписывать» вообще ни насколько объективно не могут свидетельствовать о корректности выбора.

coerceTransitive : (Coercible a b, Coercible b c) => a -> c


Я правильно понимаю, что у вас есть метод, который принимает два аргумента, которые на входе могут быть либо Double, либо String, и на выход отдает String?

Ну, собственно func coerceTransitive(a, b string.Stringer) String подойдет?
0
«отсутствие необходимых библиотек или имеющаяся кодовая база — это объективный факт» — вот это неверный постулат.

Я как-то неявно полагал, что необходимость — она не сферическая в вакууме, а для чего-то конкретного. Вот отсюда у нас, видимо, и взаимное непонимание.


Ну а «уже имеющиеся проекты, которые никто не будет переписывать» вообще ни насколько объективно не могут свидетельствовать о корректности выбора.

Конечно, вопрос исключительно в экономических затратах.


Я правильно понимаю, что у вас есть метод, который принимает два аргумента, которые на входе могут быть либо Double, либо String, и на выход отдает String?

Нет.


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

0
Этот метод для любых трёх типов a, b, c таких, что a можно преобразовать в b, а b можно преобразовать в c, принимает a и возвращает c (делая преобразование через b, кстати, и для того, чтобы это доказать, вам достаточно посмотреть на сигнатуру функции, но это так, мысли вслух).


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

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


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

0
Кстати, почему-то для разговоров о том, как тайпклассы заменить интерфейсами, суть задач и их применимость узнавать не потребовалось :)


Потому что суть задач и применимость интерфейсов очевидна?)) При том, что тайпклассы, вроде как, имеют частично схожее назначение «плюс что-то еще такое, что очень нужно, но чего нет в интерфейсах». Вот и интересно, что же это такое.
0

Более частые примеры — упомянутый рядом MonadReader m a, ну или результат умножения двух типов в какой-нибудь линейной алгебре, Mult lhs1 lhs2 rhs какой-нибудь. Или класс, ставящий в соответствие контейнеру его элемент. Или тот же Coercible, просто без всяких функций.

0
MonadReader m a, ну или результат умножения двух типов в какой-нибудь линейной алгебре, Mult lhs1 lhs2 rhs какой-нибудь. Или класс, ставящий в соответствие контейнеру его элемент. Или тот же Coercible, просто без всяких функций.


Ну т.е. вы опять сетуете, что Go не функциональный язык?)
0

Простите, но после вашего комментария на ум пришла вот эта паста:


фп это просто зонтичное название для "фич придуманных после 75-го года"
если язык не ФЯ, это значит, что его автор — сумашедший старый дед
0
плохая какая-то паста, негодная. Захожу, значится, в википедию, а мне там говорят, что ФП появилось раньше 75-го. Лисп — 58й.
0
Лисп — это немножко не то ФП. То ФП, которое мы так любим — это ISWIM. Ресёрч по интересным системам типов — 70-е и 80-е (хотя можно и automath вспомнить, например).
0
Вы под функциональными языками имеете в виду языки с мощными системами типов, что ли?
+2
Система типов «рожается в муках» на этапе формализации/декомпозиции задачи.

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


Более мощные системы типов позволяют больше всего выразить в этих самых типах.


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

Это ложная дихотомия.


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


TDD как type-driven development — именно про это.


И вот именно в этой среде Go стремительно «набирает очки» за простоту рефакторинга

Рефакторить, может, и просто, но уж точно не надёжно.


В коде на хаскеле я не пишу юнит-тесты. Минимальная гранулярность — на уровне одной-двух функций, экспортируемых из модуля, и это скорее проверка соответствия некоторым частным случаям спеки. И сразу после рефакторинга, как только тайпчекер перестал ругаться, эти тесты снова зелёные! И, естественно, ничего не падает, не зависает, не ломается.


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

0
В коде на хаскеле я не пишу юнит-тесты. Минимальная гранулярность — на уровне одной-двух функций, экспортируемых из модуля, и это скорее проверка соответствия некоторым частным случаям спеки. И сразу после рефакторинга, как только тайпчекер перестал ругаться, эти тесты снова зелёные! И, естественно, ничего не падает, не зависает, не ломается.

А как в Хаскеле с производительностью? Я тут вычитал, что быстрая сортировка в Erlang в следствии неизменяемости данных выжрет n^2 дополнительной памяти чем загубит идею быстрой сортировки.
+1

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


Сортировка вектора же в хаскеле использует инкапсулированную мутабельность и так же не даёт дополнительного оверхеда по памяти.

0

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

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

Более мощные системы типов позволяют больше всего выразить в этих самых типах.


Так точно.

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

А причём тут профилировщик? Я думал, мы проектирование обсуждаем.


Да и рабочий прототип, по моему опыту, получается не медленнее (а чаще — сильно быстрее), причём на сильно разных масштабах. Что если вы однострочник для перекидывания данных из json в xml делаете, что если вы пишете ерунду на несколько тысяч строк (которые превратятся в десятки тысяч на более мейнстримных языках).

0