defer, recover и panic отличие от try-catch
В чём отличие от try-catch в большинстве языков конструкции defer-recover-panic?
Как panic сказывается на производительности? Подумываю о том, чтобы вместо постоянного проброса наверх ошибок, в отдельных местах использовать связку defer+recover+panic.
Какие есть ещё нюансы, помимо производительности7?
Ответы (1 шт):
Отличие panic/recover от try/catch в других языках в том, что оно полностью прерывает выполнение текущей функции.
Для большей наглядности:
function doSomething() {
throw "panic";
}
(() => {
try {
doSomething();
} catch(e) {
console.log(e);
}
console.log("after try/catch")
})()
package main
import (
"fmt"
"runtime/debug"
)
func doSomething() {
panic("panic")
}
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("%s: %s", err, debug.Stack())
}
}()
doSomething()
fmt.Print("after panic")
}
В первом примере текст after try/catch будет выведен, во втором случае after panic не выведется, т.к. функция main закончит свое выполнение.
Так же recover может быть только один, грубо говоря, как-будто всё тело функции обернуто в try/catch, что делает его менее гибким. Читал, что это сделанно намеренно для избежания злоупотреблений.
На счет производительности вопрос довольно сложный, определенно panic/recover медленней, но я затрудняюсь назвать разницу, ибо довольно сложно написать бенчмарк с одинаковым применением.
Я ни в коем случае не претендую на корректность бенчмарка, надеюсь на правки со стороны коллег в комментариях.
Запускал на ноутбучном i5, go version go1.15 linux/amd64
BenchmarkPanicDefer-4 3006555 353 ns/op
BenchmarkErrorCatch-4 1000000000 0.502 ns/op
Моя попытка написать бенчмарк:
package main
import (
"errors"
"testing"
)
func getPanic() {
panic("")
}
func PanicDefer() {
defer func() {
if err := recover(); err != nil {
// do something
}
}()
getPanic()
}
func getError() error {
return errors.New("")
}
func CatchError() {
if err := getError(); err != nil {
// do something
}
}
func BenchmarkPanicDefer(b *testing.B) {
for i := 0; i < b.N; i++ {
PanicDefer()
}
}
func BenchmarkErrorCatch(b *testing.B) {
for i := 0; i < b.N; i++ {
CatchError()
}
}
Понятное дело, что условия не совсем равные, я попытался сделать их максимально близкими к реальному приложению.
Как правило между "ошибка" и "исключение" границы довольно размыты, сильно зависит от контекста. В других языках try/catch это универсальное решение для обоих случаев.
На мой взгляд, если использование panic/recover в каком-то конкретном месте делает код чище, то ничего не мешает так писать.