在Golang捕捉恐慌

20jt8wwn  于 2023-09-28  发布在  Go
关注(0)|答案(8)|浏览(123)

对于下面的代码,如果没有给出文件参数,那么第9行panic: runtime error: index out of range将如预期的那样抛出异常。
当直接向它传递导致死机的东西(os.Args[1])时,我如何“捕获”这个死机并处理它?就像PHP中的try/catch或Python中的try/except。
我在StackOverflow上进行了搜索,但我没有找到任何答案。

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open(os.Args[1])
    if err != nil {
        fmt.Println("Could not open file")
    }
    fmt.Printf("%s", file)
}
n7taea2i

n7taea2i1#

恐慌程序可以通过内置的recover()函数恢复:
recover函数允许程序管理恐慌goroutine的行为。假设一个函数G推迟了一个调用recover的函数D,并且在执行G的同一个goroutine上的一个函数中发生了panic。当延迟函数的运行达到D时,Drecover的调用的返回值将是传递给panic的调用的值。如果D正常返回,而没有启动新的panic,则恐慌序列停止。在这种情况下,Gpanic之间调用的函数的状态将被丢弃,并恢复正常执行。GD之前延迟的任何函数都将运行,G的执行将返回到其调用方而终止。
当goroutine没有panicking或者recover没有被延迟函数直接调用时,recover的返回值是nil。相反,如果一个goroutine出现了panicing,并且recover被一个deferred函数直接调用,那么recover的返回值肯定不会是nil。为了确保这一点,使用nil接口值(或非类型化的nil)调用panic会导致运行时死机。
下面是一个如何使用它的示例:

// access buf[i] and return an error if that fails.
func PanicExample(buf []int, i int) (x int, err error) {
    defer func() {
        // recover from panic if one occurred. Set err to nil otherwise.
        if (recover() != nil) {
            err = errors.New("array index out of bounds")
        }
    }()

    x = buf[i]
}

请注意,恐慌往往不是正确的解决方案。Go范式是显式地检查错误。一个程序只有在正常程序执行期间不发生死机的情况下才应该死机。例如,无法打开文件是可能发生的事情,不应该导致恐慌,而内存不足值得恐慌。然而,这种机制的存在甚至能够捕获这些情况,并可能优雅地关闭。

yqhsw0fo

yqhsw0fo2#

Go不是Python,在使用它之前,你应该正确地检查args:

func main() {
    if len(os.Args) != 2 {
         fmt.Printf("usage: %s [filename]\n", os.Args[0])
         os.Exit(1)
    }
    file, err := os.Open(os.Args[1])
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", file)
}
wn9m85ua

wn9m85ua3#

一些Golang官方软件包使用 panic/defer+recover 作为 throw/catch,但仅当它们需要展开大型调用堆栈时。在 *Golang的json包中 * 使用 panic/defer+recover 作为 throw/catch 是最优雅的解决方案。
http://blog.golang.org/defer-panic-and-recover
有关panic和recover的真实示例,请参阅Go标准库中的 *json包 。它使用一组递归函数对JSON编码的数据进行解码。当遇到格式错误的JSON时, 解析器调用panic将堆栈展开到顶级函数调用,该函数从panic中恢复 * 并返回适当的错误值(请参阅decode.go中decodeState类型的'error'和'unmarshal'方法)。
http://golang.org/src/encoding/json/decode.go上搜索d.error(
在您的示例中,“惯用”的解决方案是在使用参数之前检查参数,正如其他解决方案所指出的那样。
但是,如果你想/需要抓住任何你可以做的事情:

package main

import (
    "fmt"
    "os"
)

func main() {

    defer func() { //catch or finally
        if err := recover(); err != nil { //catch
            fmt.Fprintf(os.Stderr, "Exception: %v\n", err)
            os.Exit(1)
        }
    }()

    file, err := os.Open(os.Args[1])
    if err != nil {
        fmt.Println("Could not open file")
    }

    fmt.Printf("%s", file)
}
62o28rlo

62o28rlo4#

第一:你不会想这么做的。Try-catch样式的错误处理不是错误处理。在Go语言中,你会先检查len(os.Args),然后只访问元素1(如果存在)。
对于罕见的情况,你需要捕捉恐慌(你的情况是不是其中之一!)结合使用deferrecover。请访问http://golang.org/doc/effective_go.html#recover

bweufnob

bweufnob5#

我们可以使用recover来管理恐慌,而无需停止进程。通过在任何使用defer的函数中调用recover,它都会将执行返回给调用函数。Recover返回两个值,一个是布尔值,另一个是要恢复的接口。使用类型Assert,我们可以得到底层错误值。您还可以使用recover打印底层错误。

defer func() {
    if r := recover(); r != nil {
        var ok bool
        err, ok = r.(error)
        if !ok {
            err = fmt.Errorf("pkg: %v", r)
        }
    }
}()
mkh04yzy

mkh04yzy6#

我不得不在一个测试案例中捕捉恐慌。我被重定向到这里。
func.go

var errUnexpectedClose = errors.New("Unexpected Close")
func closeTransaction(a bool) {
    if a == true {
        panic(errUnexpectedClose)
    }
}

func_test.go

func TestExpectedPanic() {
    got := panicValue(func() { closeTransaction(true) })
    a, ok := got.(error)
    if a != errUnexpectedClose || !ok {
        t.Error("Expected ", errUnexpectedClose.Error())
    }
}

func panicValue(fn func()) (recovered interface{}) {
    defer func() {
        recovered = recover()
    }()
    fn()
    return
}

使用自https://github.com/golang/go/commit/e4f1d9cf2e948eb0f0bb91d7c253ab61dfff3a59(参考自VonC)

eufgjt7s

eufgjt7s7#

请注意,在issue 14965之后,go 1.7可能会更改对panic Execution 错误(例如尝试索引数组越界触发器)的恢复处理
参见CL 21214及其试验:

运行时:make execution error panic values实现Error接口

使执行panics实现运行时panics(specs)所要求的Error,而不是带有字符串的panics。
当您恢复一个panic错误时,您可以执行以下操作:

if _, ok := recovered.(runtime.Error); !ok {

这仍在评估中,并作为Dave Cheney。提到:
我不知道人们目前在做什么,但从我的经验来看,这已经被打破了很长一段时间,没有人抱怨,所以他们要么明确地依赖于破碎的行为,或者没有人在乎。无论哪种方式,我认为这是一个好主意,以避免作出这种改变。

sf6xfgos

sf6xfgos8#

虽然Go语言中的panic不是惯用的,不应该在其他编程语言中用作异常,但有时确实需要“捕获”它们。
在这些情况下,我发现定义一个CatchPanic实用程序将panic转换为可检查的error以最大限度地减少与Go语言习惯用法的差异是很有用的。
举例来说:

func CatchPanic[T interface{}](f func() T) (ret T. err error) {
    defer func() {
        rec = recover()
        if rec != nil {
            err = errors.New(fmt.Sprint(rec))
        }
    }()
    return f(), nil
}

用法示例(过于简化..):

func main() {
    arr := []int{0, 1, 2, 3, 4}
    val, err := CatchPanic(func() int {
        return arr[5] // Will panic
    })
    if err != nil {
        // Handle error
    }
}

lambda参数的返回值可以根据其他用例进行调整。
请注意,在可能的情况下,建议不要在Go中依赖恐慌捕获,并且在访问之前进行检查是更可取的。

相关问题