LINUX.ORG.RU

golang - не хочу возвращать err, хочу паниковать!

 , , обработка ошибок


0

3

Какая-то секта с этими err. Код распухает в несколько раз. Идея с defer выглядит довольно здравой - я в своё время делал такой defer для 1C и для Delphi. Но паника лучше, чем возврат err-ов. Таковой возврат ничего не упрощает. Когда выпадает исключение, сразу виден весь стек. Сгенерированный err не показывает места своего возникновения, т.е. с помощью брекпойнтов нужно много итераций, чтобы локализовать ошибку. А на fatalpanic есть чуть ли не встроенный брекпойнт, во всяком случае, у меня на fatalpanic отладка сама по себе останавливается.

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

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

★★★★★
Ответ на: комментарий от den73

Владимир

Спасибо!
Добротный анализ ...
Моя любимый «Helllo World» - код подсчета суммы натурального ряда чисел от 1 до 100.
Так даже такой код в go не опробовал /других задач много/.
Но вот с архитектурой объектов go познакомился - на первый взгляд весьма толково.
Если этот язык не превратят в мусорную свалку, то буду рад за него.

Скорее всего текст go легко конвертируем к C /со всеми вытекающими .../

anonymous
()
Ответ на: комментарий от anonymous

Речь про панику в обработчике.

anonymous
()
Ответ на: комментарий от den73

А иметь три машины ради какого-то тривиального сайтика - это перебор.

Так для тривиального сайтика не нужно никакого приложения. В лучшем случае скрипты на PHP, а то и вовсе можно сгенеренной статикой обойтись. А для нагруженного сервиса может понадобится не 3, а все 33 машины.

anonymous
()
Ответ на: комментарий от eao197

Нет исключений — ничего не платишь.

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

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

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

annulen ★★★★★
()
Ответ на: комментарий от annulen

Веб-сервер и сервер приложений так вообще испокон веков смешивают, начиная с Апача.

Это как раз прошлый век. Никто сейчас сервер приложений голой жопой в инет не выставляет.

anonymous
()
Ответ на: комментарий от RazrFalcon

В Scala их нет. Потому как тип Option и стандартная обработка исключений покрывают все юзкейсы checked exceptions.

LongLiveUbuntu ★★★★★
()
Ответ на: комментарий от den73

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

vlad9486
()
Ответ на: комментарий от anonymous

Никто сейчас сервер приложений голой жопой в инет не выставляет.

Но однако же все современные серверы приложений в том или ином виде поддерживают HTTP, то есть по факту являются веб-серверами

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

У меня недавно было, был дженерик параметр реализующий crypto_mac::Mac у него есть метод

fn new_varkey(key: &[u8]) -> Result<Self, InvalidKeyLength>

Смотри здесь.

Мой код был что-то типа

impl<T: Mac> Foo<T> {
    fn bar<B: AsRef<[u8]>>(key: &B) {
        ...
        T::new_varkey(key.as_ref()).unwrap()
        ...
    }
}

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

Я написал комментарий что то что сюда подставляется должно мочь создаваться с ключами любой длины и .unwrap(). Это нельзя было выразить в типах.

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

vlad9486
()
Ответ на: комментарий от den73

В моём случае это, например, неудачный коммит транзакции по неизвестной причине

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

feofan ★★★★★
()
Ответ на: комментарий от RazrFalcon

Хорошо, вы работаете с деньгами, произошел какой-то нереальный 3.14 на интегрируемой бирже, вместо нужного вам ордера начали создаваться очень кривые. В секунду тысяч 5-10 таких вот ордеров - т.е. В таком случае кидать простой warn или error в систему уведомлений просто опасно. Человек может не среагировать. Робот же в свою очередь не сможет правильно решить проблему. Это раз.

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

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

Программа не должна падать в принципе

Приводят к реальной жести

deterok ★★★★★
()

Я использую panic'у только там где реально программа не может продолжить работу (при инициализации база не работает например), err использую в остальных случаях. Лично мне неприятен подход через ручную проверку err, но такого цена. Я бы предпочел анало питонячьих исключений.

deterok ★★★★★
()
Ответ на: комментарий от deterok

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

feofan ★★★★★
()
Ответ на: комментарий от feofan

Эм,

необработанные исключения стреляли в продакшене
явная обработка радуют сильно больше

Ну т.е. если бы они были обработаны то было бы совсем иначе :)

Я например словил несколько игноров ошибок при работе с ценой в одном проекте, что-то типа

price, _ := getPrice(...)
Где _ - весело игнорируемая ошибка, а в price прилетал объект типа Decimal и дальше неправильно проверялся на 0.

И вот такое у новичков и не только сплошь и рядом в Go.

А в Python встречал вообще вот такое:

raise RuntimeError(...) from None

И иди ищи откуда что прилетело...

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

deterok ★★★★★
()
Ответ на: комментарий от deterok

весело игнорируемая ошибка ... сплошь и рядом в Go

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

anonymous
()
Ответ на: комментарий от deterok

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

А говно можно написать на любом языке.

Безусловно.

feofan ★★★★★
()
Ответ на: комментарий от RazrFalcon

А как ее обработают?

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

vlad9486
()
Ответ на: комментарий от RazrFalcon

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

if r.is_err() {
    /* ну ладно, раскусили, делаю нормально */
}

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

vlad9486
()
Последнее исправление: vlad9486 (всего исправлений: 2)
Ответ на: комментарий от vlad9486

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

