区分恢复恐慌和没有错误发生的情况。

huangapple go评论86阅读模式
英文:

Distinguish between panic with recovery and no error occuring

问题

我有以下代码:

package main

import (
	"fmt"
)

func recoverFoo() {
	if r := recover(); r != nil {
		println("Recovered")
	}
}
func foo() (int, error) {
	defer recoverFoo()
	panic("shit!")
}
func main() {
	x, err := foo()
	println("after foo x = " + fmt.Sprint(x))

	if err != nil {
		println("An error occured")
	} else {
		println("No error occured")
	}
}

在这种情况下,我调用了foo函数(实际上我的foo函数调用了一个第三方库,该库有时会发生panic,但有时也会返回错误)。如果发生panic,我不能让它崩溃,但我需要知道出了问题,因为我必须在错误时写入本地存储。

但在这种情况下,从foo函数返回的值x可能是一个有效的0值。因此,将x和err恢复为它们的默认值(0和nil)并不能告诉我是否实际上发生了错误...

我看到两种可能的解决方案,(1)将err和x封装到自定义返回类型中,并假设如果它是nil,则表示发生了错误。 (2)我有一个第三个返回布尔值,指定没有发生panic(它将默认为false)。

在处理Go语言的错误和恢复panic方面,我是否遗漏了什么?我是Go语言的新手,希望能得到一些建议。

英文:

I have the following code:

package main

import (
	"fmt"
)

func recoverFoo() {
	if r := recover(); r != nil {
		println("Recovered")
	}
}
func foo() (int, error) {
	defer recoverFoo()
	panic("shit!")
}
func main() {
	x, err := foo()
	println("after foo x = " + fmt.Sprint(x))

	if err != nil {
		println("An error occured")
	} else {
		println("No error occured")
	}
}

In this situtation, I am calling foo (in reality my function foo is calling a third party library which sometimes panics, but also sometimes return err). If it panics I can't have it crashing the app, but I need to know something went wrong as I have to write to local storage on error.

In this case though the value x returned from Foo can have a valid value of 0. So the recovery setting x and err to their defaults (0 and nil), doesn't tell me if an error actually occurred...

I see two possible solutions, (1) I wrap the err and x into a custom return type and assume if its nil then an error occurred. (2) I have a third return boolean that specifies a panic didn't occur (it will default to false)

Is their something I'm missing here around go error handling and recovering from panics. I'm new to go so would like some advice.

答案1

得分: 4

由于恐慌和“软”错误都是程序异常,您应该保留非nil错误的语义。您可以将错误包装在自定义类型或简单的错误变量中,并在函数调用后检查该错误

此外,为了实际修改返回的错误,您还应该:

  • 在延迟函数文字中使用recover()
  • 使用命名返回参数

根据规范延迟语句

> 例如,如果延迟函数是函数文字,并且周围的函数具有在文字中范围内的命名结果参数,则延迟函数可以在返回之前访问和修改结果参数。

package main

import (
	"errors"
	"fmt"
	"log"
)

var ErrPanicRecovered = errors.New("recovered from panic")

// 命名返回参数
func recoverableFoo() (i int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%w: %v", ErrPanicRecovered, r)
		}
	}()
	// panic("problem!") // 或任何可能引发恐慌的调用;取消注释以进行测试
	return 1, nil
}

func main() {
	x, err := foo()
	if err != nil {
		if errors.Is(err, ErrPanicRecovered) {
			log.Fatal("panicked: ", err)
		}
		log.Printf("some other error: %s", err.Error())
		return
	}

	fmt.Println("after foo x =", x)
}

特别是,使用fmt.Errorf%w格式化动词允许您正确包装错误,并稍后使用errors.Is进行检查:

> 如果格式说明符包括带有错误操作数的%w动词,则返回的错误将实现返回操作数的Unwrap方法。

Playground: https://play.golang.org/p/p-JI1B0cw3x

英文:

Since both the panic and the "soft" error are program exceptions, you should preserve the non-nil error semantics. You can wrap the error in a custom type, or a simple error variable, and check for that after the function call.

Also, in order to actually modify the returned error, you also should:

  • use recover() in a deferred function literal
  • use named return parameters

From the specs Defer statements:

> For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned

package main

import (
	"errors"
	"fmt"
	"log"
)

var ErrPanicRecovered = errors.New("recovered from panic")

// named return parameters
func recoverableFoo() (i int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%w: %v", ErrPanicRecovered, r)
		}
	}()
	// panic("problem!") // or any call that may panic; uncomment to test
	return 1, nil
}


func main() {
	x, err := foo()
	if err != nil {
		if errors.Is(err, ErrPanicRecovered) {
			log.Fatal("panicked: ", err)
		}
		log.Printf("some other error: %s", err.Error())
		return
	}

	fmt.Println("after foo x = " + fmt.Sprint(x))
}

In particular, using fmt.Errorf with the %w formatting verb allows you to properly wrap the error and later inspect it with errors.Is:

> If the format specifier includes a %w verb with an error operand, the returned error will implement an Unwrap method returning the operand.

Playground: https://play.golang.org/p/p-JI1B0cw3x

huangapple
  • 本文由 发表于 2021年7月22日 17:01:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/68482028.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定