为了账号安全,请及时绑定邮箱和手机立即绑定

如何包装错误并使其可检查?

如何包装错误并使其可检查?

Go
ITMISS 2022-07-25 10:51:42
我正在实现一个使用 TCP 的服务器。读取函数具有以下签名func ReadNext() (Message, error)该功能可能因两个原因而失败:连接丢失解析错误我的第一反应是用fmt.Errorf(). 但是,我想为该包的使用者提供根据失败原因进行检查和调度的能力,因此据我所知,使用内置包装不提供该选项。我阅读了不要只是检查错误,优雅地处理它们,发现基本上他提倡使用自定义错误类型进行包装,并在自定义接口上使用类型断言。我发现这很冗长,需要大量额外的代码。另外,我看不到与我的错误类型相关的行为会使它更清晰:type ParseError interface {  IsParseError() bool}type NetworkError interface {  IsNetworkError() bool}我基本上看到三个选项:不要包装并返回哨兵错误用自定义错误类型包装并进行类型比较Dave Cheney 风格:使用自定义错误类型进行包装,并对描述行为的接口进行断言。我发现没有特别好的或优雅的解决方案。有什么建议吗?
查看完整描述

3 回答

?
烙印99

TA贡献1829条经验 获得超13个赞

错误很复杂,因为,嗯,错误只是复杂。

Dave Cheney 的博客文章包括以下声明:

我得出的结论是,没有单一的方法可以处理错误。

我完全同意。考虑一些简单的事情,比如“从文件中读取数据”。这可能会失败。但是为什么它会失败,你能对这些失败做些什么吗?

以下是它可能失败的一些原因。这并不意味着是所有可能原因的完整列表,只是其中一些原因:

  1. 也许该文件不存在。它应该,但只是没有。

    您可能对此无能为力。

  2. 也许该文件确实存在,但您没有访问它的权限。你应该,但你只是不这样做。

    除非您具有管理员访问权限,否则您可能对此无能为力,在这种情况下,您可以授予自己权限并继续。

  3. 也许该文件存在并且您有权访问它,但数据已损坏。

    对此您可能无能为力,但如果系统是自我修复的(此类系统存在),这可能是暂时的。

  4. 也许该文件暂时无法访问,因为它位于网络上并且网络已分区。

    这类似于案例 3:等待和重试可能会解决问题(实际上它可能比案例 3 更有可能)

... [他建议] 包装自定义错误类型并在自定义接口上使用类型断言。我发现这很冗长,需要大量额外的代码。

确实如此。它还需要首先产生错误的人的合作,以便您可以做出这些细微的区分。那是因为您正在处理真正复杂的硬件和软件系统。

这不是很令人满意,但这是真的。幸运的是,对于很多软件来说,很多错误情况都可以归结为“不是我的问题”:上面的前两个示例无法打开和读取某些文件(不存在或权限被拒绝)可以放入这个组。如果您正在编写文件列表实用程序或类似的东西,这可能没问题。如果您正在编写代码来运行起搏器,则停止并显示错误消息(“致命:无法报告心率”)很可能是,呃,致命的。你需要考虑上下文。


查看完整回答
反对 回复 2022-07-25
?
largeQ

TA贡献2039条经验 获得超8个赞

如果你用 包装一个错误errfmt.Errorf你可以检查错误errors.Is

例子:

    var ExpectedError = errors.New("This is an error")
    err := fmt.Errorf("I am now wrapping %w ",ExpectedError)
    fmt.Println("Checking error", errors.Is(err, ExpectedError))

https://play.golang.org/p/wtOeopA-f5B


查看完整回答
反对 回复 2022-07-25
?
阿波罗的战车

TA贡献1862条经验 获得超6个赞

错误为不应运行的代码提供了保护,并提供了处理故障后状态的方法。


Swift 和 Rust 的学习应用在惯用的 go 风格中:


在应用程序级别,我希望将所有错误集中在一个地方,以便我知道该应用程序处理哪些错误。


所有关于错误的单元测试和应用程序行为都将基于我的应用程序定义的错误类型和错误变量。为此,我必须编写从 lib 错误到 AppErrors 的转换,并在应用程序首次检查错误的任何地方使用它们。


因此,如果任何库更改或被等效库替换,则只需更改错误的转换(适应)。应用程序错误处理策略的其余部分保持原样并经过测试。


错误类型实现接口或者我们只是使用错误类型检查/切换是次要的。


这是该方法的示例。我更喜欢使用 const 来最小化强制转换。我相信这会更快、更易读。


package main


import (

    "fmt"

    "os"

)


type ErrorType int


const (

    ReaderEndError ErrorType = iota + 1

    ResourceNotFoundError

)


type AppError struct {

    ErrType ErrorType

    s       string

    error

}


func (ae AppError) Error() string {

    return ae.s

}


func (ae *AppError) GetType() ErrorType {

    return ae.ErrType

}


func main() {


    _, err := readData("notexists")

    if err != nil {

        if ae, ok := err.(AppError); ok {

            //should use switch case in actual startegy

            if ae.ErrType == ResourceNotFoundError {

                if ae.error != nil {

                    fmt.Println("Inner wrapped details (use for logging)", ae.error)

                }


                fmt.Println("handle ResourceNotFoundError here")

            }

        }

    }

}


//sample func

func readData(file string) (*string, error) {

    _, err := os.Open(file)

    if err != nil {

        //switch case here actually

        e, _ := err.(*os.PathError)

        //actually should have many helper conversion functions to do below conversions

        return nil, AppError{ResourceNotFoundError, "Not found", e}

    }


    //assume read data

    data := string("data")

    return &data, nil

}


查看完整回答
反对 回复 2022-07-25
  • 3 回答
  • 0 关注
  • 265 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号