Комментарии 297
handle err {
w.Close()
}
check w.Close()
Ммм, это пять :-)
func printSum(a, b string) error {
x := check strconv.Atoi(a)
y := check strconv.Atoi(b)
check fmt.Println("result:", x + y)
return nil
}
Так как подавляющее большинство функций может завершиться ошибкой, то получается, что перед всеми вызовами будут писать check
. Какой смысл в такой "явности" ума не приложу. Тем более что всё равно не понятно какие именно ошибки может кинуть функция и все ли из них были обработаны.
Так как подавляющее большинство функций может завершиться ошибкой, то получается, что перед всеми вызовами будут писать check. Какой смысл в такой "явности" ума не приложу. Тем более что всё равно не понятно какие именно ошибки может кинуть функция и все ли из них были обработаны.
Ну суть как раз в явности — видно какая функция возвращает ошибку. Если нету хедлера выше, тогда дефолтный используется (просто вернуть ошибку). Все явно — где и как ошибка обработана.
Ясное дело, check нужно делать только там, где есть ошибка (думаю, иначе ошибка компиляции).
Ну суть как раз в явности — видно какая функция возвращает ошибку.
Да все функции возвращают ошибку. А если какая-то ещё не возвращает, то она либо бесполезна, либо не доработана. Банально даже простое сложение двух чисел может вызвать переполнение и по хорошему нужно не игнорировать это, а позволить вызвавшему коду обработать эту исключительную ситуацию.
Все явно — где и как ошибка обработана.
Куда важнее где и какая ошибка не обработана и на этот вопрос Go не отвечает. Программист не может понять какие вообще типы ошибок могут прилететь. Компилятор это тоже не проверяет.
Да все функции возвращают ошибку.
Это неверное утверждение.
Быстрый поиск по стандартной библиотеке:
$ grep -r '^func' . | grep error | wc -l
12928
$ grep -r '^func' . | grep -v error | wc -l
38878
Куда важнее где и какая ошибка не обработана и на этот вопрос Go не отвечает.
Статический анализ не может дать ответ во всех возможных случаях, нужно ли проверять ошибку или нет. Например, `fmt.Println()` возвращает ошибку, но в большинстве случаев практического смысла проверять её нет. Даже линтер `errcheck` тут не будет ругаться.
$ grep -r '^func'. | grep error | wc -l
12928
$ grep -r '^func'. | grep -v error | wc -l
38878
Это неверный подход к анализу. Из выборки также необходимо исключить те функции, которые ничего не возвращают (процедуры, которые не задеклалированы в языке). А почти треть всех функций с error все же значимая часть кода, несмотря на пример с `fmt.Println()`, который больше исключение подтверждающий правило.
В целом направление интересное и возможно верное, хотя check станет самым частым словом в теле функции как в примере выше. Хоть и укорачивает, но слегка нечитабельно.
Возможно, как вариант вообще избавиться от слова check, введя дополнительный вид присваивания? Например, так:
func printSum(a, b string) error {
x ~ strconv.Atoi(a)
y ~ strconv.Atoi(b)
check fmt.Println("result:", x + y)
return nil
}
Где `~` есть эквивалент `:= check`. Или вот так, получше вариант:
func printSum(a, b string) error {
@x := strconv.Atoi(a)
@y := strconv.Atoi(b)
check fmt.Println("result:", x + y)
return nil
}
Последний пример напомнил перл с его:
@file = open($file) or die "cant open file $file: $!"
Насчёт односимвольных синтаксических конструкций – в Go они никогда не приживались, и, надеюсь, и не будут. `check` несёт смысл в самом названии и, даже без чтения спецификации/книжек можно догадаться о его функции, а вот `~` и `@` уже никак не догадаешься сходу.
@
и ошибки это из php, там правда смысл несколько иной: http://php.net/manual/ru/language.operators.errorcontrol.php
Ой как весело комментировать только половину фразы.
А как вы понимаете надо ли обрабатывать ошибку от fmt.Println()
? Мне вот ни из сигнатуры, ни из документации это не понятно:
func Println(a ...interface{}) (n int, err error)
Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.
Что за ошибки? В каких случаях возникают? Насколько критичные?
fmt.Println("result:", x + y)
Функция не возвращает ошибок? Или возвращает, но мы их игнорируем? Или мы забыли обработать ошибки? Или мы не знали, что функция вообще возвращает ошибки? Или знали лишь о 3 из 5 вариантах падения и не были в курсе, что оставшиеся 2 надо обработать по особому?
А как вы понимаете надо ли обрабатывать ошибку от fmt.Println()? Мне вот ни из сигнатуры, ни из документации это не понятно.
В комментарии сказано «write error». Также в нём сказано, что запись производится в stdout. Этого достаточно, чтобы сообразить, что возможна ошибка записи в stdout и уже вам судить, насколько это вероятная ситуация и что в ней делать.
А про «половину фразы» не надо – ради краткости есть смысл не копировать все предложения, которые не меняют смысл вашего утверждения про «все функции возвращают ошибку».
И какая же «ошибка записи» возможна? man 3p write
, раздел ERRORS
описывает 23 возможных ошибки, хотя часть с одними кодами, часть применима только к write()
или только к pwrite()
(кстати, ещё могли сообразить обёртку над writev()
, в определённых случаях функция более удобная, тем более если нужно писать более одной строки), и всё применимо только к linux. Какие из этих ошибок может вернуть fmt.Println
, а какие либо не возникнут вообще, либо будут обработаны самой fmt.Println
и не дойдут до пользователя (я про EINTR в первую очередь, пользователя обёрток практически никогда не заморачивают обработкой этой ошибки)? Что она вообще вернёт конкретно и будет ли она возвращать одинаковой результат для схожих ошибок на Windows и linux?
В вышеприведённом примере понятно, что Println может вернуть ошибку записи в stdout. Этого в 90% случаев достаточно, чтобы понять, как реагировать на данную ошибку. Работа со стандартными дескрипторами в posix это знакомая концепция, поэтому легко сделать вывод, что если произошла ошибка записи в stdout, то программа мало что может тут сделать. Поэтому в подавляющем большинстве практических случаев совершенно не важно, какой из 23 ошибок может быть возвращаемая ошибка в Println, и, учитывая крайнюю редкость ситуации, её есть смысл игнорировать.
Это называется практический опыт и здравый смысл. Если вам нужно копать глубже и для вашей программы действительно важно среагировать на закрытый дескриптор иначе, чем на отсутствие памяти – простыми движениями вы сможете эту информацию найти, спасибо godef и gocode. В Go почти везде используется оптимизация по принципу Парето – дефолтные значения и дизайн заточен под 80% случаев, для остальных 20% есть способы копнуть глубже.
del
catch (error) {
...handle error...
} try {
... do something...
}
чутка непривычно, но я смогу с этим жить.
1. сказать компилятору для конкретной функци: по завершению работы, автоматически закрыть все дескрипторы
2. на этапе компиляции указывать компилятору закрывать все открытые ресурсы после завершения функции
под завершением работы функции имею ввиду любой завершение, паник, корректное (или возврат ошибекИ)
От слова check рябит в глазах
Так что пока неизвестно, будет ли это Go 1.34 или Go2. Сценария Python 2/3 не будет железно.
Это значит что они планируют сохранить обратную совместимость?
Ниже там и про exceptions, assertions, etc.
И списками вы, наверное, тоже не пользуетесь. Не нужны ведь.
Ну, словари и слайсы — это дженерики. Просто авторы Go могут в дженерики, но другим не дают.
Так что говоря, что дженерики не нужны — вы немного не правы. Возможно, стоит говорить "пользовательские дженерики не нужны", но реальность настолько ужасна, что очень большое количество людей решает неправильные задачи неправильно и избежать этого нельзя, особенно техническими методами.
а это хороший кандидат на первое приближение к тому, насколько востребованы дженерики.
Плохой. Это, скорее, прокси к показателю того, как плохо люди умеют маппить ментальные абстракции на системы типов.
Мне запомнился с GopherCon один потрясающий доклад про рейтресер, и в конце спикер поделился некоторыми моментами, с которыми он столкнулся, будучи новичком в Go. И вот один из них был момент "просветления", когда он вместо того, чтобы повсюду использовать тип Vector3
сделал следующее:
type Direction Vector3
type Energy Vector3
...
и как это кардинально упростило работу, уменьшило ошибки и повлияло на API пакетов (он еще сказал, что всегда считал такое определение типов бесполезной вещью).
И, вот, если подумать, то что 90% использования interface{}
, что Vector3
в этом примере, что дженерики (в большинстве своём) – это проблема из одной плоскости: не умения чётко описывать проблемную область на языке типов данного языка программирования. Как можно догадаться, одни языки этому помогают, другие мешают.
Обобщать (generalize) легче, чем чётко раскладывать проблемную область по полочкам и описывать каждую абстракцию своим типом. Поэтому, если люди приходят с других языков и используют interface{}
вместо того, чтобы правильно описывать типы, это скорее PTSD от других языков, а не признак востребованности дженериков.
Кстати, класс-ориентированное ООП тут отдельные палки в колёса вставляет людям, но это уже совсем другая история.
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer(failure) os.Remove(dst);
if _, err := io.Copy(w, r); err != nil {
w.Close()
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
if err := w.Close(); err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
}
продолжать использовать существующий механизм обработки ошибок
В нем и проблема. Defer при ошибке это бонус предлагаемого решения. У других предложений такого бонуса нет. Основные требования — уменьшить кол-во кода на обработку ошибок, сохранить все преимущества текущего подхода, упростить добавку контекстной информации в ошибки. Ваш пример ничего этого не решает.
1. Многословность проверок кодов ошибок.
2. Сложность с написанием кода по очистке ресурсов и/или откату каких-то операций при обработке ошибок.
Первая проблема может быть решена тем или иным синтаксическим сахаром. Например, использованием ключевого слова check или префикса/суффикса '?'. Вроде такого:
w := check os.Create(dst)
// или
w := ?os.Create(dst)
Вторая проблема решается за счет defer(success/failure).
* По поводу «проблем». ИМХО, своим успехом Go обязан именно такому вот примитивному и прямолинейному коду, который не требует высокой квалификации от разработчиков и делает код разных людей максимально однотипным. Так что, что кажется проблемой, ИМХО, на самом деле является одной из важнейших составляющих привлекательности Go в современном мире.
Сложность с написанием кода по очистке ресурсов и/или откату каких-то операций при обработке ошибок
Если честно, эту проблему я слышу сейчас первый раз. Я давно слежу и участвую в дискуссиях по поводу ошибок в Go 2, но ниразу я не встречал именно эту проблему. Да и на практике тоже.
Если честно, эту проблему я слышу сейчас первый раз.Значит вы не читали обсуждаемую статью, ибо в разделе «В чём проблема с обработкой ошибок в Go?» об этом и идет речь. См. пример из этого раздела, в котором os.Remove() нужно вызывать в двух if-ах. Это оно и есть.
Я даже на практике не помню проблем подобных. Удалить файл при ошибке — ерунда, это не требует изменений в языке. В том же самом defer можно банально проверить наличие ошибки и выполнить нужный код.
Я даже на практике не помню проблем подобных. Удалить файл при ошибке — ерунда, это не требует изменений в языке. В том же самом defer можно банально проверить наличие ошибки и выполнить нужный код.Ну значит вообще весь этот огород с handle err не нужен, если по вашей практике судить. Достаточно лишь ввести конструкцию, которая будет аналогом Rust-овских try! и '?'. Делов-то.
Вы это Google-овцам расскажите, а то они не знают, что на практике проблем нет, выдумывают какие-то handler-ы.
А defer(failure)
можно реализовать через проверку:
defer func() {
if err != nil {
...
}
}
Кому интересны подробности из первоисточника и кто не знаком с с++ и/или концепций on_scope_exit гуглите по Андрей Александреску с++ декларативное программирование. Я смотрел/читал все на английском поэтому не уверен, что мой русский перевод нагуглиться. Вот как раз местный пост кому лень гуглить.
Вас не смущает, что defer и handle имеют разные сигнатуры, да и семантика отличается?
В с++/ди для проброса ошибки внутрь дефер/хендл используются техника раскрутки стека при эксепшене в го такого механизма (пока) нет. Вместо этого я полагаю (спецификацию не читал могу ошибаться) в го идиома возврата кода ошибки как последнего значения в тупле скорее всего поддерживается на уровне компилятора путем размещения значения ошибки в регистре ЦПУ (как очень горячих данных). Следовательно нет вообще никаких проблем передать это значение внутрь хендл (оно уже по сути там в определенном регистре), а остальные данные при необходимости могут быть переданы через стэк. Опять же не знаю точно, но как я понял в го нет деструкторов и этот факт значительно облегчает реализацию этой новой концепции обработки ошибок. По сути она тривиальна. Весь код из дефер/хендл пишится в конце скоупа с разными метками входа в соответсвии с порядком объявления. Далее если основной код завершился без ошибок то регистр с ошибкой содержит 0 и выполняется только код соответсвующий дефер секциям. Весь код хендл условный т.е. если регистр с ошибкой не 0 то выполняем код из хендл секции иначе не выполняем. Также выражение check XXX разворачивается во что то наподобие
result, err = XXX()
if (err != nill)
goto end_of_scope_N
где end_of_scope_N метка соответсвующая одному из входу в зависимости от порядка объявления дефер/хендл.
По мне так все логично эфективно и удобно мне бы такое понравилось на с++, но это врядли случится в обозримом будующем.
Теперь в самом простом языке 3 разных вида обработки ошибок, ни один из которых не является универсальным. Defer будет использоваться в случае, когда действие нужно выполнить вне зависимости от успешности (аналог finally), handle — когда приборка отличается в случае успеха и неудачи. Ну и старая добрая проверка err на nil никуда не денется, так как check подразумевает не обработку ошибки, а проброс вверх по стеку, если некоторая ошибка восстановима, то check не подходит. Особую прелесть добавляет факт, что defer и handle похожи, но заметно отличаются, один дружит с паникой, другой — нет, разно ведут себя в циклах, поэтому совершенно непонятно, автор использовал defer потому, что ждёт панику или просто потому что так писать короче.
Кроме того, в любой более-менее серьёзной функции ошибка будет одной из множества, и они будут обрабатываться по-разному, файл не найден, тогда пробуем по другому пути или создаём, или файл создан в более новой версии и нужно отказаться трогать чтоб не испортить данные. Соответственно нужно писать ручную проверку типа ошибки. Ну вот написали мы проверку, как быть в ветке с необрабатываемой ошибкой? Просто вернуть err? Тогда не вызовутся уже установленные хэндлеры. Выполнить check err? Отличная идея, так и нужно делать… Вот только нам же обещали отсутствие ситуации питон 2 vs 3, код должен быть совместимым, добавляя свой хендлер в начале функции нужно сначала посмотреть, нет ли где в теле выхода с ненулевым err, а то хендлер проигнорируется.
В общем этот го не получился. Выбросите и сделайте новый.
Проблема унификации (единого стиля) проверки/обработки ошибки это проблема светлого будующего (когда уже никто и никогда не забывает проверять и обрабатывать ошибки) которое по моим скромным предположениям наступит чуть ранее чем никогда.
Но вы также поднимаете и реальные проблемы — совместимость со старым кодом. Думаю хорошим рабочим решением будет ворнинги компилятора детектирующие описанные вами кейсы так, что го еще рано закапывать, хотя персонально я разделяю ваше оценочное суждение по поводу качества го в целом как языка.
С ворнингами или линтером тоже непонятно, вот, скажем, у меня стоит свитч по типу ошибки, в одной ветке я ошибку исправляю и продолжаю дальше, в другой я выполняю check err, т.е. вызываю обработчики и пробрасываю наверх, а в третьей просто пишу «return nil, err» или «return nil,OuterError(err)». Идеальный случай для ворнинга. Но по факту возможно что это старый непроверенный код, или мне просто не нужен обработчик в этом случае. В общем чую появится специальный комментарий, отменяющий предупреждение в данном случае.
Ну и отсутствие унификации тут не то что бы главное. До этого была концепция «ошибка — это просто один из видов данных». У такого подхода свои недостатки, но есть и достоинства (наверное есть). Теперь же err больше не последнее значение в возвращаемом кортеже, а некий благословенный элемент кортежа, каким до этого было исключение. Только с дырой в виде обработчиков, которые могут не вызваться. Получается как в анекдоте «не кажется ли тебе, Билл, что мы забесплатно дерьма поели».
r, err := os.Open(src)
if err != nil {
return err
}
То я бы хотел иметь возможность написать в одну строку примерно так:
r, err := os.Open(src) try ("blah-blah: %v", err)
Вместо «try» поставьте любое красивое и подходящее слово.
При таком синтаксе возвращалась бы ошибка и остальные — некие дефолтные значения в зависимости от типа. Если же мне нужно вместе с ошибкой вернуть и ещё что-то конкретное, а не дефолтное, то никто не отменяет классический более многострочный вариант.
Ещё более коротко (без добавления некоей обработки ошибки):
r, err := os.Open(src) try
github.com/golang/go/wiki/Go2ErrorHandlingFeedback
Хотя уже было похожее предложение: github.com/golang/go/issues/21161
Есть реальная возможность повлиять на дизайн:
Вы, предположу, не подписаны на кейсы по обработкам ошибок и генерикам.
Гугл в очередной раз
Ну и удивительно, что они выбрали такой фидбэк от коммьюнити а не вообще обычную почту.
Постить ссылки в вики безполезно — гугл второго раунда делать не станет.
Точно также как было с «обсуждением» vgo.
Так называемому коммьюнити politely дают возможность промеж себя пообщаться и кушать, что дают. В кейсах по этим пропозалам гугловцев не было и они никаких хинтов там не постили.
И это ничего не меняет.
Гугл имеет свой интерес и профит и все решения принимает сам. Да они скорее всего пистонов отхватили за то как vgo выкатили, и теперь все могут аж напихать ссылок. Они даже в оригинальных кейсах линки на пропозал не воткунли. Wtf?
github.com/golang/go/issues/18130 — так это только подтверждение что фидбэк им только мешает — изза публичной дискуссии они релиз пропустили.
Тоесть эти два «не связанные» пропосала внесла не го тим и даже не гуглеры?!
И фича гуглу не нужна была вовсе?
И релиз они не пропустили?
А форсить пользователей постить линки на вики страничке (где подписаться на изменения нельзя) это несомненно улучшение процесса?!
Хотите верить, что давать конструктивный фидбек, когда его просят – бесполезно: пожалуйста. Только не подавайте это как какой-то факт, и уж тем более, не притягивайте за уши аргументы, о которых другие люди более осведомлены.
Что через «публичные» обсуждения их пропосалы тяжело проходят?
Что вместо осуждения их пропосала через issue они услажняют процесс обсуждения?
Again, I apologize for all the mistakes I made during this process. I've learned a lot about what a successful community collaboration does and does not look like, and I hope it helps the next one run more smoothly.
Куда уж более гладко — коммьюнити в песочнице с issues играется — их мнение нам так важно, что мы знать не хотим нравится\не нравиться им то что мы предложили — пусть письменно заявления подают — мы несомненно их рассмотрим.
github.com/golang/go/wiki/Go2ErrorHandlingFeedback
Кстати, я уже встречал такое предложение в одном из отзывов.
if result, err = funcName(); err == nil {
//DO SONTHING
} else {
fmt.Println("Error:", err)
}
Легаси библиотеки никому переписывать не хочется — это понятно. Можно было бы добавлять «check» или что-то подобное перед функциями, ошибки которых хочется выбрасывать исключениями, а дальше: try, catch, finally — все как у людей.
«Нам кажется, что правильное использование проверенной временем конструкции „try-catch-finally“ наш бородатый контингент не осилит и наговнокодит на Гошке так, что все шишки опять полетят дизайнерам языка (а далеко не все из шишек нам по вкусу!).
Неразобравшийся чувачок понавтыкает исключений куда следует и куда нет, что мы считаем неправильным, и потому тупо такого инструмента ему (и тебе, бро, сорри) не дадим.
Мы пойдем своим путём. Для обработки обычных ошибок, пожалуйста, используйте возможность возврата множества значений из одной функции (заодно подразобьёте все свои if'ы на присвоение/проверку, Гошка проще всех, вы не знали?).
Встроенный тип „ошибка“ и другие запиленные нами удобства позволят вам обрабатывать все мыслимые ошибки, шутя и наслаждаясь. Однако мы постарались, чтобы имеющийся опыт в других языках здесь вам ни разу не пригодился (разве что в Си или Бэйсике).
Для тех, кто жить не может без исключений мы закинули пару встроенных функций восстановления состояния программы. Но сделали это так, чтобы ими было не слишком то просто пользоваться в быту.
Мы уже завезли Defer, Panic, Recover и планируем продолжать удивлять вас количеством и разнообразием способов делания одинаковых вещей.
Великолепие Гошки в обработке ошибок вы в полной мере оцените после пары-тройки серьезных проектов (*демонический смех)
Вкусного вам кофе, держитесь там,
любящий вас в мыслях
авторский коллектив.»
Я — человек широких взглядов, практикую на чем придётся, в соответствии с требованиями проекта. Прилично налюбился с явой, плюсами и чистым си; по вебу, в основном, бывают: питон, нода и пхп с яваскриптом.
Гошка, кстати, хорош, когда дело касается несложного, но нагруженного бэкенда, сетей, внутрипротокольных блужданий. Критикую я его редко, по делу и для смеха.
Но когда хайпанутому клиенту стукнет «вот прям всё переписать на го!» — это явный перебор и потеря времени, как по мне. Стараюсь образумить, выходит не всегда.
Теперь то можно встать в полный рост и сказать "а я же говорил".
Ну и как обычно
и проверка и обработка ошибок должны оставаться явными – то есть легко видимыми при чтении кода, не повторяя проблем исключений
Нет, это не проблема исключений, правда. В java такой проблемы, например, нет и всегда понятно, какое исключение может выбросить функция (исключая Runtime, но и panic в go есть).
А любителям рассказывать, что вот для java нужно ide, а для golang — нет, можно я напомню, что у go с этим делом еще хуже в силу того, что существует неявный импорт всех модулей из папки и в целом импорт сразу всего, а не по деталям в моде. Из интерфейса github код на go очень больно читается из-за этого.
Теперь то можно встать в полный рост и сказать «а я же говорил».
Ха. Ну да, вместо того, чтобы давать конструктивный фидбек и писать experience reports, которые повлияли на развитие Go и привели к появлению данного черновика, вы, в очень, мягко говоря, не конструктивной манере, рьяно комментировали на Хабре. Упустили шанс сделать мир лучше, поздравляю.
Ха. Ну да, вместо того, чтобы давать конструктивный фидбек и писать experience reports, которые повлияли на развитие Go и привели к появлению данного черновика, вы, в очень, мягко говоря, не конструктивной манере, рьяно комментировали на Хабре. Упустили шанс сделать мир лучше, поздравляю.
А как там такой же репорт по поводу дженериков практически на заре go? Или я что-то путаю?
Если я не ошибаюсь, где-то для версии go1.5,go1.6 кто-то написал очень хороший propasal по дженерикам, на что был большой ответ в google docs где подробно объяснялось почему "это не нужно".
Или я что-то путаю?
Если я не ошибаюсь, вот этот документ. Вроде как он появился раньше 2016 года (если судить по issue и я вполне могу ошибаться). Довольно подробный документ по generiс в целом.
Когда вы говорите про "вы можете повлиять на go", вы наверное подразумеваете "вам что-то не нравится, вы можете это исправить", но на самом деле ситуация выглядит как "вам что-то не нравится, и вы можете это исправить, если ваше решение и ваша проблема будут казаться важными для создателей языка." А спорить с людьми, которые не понимают очевидных вещей довольно сложно.
Каким образом вы его трансформировали в «где-то для версии go1.5,go1.6 кто-то написал очень хороший propasal по дженерикам, на что был большой ответ в google docs где подробно объяснялось почему „это не нужно“.» – для меня загадка, но это очень хорошо иллюстрирует суть всех дискуссий с Вами.
SirEdvin — постоянно озвучивал, что есть проблема, но не предлагал конкретных решений.
divan0 — постоянно утверждал, что никакой проблемы нет, все хорошо.
По итогу комьюнити/коре-тим таки пришло к выводу, что проблема есть. Кто сделал полезное дело, SirEdvin, призывающий решить проблему, или divan0, закрывающий на нее глаза?
SirEdvin — постоянно озвучивал, что есть проблема, но не предлагал конкретных решений.
divan0 — постоянно утверждал, что никакой проблемы нет, все хорошо.
Я ваши комментарии тоже помню, поэтому не ожидаю объективности, но, справедливости ради, SirEdvin «постоянно озвучивал», что в Go плохо всё и атаковал без разбору все аспекты языка – от GOPATH до интерфейсов и до отсутствия исключений. Ну, знаете, тот случай, когда предмет обсуждения не важен, а важен сам факт критики. Мои комментарии были парированием его утверждений, поэтому вам могли они выглядеть как «утверждения, что никакой проблемы нет». Другими словами, когда собеседник делит тему на белое и чёрное, то аргумент «нет, оно не чёрное» не означает, что это «белое». Это классическая fallacy of grey.
Я, кстати, по прежнему считаю, что явная проверок ошибок на порядок лучше исключений. К вышеприведенному дизайну у меня двоякие чувства – с одной стороны, виден очевидный плюс, с другой – это усложнение языка, чуть более «магии» и т.д. Но, посмотрим, как дальше пойдет процесс.
атаковал без разбору все аспекты языка
А как называется такая логическая ошибка, когда вы перекручиваете утверждение и приписываете мне то, что я не говорил?
Изначально мне в го не нравились:
- Обработка ошибок (и ее фиксят)
- Отсутствия дженериков (и как следствие, я писал, что все используют вместо них interface, но нападать на интерфейс как-то… странно, и я такого не помню).
- Идеи с окружением (GOPATH, версионность по коммитам вместо нормальный пакетов), и это фиксят
- Идеи с тем, что комментарии можно спокойной использовать как метаинформацию без отделения специальным синтаксисом. И в этом я был прав, у go теперь есть прекрасная easyjson и писать комментарии на go стало еще немного страшнее).
Я что-то еще забыл?
Вырывать комментарии из контекста не очень красиво.
Возможно, я опять же не правильно выразился, но суть моей фразы была такой "в реальности, для общения между воркерами будет использоваться внешняя очередь".
А вот ваш комментарий
А еще обратно к реальному миру, мне кажется, вместо каналов вы чаще будете использовать какие-то другие MQ, например nats.
Создается 10 goroutine, которые читают с одного канала и обрабатывают сообщение. Есть рутина, которая пишет в канал (не важно как, например, читает с файла и пишет в канал). Обработка сообщений затратный процесс (потому один пишет и много читают). Самый обычный кейс.
При этом, после Ctrl+C завершается работа всех goroutine и после этого приложение останавливается.
А вот ваша задача. Я воспринял это как опять же паттерн "мастер ставит задачи — воркеры выполняют". Поэтому и сказал о внешних очередях.
Если вы говорите, что вместо каналов в реальности будут использовать внешнюю очередь — то вы не понимаете, что такое многопоточность.
Я имею ввиду, что в случае, если вам нужно будет выполнить задачу распределено, то вполне вероятно, что одного сервера с потоками вам не хватит, а для распределения задачи на несколько серверов вам понадобится внешня очередь, я не прав?
Разумеется, если вам не нужно несколько серверов, внешняя очередь не нужна.
Мы говорили про concurrency в Go. Вы сказали, что в питоне не хуже, потом привели пример с IPC и процессами.
Основная идея сообщение была в том, что в python (и в других языках) тоже есть удобная возможность реализации параллельных (несколько одновременно) и конкурентных (одновременно только одно, но без ожидания) операций, которые покрывают большинство кейсов, в которых используются эти языки. И проблем с concurrency, как в страшном древнем C (которые там были, судя по слухам) не испытывают пока.
Скобочки я расставил для себя, что бы убедится, что опять не напутал термины и вы меня правильно поймете.
Ну, это костыли, которые призваны решить проблемы, с которыми эти конкретные костыли еще не справились. Я так же мог использовать треды в python и in_proc из zmq, если бы в python треды работали и было бы почти без костылей. Поэтому и живем на процессах, да.
Но в моем исправленном тезисе вопрос был про удобство, а как я понимаю, эти костыли в целом влияют на производительность.
Офф-сайд: некоторые опытные люди, например, автор fasthttp предлагают в go так же запускать по процессу на процессор (в README написано, что на ядро, но в telegram чатике go вызвали вроде как core-разработчика и он пояснил, что это старая информация и имеет смысл только в том случае, если у вас несколько процессоров, прирост почти 15-20%)
Но в моем исправленном тезисе вопрос был про удобство, а как я понимаю, эти костыли в целом влияют на производительность.
Я уже устал, изначальный разговор не было про только удобства, вы сами подсунули этот тезис.
Офф-сайд: некоторые опытные люди, например, автор fasthttp предлагают в go так же запускать по процессу на процессор (в README написано, что на ядро, но в telegram чатике go вызвали вроде как core-разработчика и он пояснил, что это старая информация и имеет смысл только в том случае, если у вас несколько процессоров, прирост почти 15-20%)
Вы сами хоть читаете ссылки, что постите?
Use Go 1.6 as it provides some considerable performance improvements.
В java такой проблемы, например, нет и всегда понятно, какое исключение может выбросить функция
Ну да, поэтому в большинстве Java программ просто выбрасывается General Exception и ловится в самом верху – всегда понятно, какое исключение :)
А go вместо General Exception у вас есть error
и ничего не поменялось. И на java, и на go у программистов есть все возможности нормально разграничить ошибки по типам и сделать их грамотную обработку. Но почему-то все равно везде чистые error и обработка ошибок где-то на самом верху.
Большинство программистов слишком ленивые для этого, ну или же сроки поджимают.
Но почему-то все равно везде чистые error и обработка ошибок где-то на самом верху.
Ваше утверждение не верно и кодом на практике не подтверждается. Обработка ошибок всегда в месте их выброса. Конкретные error, а не просто строкое значение, нужны очень редко и используются, когда это необходимо. Go никак это не заставляет делать (можно вообще ошибку игнорировать и никто не скажет ничего), эту проблему я в предложениях озвучивал на гитхабе. К счастью, команда Go изначально озвучила best practices и программисты в своем подавляющем большинстве их соблюдают. Программисты ленивые, когда им нужно менять свои привычки. Тут же привычка была привиты изначально.
Ваше утверждение не верно и кодом на практике не подтверждается. Обработка ошибок всегда в месте их выброса
У меня есть два пруфа:
- Почитайте в этой статье про дефолтный хендлер
- Вот я открыл случайный файл в moby/moby и что же я там вижу? 4 места обработки ошибок и все только то и делают, что пробрасывают ошибку наверх. Если вы так не делаете — это круто и достойно уважения, но это не так работает.
Где этот факт? Не скажу, что я читал довольно много кода на go, но какие проекты приходилось, и везде куча пробросов ошибок наверх.
Это настолько понятно, что даже авторы go смирились и сделали это поведение поведением по умолчанию.
Тут, конечно, не только обработка ошибок, но и в целом подход к написанию программ: меньше динамики (в Java динамика — часть парадигмы языка). Но, когнитивная нагрузка при чтении исходников после введения check/handle в Go, конечно, возрастёт. Надеюсь, не сильно.
Вот я открыл случайный файл в moby/moby и что же я там вижу?
Это называется anecdotal evidence. Не рекомендую таким образом что-то кому-то доказывать, если хотите, чтобы к вам прислушивались.
Мне вот интересно, вы проходили какие-то курсы, как писать псевдоумные комментарии?
Вы постоянно злоупротребляете практически всеми логическими ошибками, такими же anecdotal evidence и апелляциями к авторитету.
Более того, абсолютно все, что вы (ну и я) пишите в обсуждениях связанных с go — является жутким субъективизмом, для которого у вас нет никакой аргументации, подкрепленной какими-либо исследованиями. То же магическое утверждение, которое мы тут обсуждаем про "модель ошибок в go заставляет программистов обрабатывать ошибки на месте", у вас есть какие-то исследования на эту тему? Правильные, с контрольной группой и прочими прелестями.
Ну и да, изначальное утверждение было "Обработка ошибок всегда в месте их выброса." Если я нашел пример, где это не так, то оно не правильное, потому что это происходит далеко не всегда.
Я правильно вас понял — вы не согласились, что ваш пример про moby это anecdotal evidence и вместо того, чтобы объяснить, где я не прав, решили сказать что мои комментарии «псевдоумные»?
Если какие-то «курсы» я и проходил, то исключительно по формальной логике. И ваши комментарии просто сильно выделяются обилием логических ошибок и манипуляций – от red herring (чуть выше в дискуссии с creker) до whataboutism. Кстати, о последнем – аргумент «вы тоже злоупотребляете» (вне зависимости от того, насколько он верен) это как бы оно.
> апелляциями к авторитету.
Appeal to authority это не логическая ошибка сама по себе. Если авторитет, о котором речь, действительно авторитет, то это легко может быть очень веский аргумент. Хотя в определенных случаях это да, может быть логической ошибкой.
Я правильно вас понял — вы не согласились, что ваш пример про moby это anecdotal evidence и вместо того, чтобы объяснить, где я не прав, решили сказать что мои комментарии «псевдоумные»?
А вы внимательно прочитали последний абзац моего комментария?
Appeal to authority это не логическая ошибка сама по себе.
Но в вашем случае это как раз ошибка, потому что источники, которые вы используете — предвзяты.
Ирония состоит в том, что практически все программирование находится вне области формальной логики, так как по тем же языкам программирования никто не проводит каких-то нормальных исследований, кроме "популярности" языков, поэтому требовать соблюдения формальной логики довольно странно, на самом деле, так практически как все, что у нас есть это более-менее предвзятые авторитеты.
практически все программирование находится вне области формальной логики
Красивая попытка оправдать обилие логических ошибок в своих аргументах, засчитано.
Я так понимаю, вы все еще не прочитали последний абзац моего сообщения? Я продублирую его для вас:
Ну и да, изначальное утверждение было "Обработка ошибок всегда в месте их выброса." Если я нашел пример, где это не так, то оно не правильное, потому что это происходит далеко не всегда.
Мне кажется, тут вполне соблюдена формальная логика.
Мне кажется, тут вполне соблюдена формальная логика.
Ухты, поздравляю.
Всё же не стоит путать логические ошибки и демагогические приёмы. Последние пытаются эксплуатировать логические ошибки у оппонента и (что чаще) сторонних наблюдателей.
В данном случае апелляция к авторитету — это демагогический приём, эксплуатирующий у людей ошибку "гугл — большая успешная корпорация, значит всё, что они делают — хорошо продумано".
Как тот несчастный, которому приходится потом поддерживать системы, от программистов, которые думают так же могу сказать — это очень плохая и просто отвратительная позиция. У меня уже собралось штук 5-7 историй, где мне хочется убивать людей за то, что они положили болт на обработку ошибок, поставили логику "если ошибка, сказать что что-то пошло не так" и мне потом пришлось в этом добре копаться и понимать "А что же пошло не так?", потому что исправлять как-то надо.
Лично мне кажется, что тот, кто так думает практически сразу не проходит проф. проверку. Причина падения важная всегда, если ваш проект хоть сколько долгосрочен. а не "запустили один раз и забыли".
Идеальный программист в вакууме пишет идеальный код, всегда обрабатывает все ошибки, укладывается во все сроки и не существует.
Жизнь накладывает свои ограничения.В основном, это время и деньги.
Нет проблем делать что-то супер качественно, если: а) на это выделят время, б) за это заплатят деньги.
Реальность в том, что в бизнесе редко кто считается с твоими (моими) личными взглядами на красоту кода.
Бегать от реальности — глупо, менять её — не всегда возможно.
Есть разница между "супер качественно" и "залогировать ошибку". Если вам так сложно выбросить ошибку в лог, что вам на это нужна куча времени, вы точно делаете что-то не так.
Мой изначальный комментарий вы опровергаете ничем не подкрепленным антитезисом:
Причина падения важная всегда
Если не сталкивались с ситуациями, когда она не важна — еще столкнётесь.
Вот у вас есть программа. Она упала. Вы ее сразу выкидываете или продолжаете с ней работать дальше? Если продолжаете работать дальше, а она опять падает, вы будет игнорировать проблему? Или как это работает?
в большинстве Java программ просто выбрасывается General Exception и ловится в самом верху
Возможно в большинстве ваших программ так и есть.
Но, к счастью, вы не автор большинства программ на Java.
Возможно вы имели в виду вот такой кейс:
try {
// Do something...
} catch (FileNotFoundException e) {
throw new RuntimeException("file is required blabla", e);
}
Да, так делают. В гугловой guava даже была спец функция Throwables.propogate.
Но ее задепрекейтили и предлагают просто писать как в моем примере.
По сути это некий аналог
err := ... // do domething
if err != nil {
log.Fatalf("file is required blabla: %v", err)
}
Вы это имели в виду, когда говорили про «General Exception»?
Вот, оригинальная ссылка neverworkintheory.org/2016/04/26/java-exception-handling.html, ссылка на сам пейпер (http://plg.uwaterloo.ca/~migod/846/current/projects/09-NakshatriHegdeThandra-report.pdf) и зеркало (оригинальная ссылка умерла вроде) www.dropbox.com/s/yhd3d818hw5dv2h/p484-kery.pdf?dl=0
Глянул на секцию Аbstract. А там пишут такое:
We found that programmers handle exceptions locally in catch blocks much of the time, rather than propagating by throwing an Exception.Не кажется ли вам, что это прямо противоречит вашему утверждению «просто выбрасывается General Exception и ловится в самом верху».
Быть может вы привели не ту статью?
Или ошиблись с переводом, когда ее читали?
Насчёт 78% — тут даже цитата в блог посте есть:
Similarly, most programmers ignore Java's carefully thought out exception hierarchy and simply catch Exception (78%) or Throwable (84%) rather than any of their subclasses.
Что несколько не то, что я сказал. Теперь самому интересно найти и перечитать оригинальный пейпер, но это то, как я его тогда запомнил, да.
А то пока тот, что вы привели, их опровергает.
Найдете — будет 1-1. А я пока подожду :D
Что несколько не то, что я сказал.Давайте честно. Это прямо противоречит тому, что вы сказали. Эпитет «несколько» тут неуместен.
Насчёт 78% — тут даже цитата в блог посте есть:Раз уж привели статью, то ссылайтесь на нее, а не какой-то левоватый блог. Да и что это за проценты такие? 100% — это что? 78% от чего? 78%+84% — это как?
Similarly, most programmers ignore Java's carefully thought out exception hierarchy and simply catch Exception (78%) or Throwable (84%) rather than any of their subclasses.
Вот вам цитата из статьи
Exception is a full 26% of all exceptionsДалее, в статье, приводятся предположения, почему так происходит. Почитайте таки статью, на которую ссылаетесь, почитайте.
Раз уж привели статью, то ссылайтесь на нее, а не какой-то левоватый блог.
Это не левоватый блог – это пост, через который я изначально узнал о пейпере, и в котором есть цитаты из последнего. Если найду оригинал, сброшу обязательно.
А этот второй пейпер, как раз тот, в котором результаты расходятся, как я понял из блог поста. Почитаю на досуге тоже, историю ошибок в языках программирования полезно же изучать. ;-)
А этот второй пейпер, как раз тот, в котором результаты расходятся, как я понял из блог поста. Почитаю на досуге тоже, историю ошибок в языках программирования полезно же изучать. ;-)В самом пейпере результаты не расходятся. Это в блоге числа напутали.
Почитаю на досуге тоже, историю ошибок в языках программирования полезно же изучать. ;-)Почитайте, чтобы знать то, насчет чего спорите. А то вы позволяете себе громкие высказывания насчет других языков, при этом демонстрируя глубочайший уровень незнания этих самых языков.
Умейте уже признавать свои ошибки.
Вы что-то слишком быстро сделали выводы, не прочитав первый пейпер. Но вы же умеете признавать ошибки, верно?
Но числа то все равно совершенно левые. 78% от чего? 84% от чего? От общего количества всех try блоков? Полный бред :)
Вероятно это проценты от количества программистов. Т.е. мол 78% из некоего множества инженеров нет-нет да и написали разок `catch (Exception e)`. Ну, такое вполне можне быть.
Хотя все равно очень и очень подозрительно, что число у Throwable больше (не все начинающие джависты про него даже знают, кхекхе). Т.е. все равно выглядит как ерунда. Ну либо выборка инженеров была очень уж своеобразная (набрали где-то говнокодеров).
По вашему второму пейперу видно, что отлавливают `Throwable` в несколько (раз так в 6-7) реже, чем `Exception`. Откуда берутся 78% и 84% полная загадка.
Так я и написал, что в пейпере несколько не то, что я изначально сказалПовторюсь, «несколько не то» тут неуместно :D
Вот вам ссылочка на вашу статью. За 5 минут нашлась :)
www.computer.org/csdl/proceedings/msr/2016/4186/00/07832935.pdf
А вот откуда получились странные числа.
To learn more, the number of methods that contained only Exception and Throwable handlers was analyzed further. A staggering 78% of the methods that caught Exception did not catch any of its subclasses and a similar observation of 84% was made for Throwable.Как видно, никакой связи с вашим начальным утверждением :)
Кратко же выводы этого пейпера — в Java очень часто тупо ловят Exception, не заморачиваясь с отловом конкретных подклассов. А еще часто перебрасывают его в виде `RuntimeException` (что я уже раньше говорил). В общем не юзают механизм checked exceptions, предпочитая иметь дело с runtime. Что как-бы и не секрет :)
Короче, спасибо, вы привели аж 2 пейпера, которые показывают вашу неправоту. Приятно вести дела с таким собеседником!
В java вы обязаны декларировать ошибки, которые может вернуть функция (исключая Runtime виды ошибок). Ну и так же, обязаны их обработать или же добавить в определение функции.
Если я правильно понимаю, что вы имеете ввиду под call site, то java дает вам информацию, какая функция какие типы ошибок может вернуть. Все остальное в руках разработчиков, которые этим управляют.
Ну а так да, это одна из фич джавы, которая почему-то не очень нравится людям.
void Foo() throws Exception {
foo.bar1();
foo.bar2();
}
И это у Java описаны исключения, а у других это только в документации вроде C# и C++.
Ну, как я уже писал, это вопрос реализации.
если воспользоваться ide
Вот именно. Нужна ide, которая это умеет, но это не главное. Главное, что тратится драгоценное время на чтение кода. Это Go и не хочет повторять. К тому же, это не единственная проблема, которая заставила отказаться от исключений.
Почитайте код написанный на go на github, хотя бы раз. После того, как вы перероете все 20 файлов в папке в поисках структуры Config
вы поймете, что в go ситуация с "мне нужна ide" еще хуже.
Это не то, что бы плохо, но вот это "не хочет повторять" не соблюдается.
То есть вам все равно нужно что-то стороннее, что бы смотреть код. Ну и где "не хочет повторять ситуацию"? Просто страничка в браузере, вместо ide, но это не очень помогает.
Перечитайте с самого начала, в чем изначально проблема Java и исключений в целом. Не в документации, а в том, что call site ничего не говорит. Этой проблемы в Go нет.
Я не вижу проблемы в том, что "call site" не говорит о том, что может возникнуть ошибка в языке, где исключение может возникнуть при любом вызове. Это надуманная проблема.
исключение может возникнуть при любом вызове
Это и есть проблема, язык таким быть не должен. Вот `Length` у массива может мне вернуть исключение? Я не знаю, может быть, это же язык, где при любом вызове может быть исключение. В документации не написано, а я ведь умный программист. Поставлю ка я лучше try/catch и в лог это плюну. Это и есть проблема, которая наводнила все exception языки. И checked exceptions в Java ничем ее не решили, как показала практика реального использования, а не фантазий, как теперь все хорошо станет.
А множество других видит
Апелляция к толпе.
язык таким быть не должен
Почему?
Вот Length
у массива может мне вернуть исключение?
Может. Более того, это может оказаться и не массив благодаря полиморфизму.
Поставлю ка я лучше try/catch и в лог это плюну.
Если вам надо продолжать работу не смотря на ошибку при попытке получить длину массива, то так и надо делать. Но обычно такой try-catch находится выше по стеку вызовов и накрывает не одно только обращение к Length.
Это и есть проблема, которая наводнила все exception языки.
Проблема-то в чём?
Апелляция к толпе.
Как любят некоторых вставлять эти заученные фразочки с вики. Нет, это апелляция к насущности проблемы, что аж 3 языка независимо друг от друга предложили все свои решения. Я думаю, что это далеко не все, но из современных эти три сейчас на слуху у всех. Не было бы проблемы не было бы изобретения заново механизмов ошибок.
Почему?
Потому же, почему критикуют исключения. Если вам неизвестны их недостатки, то вперед в гугл. Информации навалом.
Проблема-то в чём?
В не очевидности control flow, сложности чтения кода, привитию дурных практики программирования с оборачиванием всего подряд в try/catch. Такое ощущение, что вы только сегодня родились. Проблемы исключений известны настолько же долго, насколько они существуют в языках.
Как любят некоторых вставлять эти заученные фразочки с вики. Нет, это апелляция к насущности проблемы, что аж 3 языка независимо друг от друга предложили все свои решения. Я думаю, что это далеко не все, но из современных эти три сейчас на слуху у всех. Не было бы проблемы не было бы изобретения заново механизмов ошибок.
Добро пожаловать в реальный мир. Асинхронное программирование было известно с еще с 80-х или 90-х, но у нас тут тот же nodejs
развирусился на фоне "ОООО, асинхронное программирование".
Переход на ошибки просто повторяет цикл с 80-х, когда исключений еще не было.
В не очевидности control flow, сложности чтения кода, привитию дурных практики программирования с оборачиванием всего подряд в try/catch. Такое ощущение, что вы только сегодня родились. Проблемы исключений известны настолько же долго, насколько они существуют в языках.
не очевидности control flow — у вас все еще есть runtime ошибки (panic или что там в других языка), которые могут возникнуть где угодно. Про control flow это скорее rust, который пытается гарантировать правильность работы системы вне unsafe блоков.
сложности чтения кода — А как это связано с исключениями?
привитию дурных практики программирования с оборачиванием всего подряд в try/catch — Сами по себе исключение этого не делают. Это делают ленивые программисты, или программисты у которых нет время правильно обработать эти исключения. В случае с ошибками они могут их игнорировать, пробрасывать наверх или просто писать в лог и завершать программу. С этой точки зрения ничего не меняется.
Как любят некоторых вставлять эти заученные фразочки с вики.
Ох уж эти любители называть вещи своими именами.
Нет, это апелляция к насущности проблемы, что аж 3 языка независимо друг от друга предложили все свои решения.
NIH
Если вам неизвестны их недостатки, то вперед в гугл.
Я с тем же успехом могу сказать, что информации в гугле по недостаткам ручной раскрутки стека куда больше и послать вас на те же 4 буквы.
Впрочем, недостатки мне известны, и Go их никоим образом не решает.
не очевидности control flow
Для любого поверхностно знакомого с языком он очевиден. Или может для вас и switch не очевиден и его срочно надо заменить на goto?
сложности чтения кода
Подкрепите ваши слова примерами.
Мне вот гугл подсказывает противоположное: "Использование исключений в целях контроля ошибок повышает читаемость кода, так как позволяет отделить обработку ошибок от самого алгоритма, и облегчает программирование и использование компонентов других разработчиков."
привитию дурных практики программирования
А кто решает какие практики дурные? Вы? Гугл?
с оборачиванием всего подряд в try/catch
Где вы такое увидели? Сколько я повидал исходников за 20 лет, нигде такого не видел.
1) есть исключения
2) любая функия/метод, которые могут кинуть исключение, должны в конце имени иметь "!" (проверяется компилятором). Примерно как публичные методы в Go должны начинаться с заглавной.
И тогда ваш пример будет выглядеть так:
func Foo!() {
foo.bar1!()
foo.bar2!()
}
Вместо
func Foo() error {
check foo.bar1()
check foo.bar2()
}
Или
func Foo() error {
if err := foo.bar1(); err != nil {
return err
}
if err := foo.bar2(); err != nil {
return err
}
}
Все явно, call-site знает где может быть ошибка. Код не будет наводнен кучей бесползных проверок. Сразу видно, где exit-points у функции.
Я никого не призываю внедрять такой подход в Go.
Но сами по себе «исключения» не обязательно делают код «менее читабельным». Вопрос именно в реализации.
func Foo!() {
foo.bar1!()
foo.bar2!()
}
Я только со второго раза увидел там!.. Думаю в Go таких односимвольных синтаксических элементов избегают не просто так.
Причем «субъективные» придирки "я не увидел". Не знаю, может вам не хватат подсветки синтаксиса (я бы такие методы у себя жирным подсвечивал). Может вы читали так много кода на Go, так бегло, что мозг уже игнорирует вещи, которых быть не должно. А может у вас плохой монитор. А может стоит проверить зрение. Много вариантов.
В любом случае, я не предлагал/ю внедрять такое в Go.
Да… можно было бы думать, если бы язык только разрабатывался…
Но сейчас уже, конечно, поздно пить боржоми :D
Я лишь хотел показать, что наличие исключений не обязательно означает
Проблема в том, что нет информации, какая функция вернет ошибку прямо в call site.
А может у вас плохой монитор.
LG 38 inch ultra-wide, рекомендую, кстати, люто.
Насчёт придирок – это не придирки. В дизайне языков ничего не бывает простым, однозначным и очевидным. Префикс/постфикс, длина слова, похожесть слова на другие, паттерны использования символа(ов) и всё прочее – совершенно непредсказуемым образом аукается на практике, и главные проблемы лежат в области когнитивной психологии и нейронауки, и эта тема (в контексте программирования) ещё очень плохо изучена.
В черовниках обсуждается вариант использования `?` вместо `check` и он не кажется удачным. Исключения, опять же, имеют и другие проблемы помимо неявности (stack unwinding, неочевидность того, какой код будет исполнен при ошибке и т.д).
LG 38 inch ultra-wide, рекомендую, кстати, люто.Ну значит проблема не в мониторе. Вычеркиваем из списка…
Исключения, опять же, имеют и другие проблемы помимо неявности (stack unwinding, неочевидность того, какой код будет исполнен при ошибке и т.д).Имеют, не имеют… Я не собираюсь обсуждать это в текущей ветке.
Тут утверждалось, что с исключениями «нет информации, какая функция вернет ошибку прямо в call site». Это не обязательно не так. Зависит от того, как сделать язык.
Я привел пример. Не пропозал для Go, не идею. А лишь упрощенный пример, чтобы донести свою мысль. Донести ее не напрягая вас лишний раз, не перегружая, так сказать.
Если у вас есть возражения насчет примера, то пожалуйста, рад подискутировать. Если же вы хотите порасписывать, почему пример не может быть интегрирован в Go2 — не марайте бумагу.
Или если мы вводим в язык строгий гайдлан «любая функция, которая может кинуть исключение, должна в имени содержать такойто суффикс», то это уже не исключения?
и этим аргументируете о читабельности последних.Не придумывайте за меня того, чего я не говорил :)
Повторю специально для ВАС
Тут утверждалось, что с исключениями «нет информации, какая функция вернет ошибку прямо в call site». Это не обязательно так. Зависит от того, как сделать язык.
и этим аргументируете о читабельности последних.
Не придумывайте за меня того, чего я не говорил :)
Ваша же цитата:
Но сами по себе «исключения» не обязательно делают код «менее читабельным».
Правда я говорил «не обязательно делают менее читабельным», а не «делают код более читабельным» (как приписываете вы). Но я все же поправлюсь:
Но сами по себе «исключения» не обязательно означают, что нельзя напрямую из кода узнать, какая функция вернет ошибку прямо в call site. Вопрос именно в реализации.
Такая формулировка лучше? :)
Вы не прочитали мой изначальный комментарий, только увидели код. Ну и подумали что это я предлагаю заменить `check` на `!`. Нет, не предлагаю. Прочитайте мой комментарий :)
.
Это замечательное решение, но уже исключениями оно не является, т.к. сейчас исключения означают вполне конкретную реализацию, которая универсальна для всех языков.Подождите, подождите. Т.е. заставить в имя функции добавить некий суффикс в конец если она может кинуть исключение — это уже не исключения? Ну допустим…
А что же тогда такое `Exception` в Java. На всякий случай напомню, что если метод может выкинуть Exception, то он обязан явно содержать `throws Exception` в своей сигнатуре. Как по вашему, это исключения? Это вписывается в ваше определение "сейчас исключения означают вполне конкретную реализацию, которая универсальна для всех языков"?
Судя по вашим предыдущим репликам вы считаете что в Java это таки исключения. Тогда поясните свою позицию пожалуйста, чем принципиально отличается мой теоретический язык от подхода Java?
Как по вашему, это исключения?Нет, так как метод может содержать `throws Exception` в своей сигнатуре, но не выкинуть Exception.
Нет, так как метод может содержать `throws Exception` в своей сигнатуре, но не выкинуть Exception.Т.е. если я напишу `throw new Exception(«bla»)` в Java, то это не исключения?
А если напишу `throw error.New(«bla»)` в моем гипотетическом языке, это исключения?
Т.е. если я напишу `throw new Exception(«bla»)` в Java, то это не исключения?Я вроде о сигнатуре метода написал
Я пытался лишь выяснить, почему некоторые (creker и divan0, возможно и вы), считают, что в Java имеется механизм исключений (со всеми вытекающими). Хотя в Java и нужно помечать сигнатуру метода специальным образом.
А в моем «гипотетическом языке» это уже, внезапно, не исключения. Хотя суть та же — если кидаем исключение, то нужно пометить сигнатуру. Если нет — все еще можно пометить сигнатуру.
Может быть вы наконец поясните эту непонятную для меня позицию?
Хотя в Java и нужно помечать сигнатуру метода специальным образом.В Java не всегда надо помечать сигнатуру метода специальным образом
В Java не всегда надо помечать сигнатуру метода специальным образомТ.е. если бы нужно было помечать всегда (не было бы RuntimeException), то в Java бы не стало бы исключений вовсе?
Так что, получается `throw new RuntimeException()` — это выброс исключения. А `throw new Exception()` — это что тогда?
А `throw new Exception()` — это что тогда?Это механизм заставить проверить на один уровень выше, как в Go
Это механизм заставить проверить на один уровень выше, как в Go«Механизм» это когда в сигнатуре есть `throws Exception`. А тут именно `throw new Exception(). Что это такое?
Что это такое?Legacy
LegacyА можете чуть-чуть подробнее объяснить свою позицию.
Но давайте я вам другой пример приведу.
Прямо из документации.
if (Thread.interrupted())
throw new InterruptedException();
Тут что. Тоже «Legacy»?И поставлю вам вопрос несколько однозначнее.
Считаете ли вы, что код `throw new Exception()` выбрасывает новое исключение в Java. Можете ответить односложно, но только «да» или «нет» :)
Тут что. Тоже «Legacy»?
Ну да, или вы думаете что checked exceptions были уже в 1.0?
Считаете ли вы, что код `throw new Exception()` выбрасывает новое исключение в Java.А что такое исключение?
Вообще я не особо хочу тратить свое личное время и объяснять вам что такое исключения, как они работают в Java и т.п. Есть куча отрытых источников на этот счет. Ну или можете записаться на какие курсы по Java.
Когда узнаете немного больше чем ничего (блин, это же надо додуматься сказать, что в java 1.0 не было checked exceptions...), тогда я с радостью продолжу нашу дискуссию.
Any Exception that can be thrown by a method is part of the method's public programming interface. Those who call a method must know about the exceptions that a method can throw so that they can decide what to do about them. These exceptions are as much a part of that method's programming interface as its parameters and return value.Т.е. это больше как возврат ошибки
Правда, похоже у вас тот же недуг, что и у divan0 — читаете текст, а смысл его уловить не можете. Там было 2 вайтпепера, тут документация Java…
Главное, что тратится драгоценное время...
Хм, а на… ч на хабре оно не, не тратится? ;)
Если бы это предложили с самого начала — было бы еще лучше.
время придется привыкать, как и в случае со случайным сокрытием переменных.
func CopyFile(src, dst string) throws error {
try {
try {
r := os.Open(src)
w := os.Create(dst)
io.Copy(w, r)
} finally {
r.Close()
}
} finally {
w.Close()
}
}
Отличный язык, но вот этот прикол с обработкой ошибок, массивы, еще пару забавных мометнов сводят на нет все его достоинства. Если бы люди думали не о том, что бы сделать не как у других, а о там что бы сделать удобно — этому языку бы цены не было.
о там что бы сделать удобно
А вам более удобно пользоваться тачпадом или мышкой?
Если бы люди думали не о том, что бы сделать не как у других, а о там что бы сделать удобно — этому языку бы цены не было.
Я правильно вас понял, что вы считаете, будто исключений в Go нет не потому, что авторы Go в них видят проблемы и неудобства, а потому что просто хотели сделать «не как у других»?
Разница же достаточно существенна:
— в catch/handle проверка ошибки очень явная – check пишется прямо перед вызовом функции (а не на целый блок, в котором могут быть функции, выбрасывающие ошибки). в этом плане оно похоже на try! из Rust или Swift.
— нет неявной развертки стека. максимум, что handler может сделать – пробросить наверх выше. тоесть, по сути, это синтаксический сахар для того, что вы могли бы делать в `if err != nil` блоке
— всегда легко понять, какой именно код будет исполнятся при check – с исключениями это не так: множественные catch конструкции будут вызываться в зависимости от ошибки.
Учитывая что в блоке try может быть вызвана всего лишь одна функция
Вы что-то путаете. В классическом понимании исключений try..catch в блоке try можно писать сколько угодно инструкций.
В классическом понимании исключений try..catch в блоке try можно писать сколько угодно инструкций.
Судя по этому ответу, вы даже не удосужились распарсить мой пост. За сим откланиваюсь, с голубями в шахматы не играю.
Если и так непонятно, но вперед читать ошибки в Swift и Rust. Там тоже нет никаких исключений и механизм примерно тот же, какой хотят в Go.Сможете показать обработчик ошибки в Rust, который определяется задолго до места, в котором ошибка диагностирована?
?
и unwrap
а что не так? Если не определять handler то будет использоваться дефолтный handle err { return nil }
— как в Rust c?..
В тоже время можно определить свой в начале метода/функции или обработать ошибку в месте, где возникла if err != nil {}
.
а что не так?Все.
'?' в Rust — это частично то, что в Go предлагают под соусом ключевого слова check. Но лишь частично. Ранее '?' был представлен в виде макроса try! Задача '?' и 'try!' лишь в том, чтобы проверить результат операции и, если результат отрицательный, возвратить его в вызывающую функцию. Все. Никакой обработки ошибок ни '?', ни 'try!' не задействует.
Обработка реализуется в вызывающей функции посредством обычного паттерн-матчинга (либо через match, либо через if let). И, что важно, обработчик ошибки в Rust-е пишется там же, где результат операции проверяется.
Там же.
А не N строчками выше. Да еще неявно провязанными в цепочки.
unwrap — это вообще из другой области. Вот вообще.
Задача '?' и 'try!' лишь в том, чтобы проверить результат операции и, если результат отрицательный, возвратить его в вызывающую функцию.
И
Все. Никакой обработки ошибок ни '?', ни 'try!' не задействует.
Тоже самое будет делать check c дефолтным хендлером.
Обработка реализуется в вызывающей функции посредством обычного паттерн-матчинга (либо через match, либо через if let). И, что важно, обработчик ошибки в Rust-е пишется там же, где результат операции проверяется.
Как и в Go c `if err != nil`
* считаете, что вызов дефолтного хендлера — это не вызов хендлера ошибок, а так, ничего;
* с какой-то стати считаете, что в Go-ных программах будут только дефолтные хендлеры.
Хорошая попытка натянуть сову на глобус, но нет.
* вызов дефолтного хендлера это обработка ошибки, так же как `?` в Rust — это обработка ошибки. Так же как `unwrap` это обработка ошибки — бросает панику если есть ошибка.
* будут не только дефолтные хендлеры, конечно же, хотят добавить больше возможностей (что бы добавлять контекст во все ошибки функции, например)
Я вам привел пример того, что в Rust есть «обработчик ошибки в Rust, который определяется задолго до места, в котором ошибка диагностирована»
if err != nil { // диагностирование
return err // обработка
}
Вы считаете, что все что в теле `if err != nil` все еще диагностирование?
Знаю что в расте оператор? или макрос try! это такая же обработка ошибочной ситуации паттерн матчингом.
Точно тоже, что делает check с дефолтным хендлером в Go или вручную с if err != nil.
делает check с дефолтным хендлером в Go
не делает, но то что предлагают, конечно же
Всё же делегирование обработки не является обработкой как таковой. Так как обработка — это прежде всего принятие решения о том, как программа (не просто функция) должна реагировать на исключительную ситуацию. И делегирование является следствием невозможности принять это решение на конкретном уровне в конкретной функции. Так что ни check
ни try!
обработкой не являются. А являются они делегированием обработки посредством ручной раскрутки стека.
Почему бы и нет:
let handle = |err| {
println!("ERROR: {:?}", err);
err
};
let r = foo().map_err(handle)?;
Еще в Rust'е у типа Result
есть метод or_else
, в котором обработчик может вернуть не только ошибку, но и Ok.
Важное отличие от go: у вас есть уникальное в определённом контексте имя. Также вам будет сложно (но не невозможно, не с существованием макросов) собрать цепочку обработчиков так, чтобы новый элемент цепочки не упоминал в явном виде предыдущий.
Так в этом и проблема предложения. В Rust чтобы так сделать вам придётся постараться и задействовать макросы. В предложении это стандартная функциональность. Ещё мне здесь не понятно, что предлагается делать с ситуацией, когда какой‐то обработчик перестал быть актуален далее по коду: ввод булевой переменной и оборачивание тела обработчика в if
как‐то не очень выглядит.
Почему бы и нет:Потому, что нет. Т.к. суть вашего примера не в наличии объявления переменной с лямбдой handle, а в явном вызове map_err именно там, где вы хотите обработать ошибку. Что будет особенно хорошо видно, если ваш пример чуть-чуть расширить:
let handle = |err| {
println!("ERROR: {:?}", err);
err
};
let another_handler = |err| ...;
let r = foo().map_err(handle)?;
let r2 = baz(r).map_err(another_handler)?;
И это будет принципиально отличаться от Go-шного варианта:
handle err {...}
handle err {...}
...
r := check foo();
r2 := check baz(r);
let handle = |err| {
println!("ERROR: {:?}", err);
err
};
это всего лишь объявление символа handle, связанного с некоторой лямбда-функцией. Будет ли эта лямбда использоваться в качестве обработчика ошибок или не будет — в месте определения не видно.
Ключевой момент в вашем примере — это вызов map_err. Вот при его вызове как раз обработчик ошибок и определяется. А уж определяется ли он как ссылка на уже существующее имя:
foo().map_err(handle)?
или же лямбда туда сразу отдается:foo().map_err(|err| ...)?
это уже мелкая и незначительная деталь.Так что ваш пример сойдет в качестве упражнения в схоластике, но это не то, что требовалось.
Будет ли эта лямбда использоваться в качестве обработчика ошибок или не будет — в месте определения не видно.
Вы так говорите, будто функция, принимающая Result::Err
может обрабатывать что-то другое, кроме ошибки.
Конечно, саму лямбду мы можем и не передать вовсе в map_err
. Но и просто наличие handle
в Go еще не означает, что будет вызван check
.
будто функция, принимающая Result::Err может обрабатывать что-то другое, кроме ошибкиДавайте еще раз:
let handler_one = |err| ...;
let handler_two = |err| ...;
...
let r1 = foo().map_err(handler_one)?; // (1)
...
let r2 = foo().map_err(handler_two)?; // (2)
В точке (1) handler_two не является обработчиком ошибки. А в точке (2) — является. С handler_one все наоборот. Так что объявление лямбда-функций в Rust-е вовсе не означает объявление обработчиков ошибок.
Кроме того, можно объявить лямбду, которая будет преобразовывать ошибки из одного представление в другое уже после того, как сама по себе ошибка уже обработана.
Так что опять нет.
Но и просто наличие handle в Go еще не означает, что будет указан check.Очередная схоластика. Наличие handle означает, что как только появится check, этот handle будет участвовать в обработке ошибок.
… как только появится map_err(handle)
, handle
будет участвовать в обработке ошибки.
action().map_err(handle)
ключевым является не handle, а map_err
? Именно map_err
указывает, что если action() завершилась неудачно, то вот здесь и вот сейчас нужно выполнить обработку которая реализуется через handle. И только через handle.Тогда как в Go мы имеем где-то
handle err
, возможно и не один. А где-то мы делаем check action()
. И вот в месте с check
может быть запущен какой-то обработчик. Какой? А вот это нужно уже смотреть выше по коду. Возможно, сильно выше по коду. И, возможно, не один раз.Теперь возвращаемся в исходную точку. Покажите такое же поведение в Rust-е. Без использования паник, естественно.
В Rust'е, слава богу, такого поведения нет.
Так что вы изначально хотели получить от Rust'а? Возможность устанавливать обработчик положением в потоке кода? Так нельзя (без макросов). Я ваш исходный комментарий понял так, что вас интересует возможность определить обработку ошибки не в том же месте, где она возникает. Но вы как-то странно понимаете, что значит "определить обработку". В итоге у вас получается, что action().map_err(handle)
— определяет обработчик handle
в месте возникновения ошибки, а check action()
+ положение этой строки в потоке кода — не определяет обработчик ошибки в этом месте ее возникновения.
Так что вы изначально хотели получить от Rust'а?
Я чего-то хотел от Rust-а? Актитесь, сударь.
Посмотрите, в какое место разговора вы соизволили вклинится тыц.
Господин creker соизволил провести аналогию между тем, что есть в Rust-е, и тем, что предлагается в Go2. Как по мне, так аналогий нет. Отсюда и просьба к creker показать, как он себе видит Go2-шное поведение с заранее определяемыми хендлерами ошибок в Rust-е.
Но тут появляетесь вы и уводите разговор куда-то в сторону, чтобы затем прийти к тому, на что прозрачно намекалось creker:
Возможность устанавливать обработчик положением в потоке кода? Так нельзя (без макросов).
Ну и:
В итоге у вас получается, что action().map_err(handle) — определяет обработчик handle в месте возникновения ошибки,И еще раз нет. В очередной раз вам повторяю, что конструкция
action().map_err()
указывает, что мы обрабатываем ошибки здесь и сейчас. С помощью handle или чего-то другого, но здесь и сейчас. Это явное определение обработки ошибки сразу после вызова action()
(на самом деле все несколько сложнее, но давайте для простоты считать так). И это в корне отличается от Go2-ного check-а, при котором мы толком не знаем, что будет сделано и какой код получит управление в случае ошибки.Пожалуй, на этом все. Если вы опять чего-то не поняли, то се-ля-ви, ничего не поделать.
Из того, что мы "толком не знаем" не следует, что обработка ошибок не будет осуществлена также "здесь и сейчас", выражаясь вашей терминологией. Объявляется обработчик отдельно, но "определяется" он точно также в месте возникновения ошибки: просто связь устанавливается не по имени, а по порядку следования. Только в этом отличие.
Вообще-то может, так как в качестве типа ошибки в Result::Err
может использоваться любой тип.
А уж определяется ли он как ссылка на уже существующее имя:
foo().map_err(handle)?
или же лямбда туда сразу отдается:
foo().map_err(|err| ...)?
это уже мелкая и незначительная деталь.
Вообще-то это очень существенная деталь. Во втором случае будет невозможно использовать один и тот же обработчик в нескольких разных местах. Собственно, handle
в Go и понадобился, чтобы в том числе избежать дублирования кода обработки ошибок.
Ну а то, что эти handle
связаны порядком следования, а не явными вызовами (как было бы в случае использования функций), по мне — так больше недостаток, чем достоинство. Чтобы понять, как будет обработана ошибка в конкретном месте, придется просматривать весь код выше и считать handle
. Если между двумя разными check
будет находиться некоторый handle
, то придется держать в голове то, что эти check
обрабатываются по-разному, хотя синатуры обработчиков и строк вызовов об этом нам не говорят.
Фундаментальное отличие в том, как обработка ошибок влияет на control flow. Исключения полностью его меняют, перелопачивая весь стэк, пока не найдется catch блок
Это, пожалуй, можно воспринимать и как catch блок ровно на одну команду.
check пишется прямо перед вызовом функции (а не на целый блок, в котором могут быть функции, выбрасывающие ошибки). в этом плане оно похоже на try! из Rust или Swift.Любопытно, можно ли будет делать так:
handle err { ... }
// ...
check func() error {
// ...
}()
И если можно (не вижу причин в обратном, можно ведь передавать анонимную функцию в блок defer), как по вашему личному мнению, будут ли программисты активно пользоваться такой возможностью?
с исключениями это не так: множественные catch конструкции будут вызываться в зависимости от ошибки.В некторых языках нет множественных catch конструкций. Например в Javascript.
Разница же достаточно существенна:
— в catch/handle проверка ошибки очень явная – check пишется прямо перед вызовом функции (а не на целый блок, в котором могут быть функции, выбрасывающие ошибки)
Получается, что это сахар — просто блок try размером в одну строку/команду?
— всегда легко понять, какой именно код будет исполнятся при check – с исключениями это не так: множественные catch конструкции будут вызываться в зависимости от ошибки.
Есть же универсальные catch конструкции на все виды исключений.
func (File f) Destructor() {
f.Close()
}
func CopyFile(src, dst string) throws error {
r := os.Open(src)
w := os.Create(dst)
io.Copy(w, r)
}
Простите, С++ в голову ударил.
Это приятный, чистый и элегантный код. Он также некорректый: если io.Copy или w.Close завершатся неудачей, данный код не удалит созданный и недозаписанный файл.
Это решается конструкцией finally…
Данный же подход в go эту проблему не решает.
Не хватает типов для ошибок
Почему? Никто не мешает делать type switch в теле хендлера, как в случае обычной обработки.
Вообще этот дизайн КМК основной целью имеет убрать бесконечные if err != nil { ... }
func printSum(a, b string) error {
handle err { return err }
x := check strconv.Atoi(a)
y := check strconv.Atoi(b)
fmt.Println("result:", x + y)
return nil
}
Почему не
func printSum(a, b string) error {
x := check strconv.Atoi(a)
y := check strconv.Atoi(b)
fmt.Println("result:", x + y)
return nil
}
?
godoc.org/github.com/pashaosipyants/errors
Псевдокод, указанный в примере кажется избыточным. В реальности же, ошибки могут быть самыми разными, и сообщения и операции будут персонализированны.
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
if _, err := io.Copy(w, r); err != nil {
w.Close()
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
if err := w.Close(); err != nil {
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
}
«Индустриализация» обработки ошибок, методами, подобными «check» не уместна и только усложнит читабельность и поддержку кода.
Обработка ошибок в Go 2