LINUX.ORG.RU

Кто должен обрабатывать panic: вызывающий или вызываемый?

 , ,


0

3

Привет всем. Задумался я тут о корректной обработке паник в golang. Предположим у меня есть функция foo, где инициируется паника:

func foo() {
...
if some {
   panic(reason)
}
}
func bar() {
  ...
  foo()
...
}
, где идеологически более правильно делать recover: прямо в foo или можно в bar (и нужно)?

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

seiken ★★★★★
()
Последнее исправление: seiken (всего исправлений: 1)

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

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

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

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

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

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

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

Но вы правы, бывает по-разному, потому жизни вирджин методов тоже важны.

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

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

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

cobold ★★★★★
()
Последнее исправление: cobold (всего исправлений: 1)

Идеологически более правильно recover не делать. panic означает, что случилось что-то плохое и лучше завершить работу. Обрабатывать нужно обычные ошибки (error).

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

don’t panic

так-то, даааа, но…

$ curl -s https://raw.githubusercontent.com/golang/go/master/src/encoding/json/decode.go | grep -c "panic("
19

(девятнадцать панических приступов при парсинге json)

разве в стандартной библиотеке не идеоматические подходы применяются? (trolling level: low)

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

panic им не замена

чуть выше давал ссылку на encoding/json. там паника как раз на манер исключений применяется.

P.S.: ээээ, блин, ни одного recover в том коде. а я как раз про него читал, попав в этот тред! кругом обман!.. гуглил вопрос и в выдаче было как раз "а вот вы на encoding/json посмотрите!

ладно, удалять я это, конечно, не буду 😒

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

Читал исходник?

Мы применяем панику ТОЛЬКО когда случается что то за гранью нормального. Например баг в нашем декодере, или что то изменяет его память во время декодирования.

Ну и даже если ты найдешь такой пакет, это еще не значит что так нужно. Исключения для исключительных ситуаций.

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)

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

Waterlaz ★★★★★
()

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

func foo() {
    ...
    if some {
        // log.Println("Fuck you")
        return
    }
}
korvin_ ★★★★★
()
Ответ на: комментарий от vbr

Я не знаю ничего о Go, но любопытно и позволю себе задать вопрос.

panic означает, что случилось что-то плохое

Каково определение «чего-то плохого»?

лучше завершить работу

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

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

Не пустое. Например метод fs.fileExists(name: str) не должен выдавать исключение, потому что отсутствие файла, это вполне ожидаемый результат.

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

Твое определение?

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

panic означает, что случилось что-то плохое

Каково определение «чего-то плохого»?

Когда известно, что программа может работать непредсказуемо. Если взять, в примеру, C, то при получении SIGSEGV продолжать работу дальше - напрашиваться на потенциальные проблемы. Для других ЯП классические примеры это нарушение assert-а, выход за пределы массива, разыменование NULL и тд. Это признаки бага в коде, а код с багом по определению работает не так, как задумывал его создатель, поэтому лучше не пытаться продолжать работу.

В го паники обычно возникают при ошибках этого рода.

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

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

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

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

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

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

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

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

Не пустое. Например метод fs.fileExists(name: str) не должен выдавать исключение

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

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

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

выход за пределы массива, разыменование NULL

А в go это «паники»? Просто эти две ситуации в других managed языках, как мы знаем, не потребовали внедрения второго механизма обработки ошибок.

чаще всего это ошибочно и её обрабатаывать не нужно

Хорошо, есть ситуации в программе для которых обработчика нет потому что: useful обработчик написать невозможно; возможно, но не разумно; это баг поэтому нет обработчика; это не баг, но неготовность к чему-то; планировали, но пока не сделали. Но в других языках эти кейсы прекрасно закрылись исключениями. И во всех этих кейсах корректным итоговым вариантом может оказаться как дать возможность программе упасть, так и добавить где-то catch и позволить работать дальше.

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

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

выход за пределы массива, разыменование NULL

А в go это «паники»?

Да, в go это паники.

Просто эти две ситуации в других managed языках, как мы знаем, не потребовали внедрения второго механизма обработки ошибок.

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

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

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

Сорри, мысль не понял. Повторю свою мысль - обрабатывать подобные ошибки ни в Go в виде паник, ни в C в виде обработчика на SIGSEGV, ни в Rust в виде паник, ни в Java в виде ловли Error-ов не нужно. Можно, если очень нужно, но, вообще говоря, не нужно. В этом плане никакой разницы нет.

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

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

выход за пределы массива, разыменование NULL

Да, в go это паники.

отдельная иерархия исключений от класса Error, в которой как раз все подобные ошибки. И как раз обрабатывать эти исключения является плохим тоном обрабатывать подобные ошибки ни в Go в виде паник, […] ни в Java в виде ловли Error-ов не нужно

Всё же в Java ArrayIndexOutOfBoundsException и NullPointerException - это RuntimeException, не Error. Если речь о выходе за границы и npe, то почему не нужно обрабатывать? Ведь это вполне себе нормальный сценарий, когда где-то выше в программе есть catch, срабатывающий в том числе для экземпляров ArrayIndexOutOfBoundsException и NullPointerException.

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

мысль не понял

Насколько я понимаю, в go два механизма: одни ситуации предстают в виде error, другие в виде panic.

То есть ситуация отнесена либо к первым, либо ко вторым.

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

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

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

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

Вспомнилось про неудачную идею с checked exceptions. Там тоже посчитали, что нужно отнести ситуацию к одной из двух групп и далее поставить подход к обработке в зависимость от этого. Чтобы такое отнесение произвести надо на что-то опираться. Выбор производится автором callee, для программы, о которой callee неизвестно и относительно работы находящейся в будущем. Значит в ситуации (в ошибке) самой по себе (ее природе) должно быть что-то, что всегда позволяет ситуацию отнести к нужной группе. Но похоже, что природа ошибки не содержит ничего чтобы такой выбор сделать. Поэтому checked exceptions оказались бестолковым и вредным элементом языка. Кроме того, у checked exceptions нет еще и обоснования тому, почему для некоторого типа исключений вообще нужен другой подход к обработке. По крайней мере обоснование, которое я видел у Блоха, на мой взгляд лишено смысла.

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

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

Просто функция не возвращает ошибку. Ввиду её ненужности. Это намного круче чем писать код обработки ошибки, вида if !я_использовал_этот_метод_правильно {}.

thegoldone
()

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

Так что идеологически правильно посмотреть каким слоям принадлежат foo и bar

oxo
()
Последнее исправление: oxo (всего исправлений: 1)