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

Комментарии 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` несёт смысл в самом названии и, даже без чтения спецификации/книжек можно догадаться о его функции, а вот `~` и `@` уже никак не догадаешься сходу.

Ой как весело комментировать только половину фразы.


А как вы понимаете надо ли обрабатывать ошибку от 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% есть способы копнуть глубже.
On Error Resume Next
Больше похоже на ON ERROR GOSUB.
catch (error) {
...handle error...
} try {
... do something...
}

чутка непривычно, но я смогу с этим жить.
Исключения мало что общего имеют с этим. Явная проверка ошибок, отсутствие stack unwinding. Роднит лишь прыжок в общий блок обработки. В Go просился синтаксический сахар и это один из его вариантов. Наиболее близкий аналог это ошибки в Swift, где, несмотря на try/catch, с исключениями тоже ничего общего.
Абсолютно согласен, это совсем не исключения.
Мне, как разработчику на go, очень не хватает како-то модификатора функции, или макроса, например, чтобы:

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

2. на этапе компиляции указывать компилятору закрывать все открытые ресурсы после завершения функции

под завершением работы функции имею ввиду любой завершение, паник, корректное (или возврат ошибекИ)
Вроде defer должен решить вашу проблему :)
посмотрите на runtime.SetFinalizer
Финализаторы вроде как крайне не рекомендуют. Тут про C#, но многое применимо и к Go.

Я бы использовал defer, как рекомендует коллега выше.
handle err {… } это возрожденный ON-unit из PL/I? Казалось бы отцы-основатели структурного программирования высмеяли такие штуки навсегда, но нет.
НЛО прилетело и опубликовало эту надпись здесь
Расширить функциональность ошибок тоже входит в их планы и описано в черновике по-соседству
Да, это будет в следующей статье.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

От слова check рябит в глазах

Так что пока неизвестно, будет ли это Go 1.34 или Go2. Сценария Python 2/3 не будет железно.

Это значит что они планируют сохранить обратную совместимость?
Да, это приоритет номер 1 во всех этих изменениях.
Расскажите им про монады что ли…
НЛО прилетело и опубликовало эту надпись здесь
Толстенько. Они действительно не особо нужны, когда не теории на бумаге рисуешь, а код пишешь. Go дает все необходимое из коробки и спокойно так десять лет прожил, отвоевав себе приличный кусок рынка. Авторы языка самой идее никогда не противились и с самого начала хотели добавить, но должной реализации не было. Сейчас есть куча разных предложений, но все имеют свои плюсы и минусы. Поэтому вопрос дженериков опять поднят прямо как при создании Go 1, но они могут и не появиться. Авторы и комьюнити не находятся в отчаянном положении, лишь бы добавить какую-то реализацию. Ничего не понравится — ничего не добавят. Может зато потратят время на другую идею, т.к. в Go 2 авторы менять сразу много тоже не хотят. 2-3 крупных изменения, а изменений на предлагали полно.
Ну даже написали почему не добавили generics — golang.org/doc/faq#generics
Ниже там и про exceptions, assertions, etc.
Дак кто ж это читает. Язык не интересен настолько, чтобы разобраться, но настолько, чтобы сраться в комментах. Ситуация знакомая, так что лицемерить и не буду даже. По-хорошему, авторы дали ответ наверное на все претензии к языку. Особенно почему нет той или иной фичи из языка Х

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

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

Ну, словари и слайсы — это дженерики. Просто авторы Go могут в дженерики, но другим не дают.


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

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

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


Мне запомнился с GopherCon один потрясающий доклад про рейтресер, и в конце спикер поделился некоторыми моментами, с которыми он столкнулся, будучи новичком в Go. И вот один из них был момент "просветления", когда он вместо того, чтобы повсюду использовать тип Vector3 сделал следующее:


type Direction Vector3
type Energy Vector3
...

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


И, вот, если подумать, то что 90% использования interface{}, что Vector3 в этом примере, что дженерики (в большинстве своём) – это проблема из одной плоскости: не умения чётко описывать проблемную область на языке типов данного языка программирования. Как можно догадаться, одни языки этому помогают, другие мешают.


Обобщать (generalize) легче, чем чётко раскладывать проблемную область по полочкам и описывать каждую абстракцию своим типом. Поэтому, если люди приходят с других языков и используют interface{} вместо того, чтобы правильно описывать типы, это скорее PTSD от других языков, а не признак востребованности дженериков.


Кстати, класс-ориентированное ООП тут отдельные палки в колёса вставляет людям, но это уже совсем другая история.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Можете объяснить, каким образом «достаточно обобщённых компонентов» решает фундаментальную задачу транслирования ментальной модели на код, о которой я говорил?
НЛО прилетело и опубликовало эту надпись здесь
Может тогда лучше на C# посмотреть как одну из самых успешных реализаций? Чего сразу неудачи Java вспоминать.
НЛО прилетело и опубликовало эту надпись здесь
По примерам в статье складывается ощущение, что вместо check/handle достаточно было повторить для Go-шного defer идею D-шного scope(exit/success/failure). Т.е. чтобы можно было писать defer(success) и defer(failure) в дополнение к нынешнему defer. Тогда можно было бы продолжать использовать существующий механизм обработки ошибок, но при этом делать какие-то специфические действия при выходе из-за ошибки (удалять ненужные файлы, например).
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 при ошибке это бонус предлагаемого решения. У других предложений такого бонуса нет. Основные требования — уменьшить кол-во кода на обработку ошибок, сохранить все преимущества текущего подхода, упростить добавку контекстной информации в ошибки. Ваш пример ничего этого не решает.
В текущем механизме обработки ошибок в Go со стороны видится две проблемы*:

1. Многословность проверок кодов ошибок.

2. Сложность с написанием кода по очистке ресурсов и/или откату каких-то операций при обработке ошибок.

Первая проблема может быть решена тем или иным синтаксическим сахаром. Например, использованием ключевого слова check или префикса/суффикса '?'. Вроде такого:
w := check os.Create(dst)
// или
w := ?os.Create(dst)


Вторая проблема решается за счет defer(success/failure).

* По поводу «проблем». ИМХО, своим успехом Go обязан именно такому вот примитивному и прямолинейному коду, который не требует высокой квалификации от разработчиков и делает код разных людей максимально однотипным. Так что, что кажется проблемой, ИМХО, на самом деле является одной из важнейших составляющих привлекательности Go в современном мире.
Сложность с написанием кода по очистке ресурсов и/или откату каких-то операций при обработке ошибок

Если честно, эту проблему я слышу сейчас первый раз. Я давно слежу и участвую в дискуссиях по поводу ошибок в Go 2, но ниразу я не встречал именно эту проблему. Да и на практике тоже.
Если честно, эту проблему я слышу сейчас первый раз.
Значит вы не читали обсуждаемую статью, ибо в разделе «В чём проблема с обработкой ошибок в Go?» об этом и идет речь. См. пример из этого раздела, в котором os.Remove() нужно вызывать в двух if-ах. Это оно и есть.
Статью я читал. Я говорю о том, что было раньше. Обсуждение предложений для Go 2 началось намного раньше. Еще раньше народ просто в бложиках постил, чего им в языке не нравится. И вот эта проблема встречается мне первый раз сейчас. Она может быть где-то и встречалась, но намного реже. Вся критика все это время была направлена именно на многословность. Поэтому и удивило, что сейчас внезапно это всплыло как одна из проблем языка.

Я даже на практике не помню проблем подобных. Удалить файл при ошибке — ерунда, это не требует изменений в языке. В том же самом defer можно банально проверить наличие ошибки и выполнить нужный код.
Я даже на практике не помню проблем подобных. Удалить файл при ошибке — ерунда, это не требует изменений в языке. В том же самом defer можно банально проверить наличие ошибки и выполнить нужный код.
Ну значит вообще весь этот огород с handle err не нужен, если по вашей практике судить. Достаточно лишь ввести конструкцию, которая будет аналогом Rust-овских try! и '?'. Делов-то.

Вы это Google-овцам расскажите, а то они не знают, что на практике проблем нет, выдумывают какие-то handler-ы.

А defer(failure) можно реализовать через проверку:


defer func() {
    if err != nil {
       ...
    }
}
Я пишу на с++, на го не пишу, но периодически почитываю статьи про другие языки, особенно про такую важную тему как обрабтока ошибок. Мне это новая обработка ошибок на го очень сильно напоминает с++ on_scope_exit концепцию/семейство функций. Ваш хендл это on_exception ваш дефер это on_scope_exit. Осталось добавить on_success аналог дефер который вызывается только если не было ошибок и относиться к проверяемым ошибкам как к эксепциям т.е. если функция вернула ошибку и она была вызвана с чеком то вызывать все подряд (дефер/хэндл) в порядке обратном объявлению. Дефер и хендл суть одно и тоже только дефер более общий случай, вызывается всегда неважно есть ошибка или нет. Нет смысла городить отдельные (от дефер) правила порядка вызовов для хендл.

Кому интересны подробности из первоисточника и кто не знаком с с++ и/или концепций on_scope_exit гуглите по Андрей Александреску с++ декларативное программирование. Я смотрел/читал все на английском поэтому не уверен, что мой русский перевод нагуглиться. Вот как раз местный пост кому лень гуглить.
Вы можете написать блог пост и добавить в вики с отзывами, там и расскажете авторам Go, что нет смысла городить отдельные от дефер правила для хендл. )
Мне инетерсна тема обработки ошибок и поэтому я написал этот коментарий здесь, но у меня нет ни одного стимула помогать развитию го и поэтому я не собираюсь ничего добавлять в указанное вики. Нужно понимать что просто заметка в вики скорее всего ничего не способна изменить. Что бы реально на что то повлиять человек должен быть готов долго и нудно отстаивать превосходство своих идей над альтернативами, а это очень тяжелый труд.
Видимо недостаточно она вам ещё интересна. :)

Вас не смущает, что 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, а то хендлер проигнорируется.


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

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

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

Но вы также поднимаете и реальные проблемы — совместимость со старым кодом. Думаю хорошим рабочим решением будет ворнинги компилятора детектирующие описанные вами кейсы так, что го еще рано закапывать, хотя персонально я разделяю ваше оценочное суждение по поводу качества го в целом как языка.
Не согласен. Текущее предложение наоборот, помогает не обрабатывать ошибки. Облегчение проброса наверх — хорошее дело, код не так замусорен, кроме того, необходимость писать после каждого вызова if err != nil немного препятствует декомпозиции, зачем выносить 4 строки в отдельную функцию, если вызов этой функции с последующей проверкой ошибки займёт те же 4 строки. Тем не менее, именно обработка ошибок ничуть не улучшается. Для обработки потребуется if или свитч по типу ошибки и гоу не подскажет в случае пропущеного варианта, в этом плане джава с её проверяемыми исключениями лучше. Гоу фактически стал аналогом других языков, где проверка ошибок сводится к ловле любых исключений на верхнем уровне.
С ворнингами или линтером тоже непонятно, вот, скажем, у меня стоит свитч по типу ошибки, в одной ветке я ошибку исправляю и продолжаю дальше, в другой я выполняю 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
Есть реальная возможность повлиять на дизайн:


Вы, предположу, не подписаны на кейсы по обработкам ошибок и генерикам.
Гугл в очередной раз всех поимел сделал все по своему (как с vgo vs dep).
Ну и удивительно, что они выбрали такой фидбэк от коммьюнити а не вообще обычную почту.
Что, простите?
На дизайн не повлиять — гугл выкатил и после «обсуждения» просто сделает это.
Постить ссылки в вики безполезно — гугл второго раунда делать не станет.
Точно также как было с «обсуждением» vgo.
Так называемому коммьюнити politely дают возможность промеж себя пообщаться и кушать, что дают. В кейсах по этим пропозалам гугловцев не было и они никаких хинтов там не постили.
сегодня видел статистику — за второй квартал 2018 года коммитов в Go от не-гуглеров больше чем от гуглеров.

И это ничего не меняет.
Гугл имеет свой интерес и профит и все решения принимает сам. Да они скорее всего пистонов отхватили за то как vgo выкатили, и теперь все могут аж напихать ссылок. Они даже в оригинальных кейсах линки на пропозал не воткунли. Wtf?

Ухты, conspiracy theories у нас ещё не было. Продолжайте)
Давайте я вам про type aliases еще напомню. Вот где был заговор всемирный.
Хахаха — ты про эти два кейса? github.com/golang/go/issues/16339
github.com/golang/go/issues/18130 — так это только подтверждение что фидбэк им только мешает — изза публичной дискуссии они релиз пропустили.
Тоесть вы сейчас выискиваете по issues все случаи, когда Go team не согласился с предложенным изменением языка, и этим доказываете свою теорию заговора? Ок.
Wat?!
Тоесть эти два «не связанные» пропосала внесла не го тим и даже не гуглеры?!
И фича гуглу не нужна была вовсе?
И релиз они не пропустили?
А форсить пользователей постить линки на вики страничке (где подписаться на изменения нельзя) это несомненно улучшение процесса?!
Мне очень сложно понять, что вы пытаетесь сказать. Я же не могу думать, что вы пытаетесь с помощью cherrypicking-а доказать какую-то точку зрения – это было слишком оскорбительно так думать о Вас. Но пока что сложно понять, что вы хотите донести.
Я понял ваше мнение, но хорошо зная многих людей и из Go команды и из коммьюнити, я не вижу ни единого подтверждения вашим словам. История vgo vs dep, которую вы так удобненько пытаетесь представить в виде доказательства, совершенно тут не к месту, в ней своя история и свои проблемы, и мы можем на эту тему пообщаться, если хотите.
Хотите верить, что давать конструктивный фидбек, когда его просят – бесполезно: пожалуйста. Только не подавайте это как какой-то факт, и уж тем более, не притягивайте за уши аргументы, о которых другие люди более осведомлены.
Какие именно аргументы притянуты за уши?
Что через «публичные» обсуждения их пропосалы тяжело проходят?
Что вместо осуждения их пропосала через 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 играется — их мнение нам так важно, что мы знать не хотим нравится\не нравиться им то что мы предложили — пусть письменно заявления подают — мы несомненно их рассмотрим.
Что вместо осуждения их пропосала через issue они услажняют процесс обсуждения?

А как надо обсуждать? Если у вас больше опыта в развитии нового языка и коммьюнити, напишите как нужно – если ваш подход будет лучше, то все прислушаются.=
Я бы все же предложил явное указание какой хендлер какому check будет соответсвовать, в функциях с кол-вом хендлеров больше одного. А то так будет сложно отыскивать ближайший блок обработки
Так предлагайте. Есть реальный шанс повлиять на дизайн же.
github.com/golang/go/wiki/Go2ErrorHandlingFeedback

Кстати, я уже встречал такое предложение в одном из отзывов.
Просмотрел фидбек и здесь уже есть подобные предложения. Понравилось вот это, особенно вторая часть, когда вместо имени переменной ошибки подставляется имя хендлера
if result, err = funcName(); err == nil {
//DO SONTHING
} else {
fmt.Println("Error:", err)
}
Так и не пойму, зачем мучить хомяка. Ну не завезли нормальных исключений изначально — бывает, так ведь дело поправимое.
Легаси библиотеки никому переписывать не хочется — это понятно. Можно было бы добавлять «check» или что-то подобное перед функциями, ошибки которых хочется выбрасывать исключениями, а дальше: try, catch, finally — все как у людей.
«Не завезли» исключения по причинам уже озвучинными авторами: golang.org/doc/faq#exceptions. И check/handle вовсе не возврат к исключениям.
Краткий перевод:
«Нам кажется, что правильное использование проверенной временем конструкции „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.

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

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


  1. Обработка ошибок (и ее фиксят)
  2. Отсутствия дженериков (и как следствие, я писал, что все используют вместо них interface, но нападать на интерфейс как-то… странно, и я такого не помню).
  3. Идеи с окружением (GOPATH, версионность по коммитам вместо нормальный пакетов), и это фиксят
  4. Идеи с тем, что комментарии можно спокойной использовать как метаинформацию без отделения специальным синтаксисом. И в этом я был прав, у go теперь есть прекрасная easyjson и писать комментарии на go стало еще немного страшнее).

Я что-то еще забыл?

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

Вырывать комментарии из контекста не очень красиво.


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

Мы разговаривали не про какие-то абстрактные воркеры, а про горутины.
А вот ваш комментарий
А еще обратно к реальному миру, мне кажется, вместо каналов вы чаще будете использовать какие-то другие MQ, например nats.
Создается 10 goroutine, которые читают с одного канала и обрабатывают сообщение. Есть рутина, которая пишет в канал (не важно как, например, читает с файла и пишет в канал). Обработка сообщений затратный процесс (потому один пишет и много читают). Самый обычный кейс.
При этом, после Ctrl+C завершается работа всех goroutine и после этого приложение останавливается.

А вот ваша задача. Я воспринял это как опять же паттерн "мастер ставит задачи — воркеры выполняют". Поэтому и сказал о внешних очередях.

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

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


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

Это тут причем?
Мы говорили про concurrency в Go. Вы сказали, что в питоне не хуже, потом привели пример с IPC и процессами.
Очевидно, потому что я немного неправильно выражаю свои мысли. Ну и еще немного неправильно в целом думал, вы меня поправили.

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

Скобочки я расставил для себя, что бы убедится, что опять не напутал термины и вы меня правильно поймете.
Мы можем конечно повторить разговор — вы покажете, как сделано concurrency в питоне (с IPC и процессах), я скажу что это костыли, а вы, что «мне кажется, вместо каналов вы чаще будете использовать какие-то другие MQ, например nats.»

Ну, это костыли, которые призваны решить проблемы, с которыми эти конкретные костыли еще не справились. Я так же мог использовать треды в 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.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Спасибо за такое детальное пояснение, кэп. Если вы им пытаетесь объяснить почему отдельные люди, не имея опыта и квалификации, обладают мотивацией чтобы писать сотни комментариев, с пеной у рта рассказывая, какой плохой язык Go, но не могут писать конструктивную критику, то у вас получилось. Теперь понятно.
НЛО прилетело и опубликовало эту надпись здесь
В java такой проблемы, например, нет и всегда понятно, какое исключение может выбросить функция

Ну да, поэтому в большинстве Java программ просто выбрасывается General Exception и ловится в самом верху – всегда понятно, какое исключение :)

А go вместо General Exception у вас есть error и ничего не поменялось. И на java, и на go у программистов есть все возможности нормально разграничить ошибки по типам и сделать их грамотную обработку. Но почему-то все равно везде чистые error и обработка ошибок где-то на самом верху.


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

Но почему-то все равно везде чистые error и обработка ошибок где-то на самом верху.

Ваше утверждение не верно и кодом на практике не подтверждается. Обработка ошибок всегда в месте их выброса. Конкретные error, а не просто строкое значение, нужны очень редко и используются, когда это необходимо. Go никак это не заставляет делать (можно вообще ошибку игнорировать и никто не скажет ничего), эту проблему я в предложениях озвучивал на гитхабе. К счастью, команда Go изначально озвучила best practices и программисты в своем подавляющем большинстве их соблюдают. Программисты ленивые, когда им нужно менять свои привычки. Тут же привычка была привиты изначально.
Ваше утверждение не верно и кодом на практике не подтверждается. Обработка ошибок всегда в месте их выброса

У меня есть два пруфа:


  1. Почитайте в этой статье про дефолтный хендлер
  2. Вот я открыл случайный файл в moby/moby и что же я там вижу? 4 места обработки ошибок и все только то и делают, что пробрасывают ошибку наверх. Если вы так не делаете — это круто и достойно уважения, но это не так работает.
В большинстве случаев проброс ошибки наверх действительно работает и ничего более делать не приходится. Но это намного лучше исключения, ибо явно от начала до конца. Все таки с ошибкой разбора JSON более ничего не сделаешь. Факт в том, что Go изначально привил best practices, и они в основном соблюдаются. Было бы лучше, если бы игнорирование ошибки было ошибкой компиляции, что я предлагал, но наверное это не сделают. Но даже так, невероятным образом это работает лучше исключений.

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


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

Читал достаточно кода на Go (на Java — ещё больше), имею сказать, что в 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 это не логическая ошибка сама по себе.

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


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

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

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

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


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

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

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

Ухты, поздравляю.
Ну так вы уже прочитали и признаете, что в этом случае были не правы? Или от комментариев, в которых вы упрекаете меня в отсутствии логики будете переходить к саркастическим?
Количество derailing-а в общении с вами превышает все допустимые нормы, поэтому завершим это «общение» сарказмом, да.
НЛО прилетело и опубликовало эту надпись здесь

Вы про них?

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

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


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

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

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

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


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

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

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

Ваши оценочные (о)суждения мне не близки.
Мой изначальный комментарий вы опровергаете ничем не подкрепленным антитезисом:
Причина падения важная всегда

Если не сталкивались с ситуациями, когда она не важна — еще столкнётесь.
Мне очень сложно понять, в каких ситуациях причина падения может быть не важна.
Вот у вас есть программа. Она упала. Вы ее сразу выкидываете или продолжаете с ней работать дальше? Если продолжаете работать дальше, а она опять падает, вы будет игнорировать проблему? Или как это работает?
> Или как это работает?
Именно так и работает
в большинстве Java программ просто выбрасывается General Exception и ловится в самом верху

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

Возможно вы имели в виду вот такой кейс:
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»?
Под General Exception я имел ввиду просто 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% — тут даже цитата в блог посте есть:
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.
Раз уж привели статью, то ссылайтесь на нее, а не какой-то левоватый блог. Да и что это за проценты такие? 100% — это что? 78% от чего? 78%+84% — это как?

Вот вам цитата из статьи
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 пейпера, которые показывают вашу неправоту. Приятно вести дела с таким собеседником!
Проблема в том, что нет информации, какая функция вернет ошибку прямо в call site. Для этого нужно лезть в прототип функции. Так что эта проблема исключений свойственна им по определению и все современные языки предлагают этому решение.

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


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


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

Ну и какую информацию здесь мне дает Java?
void Foo() throws Exception {
    foo.bar1();
    foo.bar2();
}


И это у Java описаны исключения, а у других это только в документации вроде C# и C++.
Тут никакую, а если воспользоваться ide, то подсказка по функция вполне помогает.

Ну, как я уже писал, это вопрос реализации.
если воспользоваться ide

Вот именно. Нужна ide, которая это умеет, но это не главное. Главное, что тратится драгоценное время на чтение кода. Это Go и не хочет повторять. К тому же, это не единственная проблема, которая заставила отказаться от исключений.

Почитайте код написанный на go на github, хотя бы раз. После того, как вы перероете все 20 файлов в папке в поисках структуры Config вы поймете, что в go ситуация с "мне нужна ide" еще хуже.


Это не то, что бы плохо, но вот это "не хочет повторять" не соблюдается.

Рыть ничего не нужно. Есть Godoc.

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

Сами сменили тему и теперь пытаетесь свою правоту доказать этим. Мне не нужно что-то сторонее, потому что best practices привили обрабатывать всегда ошибку. Поэтому я читаю код, и он намного более ясный, чем код с исключениями. Я вижу по call site, где и когда поток исполнения может изменить свое направление, потому что есть явный return. Это то, что хотела команда Go, отказавшись от исключений, и они своего добились. Сейчас проблема стоит совершенно другая — best practices привели к тому, что код наводнен обработкой ошибок. Этот код надо бы как-то сократить, не утратив все преимущества полученные. Это и есть задача Go 2.

Перечитайте с самого начала, в чем изначально проблема Java и исключений в целом. Не в документации, а в том, что call site ничего не говорит. Этой проблемы в Go нет.

Я не вижу проблемы в том, что "call site" не говорит о том, что может возникнуть ошибка в языке, где исключение может возникнуть при любом вызове. Это надуманная проблема.

А множество других видит, поэтому в Go, Swift, Rust пошли другим путем.

исключение может возникнуть при любом вызове

Это и есть проблема, язык таким быть не должен. Вот `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. Вопрос именно в реализации.