Вспоминается f64::from_str_radix который паникует, если левый radix подставлен. Но в данном случае это можно решить типами, правда это будет длинный enum от 2 до 36.

RazrFalcon ★★★★★
()
Последнее исправление: RazrFalcon (всего исправлений: 1)
Ответ на: комментарий от deterok

Это вопрос философии. Скрипты могут падать. Сервер - ну может. GUI - ни в коем случае.

получил периодическую рассогласованость в базе.

Для этого есть транзакции.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

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

vlad9486
()
Ответ на: комментарий от RazrFalcon

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

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

vlad9486
()
Ответ на: комментарий от vlad9486

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

«Плазма падает» - это вот из-за такого подхода к написанию софта, когда падение - это норм.

RazrFalcon ★★★★★
()
Последнее исправление: RazrFalcon (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

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

vlad9486
()
Ответ на: комментарий от RazrFalcon

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

vlad9486
()
Ответ на: комментарий от vlad9486

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

Давай начнём вот с чего. Где бывает безплатный сыр? Есть основания подозревать, что маркетологи учат не совсем тому, чему надо. Дальше, как падают? os.Exit(1). Паника - это не совсем падение, до падения оно обработает все defer-ы. Где гарантия, что наш баг не введёт эти defer-ы в ступор? Программа может не смочь упасть, в зависимости от бага. Значит, обработки баги паникой - это неверный подход, ненадёжный.

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

Дальнейшее я описал чуть выше по теме. Есть категории серьёзности ошибки (например, падение программы, падение обработчика одного запроса к сервису, предупреждение в лог), и есть способы их обработки. Что err != nil, что паника в вычислительном отношении абсолютно одинаковы. У тебя есть какая-то НЁХ. В одном случае это значение с ни к чему не обязывающим интерфейсом err, а в другом - это ни к чему не обязывающее значение, возвращённоё из recover.

Случай паники сводится к случаю возврата err примерно так:

// было
func f1 () (res int, err error) {
 return 0, errors.New("Беда")
}

func main() {
 res, err = f1()
}

// стало
func f2 () (res int) {
 panic(errors.New("Беда"))
}

res, err = func() (res int, err error) {
 defer func() { err = recover() }
 res = f2() 
 return
} ()
Если я ничего не путаю. И в обратную сторону тоже верно - панику можно выразить через return. Т.е. эти механизмы с вычислительной точки зрения тождественны, но требуют разного количества слов в разных ситуациях. То, что один из этих механизмов якобы нужно использовать «при обычных ошибка», а второй - «при необычных» - в этом и есть маркетинг. На самом деле серьёзность ошибки не так связана с тем, какой механизм нужно применять. Фатальная ошибка - это всегда только os.exit, а нефатальная может быть и err, и panic.

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 3)
Ответ на: комментарий от den73

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

Нет. Остальное даже комментировать лень.

feofan ★★★★★
()
Ответ на: комментарий от feofan

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

Кстати, есть ли где-нибудь чёткое определение, что такое «действительно исключительная» ситуация?

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

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

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

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

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

vlad9486
()
Ответ на: комментарий от vlad9486

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

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

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Не понял как ты собираешься падать без помощи паники.

Паника если контракт между частями твоей программы в рамках одного бинарника нарушен. Классический пример: значение переменной должно быть между 100 и 100500, а оно прилетело 100501, причем прилетело не из сети и не из пользовательского ввода и не из другого внешнего источника, а вычислено в твоем же коде. Тогда паника.

vlad9486
()
Ответ на: комментарий от feofan

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

den73 ★★★★★
() автор топика
Ответ на: комментарий от vlad9486

Смотри:

package main

func trap(n int) {
 defer func() { if n>100500 {
  for {}}}()
 if n<100 || n>100500 { panic("Wrong n") }
}
func main() {
 trap(100501)
}
Хорошо падается с помощью паники? В реальной жизни в defer будет какой-нибудь close(), который захочет какой-нибудь мьютекс или ещё что-то, и всё дело зависнет, и ты не узнаешь код возврата.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Если пишешь библиотеку, отвечай за свои деферы. А за пользовательские пусть отвечают пользователи.

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

feofan ★★★★★
()
Последнее исправление: feofan (всего исправлений: 2)
Ответ на: комментарий от den73

Влад дал хорошее определение: паниковать нужно в ситуациях, которые исправляются только изменением кода

feofan ★★★★★
()
Ответ на: комментарий от vlad9486

Ну так мне кто-нибудь даст определение особенно исключительных случаев, когда паника всё же нужна?

den73 ★★★★★
() автор топика
Ответ на: комментарий от feofan

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

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 2)
Ответ на: комментарий от feofan

Влад дал хорошее определение: паниковать нужно в ситуациях, которые исправляются только изменением кода

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

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от feofan

Отлично, если мы потеряли контроль над состоянием программы, зачем нам падать с помощью паники, а не с помощью более надёжного Log.Fatal aka os.Exit(1)?

Я уже приводил пример FFI. Не знаю, как в го, а обычно FFI может повредить память приложения. В языке со сборкой мусора это особенно страшно. И вот мы написали, прости господи, некий assert, который хоть как-то проверяет вменяемость программы после возврата из FFI. Допустим, этот assert не прошёл. Особо нечего здесь обсуждать, кроме os.Exit(1) тут нет вариантов, т.к. у нас может быть повреждён и стек, в результате чего паника не сможет выполниться. Но не буду об этом спорить.

Давай вынесем за скобки эту ситуацию потери контроля над состоянием приложения. Какие ещё ситуации подходят для паники?

den73 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.