V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
CC11001100
V2EX  ›  Go 编程语言

Golang 邪修: Try-Catch 的 Go 实现

  •  1
     
  •   CC11001100 ·
    CC11001100 · 2023-03-07 00:59:40 +08:00 · 2442 次点击
    这是一个创建于 624 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看 v 友们对 golang 错误处理比较感兴(si)趣(bi)( https://v2ex.com/t/921483 ),看热闹不嫌事大,更新 Golang 邪修系列,之前基于状态机实现的 try-catch-finally 。。。

    项目地址: https://github.com/golang-infrastructure/go-try-catch

    Try-Catch 的 Go 实现

    一、这是什么?为什么会有这个?

    在其他语言中有 try-catch 的实现,但是在 Go 里面并没有提供 try-catch 的实现,更蛋疼的是 Go 里面很多操作又很容易 panic ,对 panic 的捕获很麻烦,粒度不好区分,因此就想着能不能引入一个库解决一部分这种问题。

    当然这种方式并不是一个好的最佳实践,只是我们需要让业务能够正常跑起来更健壮以免隔三差五背锅,仅此而已!

    最后,切记:错误应该被尽早的暴露出来,而不是一味的掩盖!

    二、安装

    go get -u github.com/golang-infrastructure/go-try-catch
    

    三、Try-Catch 方法

    提供了两种 helper 方法,一种是比较轻量级的方法,比如当有一段代码需要执行,但是不确定会不会产生错误,就可以这个样子:

    package main
    
    import (
    	"errors"
    	try_catch "github.com/golang-infrastructure/go-try-catch"
    	"github.com/stretchr/testify/assert"
    	"testing"
    )
    
    func TestTryCatch(t *testing.T) {
    
    	var errFoo = errors.New("foo")
    
    	// 正常执行
    	err := try_catch.TryCatch(func() {
    		t.Log("ok")
    	})
    	assert.Nil(t, err)
    
    	// 执行时发生 panic
    	err = try_catch.TryCatch(func() {
    		panic(errFoo)
    	})
    	assert.NotNil(t, err)
    	assert.ErrorIs(t, err, errFoo)
    }
    

    如果需要返回值的话:

    func TestTryCatchReturn(t *testing.T) {
    
    	var errFoo = errors.New("foo")
    
    	// 正常执行
    	v, err := try_catch.TryCatchReturn(func() int {
    		return 10086
    	})
    	assert.Nil(t, err)
    	assert.Equal(t, 10086, v)
    
    	// 执行时发生 panic
    	v, err = try_catch.TryCatchReturn(func() int {
    		panic(errFoo)
    	})
    	assert.NotNil(t, err)
    	assert.ErrorIs(t, err, errFoo)
    }
    

    如果需要更多返回值:

    func TryCatchReturn2[R1, R2 any](f func() (R1, R2)) (r1 R1, r2 R2, err error) 
    func TryCatchReturn3[R1, R2, R3 any](f func() (R1, R2, R3)) (r1 R1, r2 R2, r3 R3, err error) 
    

    四、Try-Catch 方法链

    try-catch 方法链就是定义了一些节点表示异常处理流程中的不同阶段,然后每个节点绑定对应的动作向下一阶段转移,整个状态图大概是这个样子的,其中节点是 Struct ,边是方法:

    graphviz

    Example:

    package example
    
    import (
    	"errors"
    	"fmt"
    	try_catch "github.com/golang-infrastructure/go-try-catch"
    	"testing"
    )
    
    func Test(t *testing.T) {
    
    	// 正常执行
    	try_catch.Try(func() {
    		fmt.Println("ok")
    	}).Do()
    
    	// try 发生异常,走 catch
    	var errFoo = errors.New("")
    	try_catch.Try(func() {
    		panic(errFoo)
    	}).Catch(errors.New("bar"), func(err error) {
    		fmt.Println("bar")
    	}).Catch(errFoo, func(err error) {
    		fmt.Println("foo")
    	}).Do()
    
    	// try 发生异常,走默认 catch
    	try_catch.Try(func() {
    		panic(errors.New("test"))
    	}).Catch(errors.New("bar"), func(err error) {
    		fmt.Println("bar")
    	}).Catch(errFoo, func(err error) {
    		fmt.Println("foo")
    	}).DefaultCatch(func(err error) {
    		fmt.Println("other")
    	}).Do()
    
    	// try 未发生异常走 else
    	try_catch.Try(func() {
    		_ = 100 + 19
    	}).DefaultCatch(func(err error) {
    		fmt.Println("other")
    	}).Else(func() {
    		fmt.Println("else")
    	}).Do()
    
    	// try 发生异常,并且走 finally
    	try_catch.Try(func() {
    		panic(errors.New("test"))
    	}).DefaultCatch(func(err error) {
    		fmt.Println("other")
    	}).Else(func() {
    		fmt.Println("else")
    	}).Finally(func() {
    		fmt.Println("finally")
    	}).Do()
    
    	// try 未发生异常,并且走 finally
    	try_catch.Try(func() {
    		_ = 100 + 19
    	}).DefaultCatch(func(err error) {
    		fmt.Println("other")
    	}).Finally(func() {
    		fmt.Println("finally")
    	}).Do()
    
    	// 发生 panic ,尝试捕获错误,但是没有捕获得到,则异常会被向上抛出,即仍然会 panic
    	try_catch.Try(func() {
    		panic(errors.New("test"))
    	}).Catch(errFoo, func(err error) {
    		fmt.Println("catch success")
    	}).Finally(func() {
    		fmt.Println("not catch finally")
    	}).Do()
    
    }
    
    9 条回复    2023-04-15 00:11:25 +08:00
    shynome
        1
    shynome  
       2023-03-07 09:07:44 +08:00 via Android
    已经用了蛮久了,很快就要 1.0 了
    https://github.com/lainio/err2
    bv
        2
    bv  
       2023-03-07 09:20:31 +08:00
    https://github.com/golang-infrastructure/go-try-catch/blob/3e523598eac40c320f42ffc50d2356ce41d1a5b1/try_catch_func.go#L9

    这些 recovery 里面都把 r 断言成了 r.(error),框架不能保证使用者 func 发生 panic 的时候传入的一定是 error 类型。这样会造成 defer recovery 里面继续 panic
    fregie
        3
    fregie  
       2023-03-07 09:28:32 +08:00 via Android
    golang 工程特性优点 -1
    FrankAdler
        4
    FrankAdler  
       2023-03-07 09:35:25 +08:00 via iPhone   ❤️ 1
    跟自己写 recover 没区别,不建议使用+1
    FreeWong
        5
    FreeWong  
       2023-03-07 09:45:37 +08:00 via Android
    一直觉得 判断 error 非常好
    me262
        6
    me262  
       2023-03-07 10:17:21 +08:00
    不上生产非好汉
    raysonlu
        7
    raysonlu  
       2023-03-07 11:58:09 +08:00
    @FreeWong 如何把控每个地方可能出现的 error 类型?
    bthulu
        8
    bthulu  
       2023-03-07 13:24:20 +08:00
    你这样太麻烦了, 不如下面这样的, 只要在需要 try-catch 的代码上下加 try()和 catch()就行了,别的什么都不用干。
    try()
    doSomething()
    catch(err)
    CC11001100
        9
    CC11001100  
    OP
       2023-04-15 00:11:25 +08:00
    @bv 大佬说的是,刚看了下确实会有这个问题,感谢指正
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3177 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 13:18 · PVG 21:18 · LAX 05:18 · JFK 08:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.