Такая формулировка лучше? :)
Лучше, но я не встречал такой реализации. Видимо, этому есть причина.
Лучше, но я не встречал такой реализации. Видимо, этому есть причина.
Ну конечно есть! Причина — вся эта «проблема» надуманна и высосана из пальца. В чем, собственно, тут вас (на пару с creker) и пытаются убедить. :D
А, я кажется понял.

Вы не прочитали мой изначальный комментарий, только увидели код. Ну и подумали что это я предлагаю заменить `check` на `!`. Нет, не предлагаю. Прочитайте мой комментарий :)
Нет, я его и прочёл. Просто давно потерял нить того, с кем/чем (и зачем) вы спорите и что пытаетесь доказать :)
И внезапно мы получаем ошибки из Swift. Это замечательное решение, но уже исключениями оно не является, т.к. сейчас исключения означают вполне конкретную реализацию, которая универсальна для всех языков.
Это замечательное решение, но уже исключениями оно не является, т.к. сейчас исключения означают вполне конкретную реализацию, которая универсальна для всех языков.
Подождите, подождите. Т.е. заставить в имя функции добавить некий суффикс в конец если она может кинуть исключение — это уже не исключения? Ну допустим…

А что же тогда такое `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. Можете ответить односложно, но только «да» или «нет» :)
Ну да
Ну да — тоже легаси. Или «ну да» — это выброс исключения?

или вы думаете что checked exceptions были уже в 1.0?
Конечно были =)
А вы считаете иначе?
Считаете ли вы, что код `throw new Exception()` выбрасывает новое исключение в Java.
А что такое исключение?
Почитайте что под исключением понимается в Java.

Вообще я не особо хочу тратить свое личное время и объяснять вам что такое исключения, как они работают в Java и т.п. Есть куча отрытых источников на этот счет. Ну или можете записаться на какие курсы по Java.

Когда узнаете немного больше чем ничего (блин, это же надо додуматься сказать, что в java 1.0 не было checked exceptions...), тогда я с радостью продолжу нашу дискуссию.
docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
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.
Т.е. это больше как возврат ошибки
Ну вы как минимум заглянули одним глазком в документацию Java — это уже конечно не «ничего».

Правда, похоже у вас тот же недуг, что и у divan0 — читаете текст, а смысл его уловить не можете. Там было 2 вайтпепера, тут документация Java…

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


Хм, а на… ч на хабре оно не, не тратится? ;)
Я конечно еще немного на GO программировал, но мне предложенный концепт — нравится.
Если бы это предложили с самого начала — было бы еще лучше.
Мне кажется очень круто! Но, да, порядок обработки и скоупы вносят некую путаницу и какое то
время придется привыкать, как и в случае со случайным сокрытием переменных.
Несмотря на годы скачек по граблям, религия так и не позволила им взять и просто впилить try/exception/finally. Такая верность убеждениям заслуживает похвалы. хихихи.
Что за религия?
Если бы религия позволяла, то ваш пример можно было бы записать кратко, понятно, логично и не придумывая забавных костылей примерно так:
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 нет не потому, что авторы Go в них видят проблемы и неудобства, а потому что просто хотели сделать «не как у других»?
Из вашего текста абсолютно не понятно в чем же принципиальная отличие check/handle от try/catch. Да есть фраза «существенно отличается от исключений», а вот чем, я не понял. Если сможете сформулировать коротенько и понятно здесь, то было бы прекрасно.
Если кому-то не понятна разница между check/handle и try/catch, то правильнее спросить в комментарии «в чём разница», а не наезжать на авторов языка. Попробуйте в следующий раз.

Разница же достаточно существенна:
— в catch/handle проверка ошибки очень явная – check пишется прямо перед вызовом функции (а не на целый блок, в котором могут быть функции, выбрасывающие ошибки). в этом плане оно похоже на try! из Rust или Swift.
— нет неявной развертки стека. максимум, что handler может сделать – пробросить наверх выше. тоесть, по сути, это синтаксический сахар для того, что вы могли бы делать в `if err != nil` блоке
— всегда легко понять, какой именно код будет исполнятся при check – с исключениями это не так: множественные catch конструкции будут вызываться в зависимости от ошибки.
Учитывая что в блоке try может быть вызвана всего лишь одна функция, а в с++ и др. языках даже не обязательно явно выделять блок фигурными скобками, то вы получаете ровно то что и catch/handle, за исключением конечно, что у вас сначала определяется обработка ошибки (catch), о потом проверка не произошла ли она (try). Извините, но я не вижу отличий, за исключением того что все поставлено с ног на голову, но это в духе религии Go.
Учитывая что в блоке try может быть вызвана всего лишь одна функция

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

Судя по этому ответу, вы даже не удосужились распарсить мой пост. За сим откланиваюсь, с голубями в шахматы не играю.
Вы, наверное, и с компилятором так общаетесь :) Удачи.
Простите, но вы ведь и в самом деле ответили невпопад…
И то верно. Зачем утруждать себя аргументацией, если можно нахамить и высокомерно слиться?
Обычно я не капитанствую, но вам отвечу. Человек мне пишет «В классическом понимании исключений try..catch в блоке try можно писать сколько угодно инструкций». Надо полагать он посчитал, что я этого не знаю. Окей, но в моем примере который вроде бы послужил отправной точкой этого дискаса в блоке try как раз таки не одна инструкция. Из чего я сделал вывод, что человек даже не удосужился вникнуть в суть вопроса, «но осуждает». Вести беседу с такими людьми бессмысленно, даже если они сами этого сильно хотят. Они тебя все равно не услышать.
Это у вас все с ног на голову. Ни исключений не понимаете, ни статьи предоставленной. Фундаментальное отличие в том, как обработка ошибок влияет на control flow. Исключения полностью его меняют, перелопачивая весь стэк, пока не найдется catch блок. Здесь же всего лишь синтаксический сахар для `if {return }` с бонусом в виде `handle` блоков. Control flow остается очевидным и читаемым по тексту программы. Если и так непонятно, но вперед читать ошибки в Swift и Rust. Там тоже нет никаких исключений и механизм примерно тот же, какой хотят в Go.
Если и так непонятно, но вперед читать ошибки в Swift и Rust. Там тоже нет никаких исключений и механизм примерно тот же, какой хотят в Go.
Сможете показать обработчик ошибки в Rust, который определяется задолго до места, в котором ошибка диагностирована?
Спасибо, повеселили.

а что не так? Если не определять 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);
Ну вы хотели пример обработчика, который определяется перед тем, как диагностирована ошибка. Rust легко позволяет такое сделать. Если вы хотите еще и цепочку обработчиков — просто вызывайте из одного другой.
Вы не показали такого обработчика. Ваше определение:
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, r)? :)

НЛО прилетело и опубликовало эту надпись здесь
гугли «религия не позволяет», только обязательно в скобочках
Это приятный, чистый и элегантный код. Он также некорректый: если io.Copy или w.Close завершатся неудачей, данный код не удалит созданный и недозаписанный файл.

Это решается конструкцией finally…

Очевидно же, что не решается. Тут нужно удалить файл, только если две из операций (Copy и Close) вернули ошибку, в остальных случаях – не нужно.

В try catch дизайне можно обрабатывать разные типы ошибок.
Данный же подход в go эту проблему не решает.
Не хватает типов для ошибок

Почему? Никто не мешает делать type switch в теле хендлера, как в случае обычной обработки.
Вообще этот дизайн КМК основной целью имеет убрать бесконечные if err != nil { ... }

А что делать если есть 2 вызова на чтение файла, но файла нет, а обработать надо по разному?
вариантов очень много, можете держать названия файла в структуре ошибки и потом сначала проверить тип, а потом поле с файлом
можете держать названия файла в структуре ошибки и потом сначала
Это библитечная функция, там есть только причина «file_not_found»
тогда можно не использовать общий хендлер и создать специальный или обработать просто с if err != nil

Просто сделать обработку руками, по старому. После чего, если очень надо, сделать check err чтобы отправить ошибку в полёт по стандартной цепочке хэндлеров.

что будет switch проверять? text contains в ошибке?
type switch
это возможно если создать свою структуру кастомной ошибки
часто это просто error.New
ок, спасибо
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
}

?

Судя по тому, что хотят дефолтный хендлер добавить вида handle err { return err }, то так и будет, как вы написали во втором случае

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

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

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» не уместна и только усложнит читабельность и поддержку кода.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории