Go. 处理来自多个抽象层次的错误的最佳实践[英] Go. Best practice to handle error from multiple abstract level

本文是小编为大家收集整理的关于Go. 处理来自多个抽象层次的错误的最佳实践的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我想知道在 go 中处理多级抽象错误的最佳方法是什么.每次如果我必须为程序添加一个新的抽象级别,我就不得不将错误代码从低级别传输到高级别.因此日志文件中有重复的通信,否则我必须记住删除通信表级别低并将他转移到更高级别.下面简单举例.我跳过了创建每个对象来更短和更清晰的代码,但我认为你理解我的问题

type ObjectOne struct{
    someValue int
}

func (o* ObjectOne)CheckValue()error{
    if o.someValue == 0 {
        SomeLogger.Printf("Value is 0 error program") // communicate form first level abstraction to logger
        return errors.New("Internal value in object is 0")
    }
    return nil
}

type ObjectTwoHigherLevel struct{
    objectOne ObjectOne
}

func (oT*  ObjectTwoHigherLevel)CheckObjectOneIsReady() error{
    if err := oT.objectOne.CheckValue() ; err != nil{
        SomeLogger.Printf("Value in objectOne is not correct for objectTwo %s" , err) //  second communicate
        return  err
    }
    return nil
}

type ObjectThreeHiggerLevel struct{
    oT ObjectTwoHigherLevel
}

func (oTh* ObjectThreeHiggerLevel)CheckObjectTwoIsReady()error{
    if err := oTh.oT.CheckObjectOneIsReady() ; err != nil{
        SomeLogger.Printf("Value in objectTwo is not correct for objectThree %s" , err)
    return err
    }
    return nil
}

在日志文件中我得到重复的帖子

Value is 0 error program 
Value in objectOne is not correct for objectTwo Internal value in object is 0 
Value in objectTwo is not correct for objectThree Internal value in object is 0

反过来,如果我只将一些 err 转移到更高级别而没有额外的日志,我会丢失每个级别发生的信息.

这是如何解决的?隐私副本如何通信?还是我的路是好的也是唯一的?

如果我创建了一些对象,这些对象在几个抽象级别上搜索数据库中的某些内容,那么问题会更加令人沮丧,然后我在 logFile 中也有几行形成相同的任务.

推荐答案

您应该处理错误,或者不处理错误,而是将其委托给更高级别(给调用者).处理错误并返回它是不好的做法,就好像调用者也这样做一样,错误可能会被处理多次.

处理错误意味着检查它并根据它做出决定,这可能是您简单地记录它,但这也算作"处理"它.

如果您选择不处理而是将其委托给更高级别,那可能很好,但不要只返回您得到的错误值,因为它可能对没有上下文的调用者毫无意义.

一种非常好的和推荐的委派方式是注释错误.这意味着您创建并返回一个 new 错误值,但旧的也包含在返回的值中 - 它提供了上下文.

有一个用于注释错误的公共库:github.com/pkg/errors;及其godoc:errors

它基本上有 2 个功能:1 用于 包装 现有错误:

func Wrap(cause error, message string) error

还有一个用于提取包装错误:

func Cause(err error) error

使用这些,您的错误处理可能如下所示:

func (o *ObjectOne) CheckValue() error {
    if o.someValue == 0 {
        return errors.New("Object1 illegal state: value is 0")
    }
    return nil
}

还有第二层:

func (oT *ObjectTwoHigherLevel) CheckObjectOneIsReady() error {
    if err := oT.objectOne.CheckValue(); err != nil {
        return errors.Wrap(err, "Object2 illegal state: Object1 is invalid")
    }
    return nil
}

第三级:只调用第二级检查:

func (oTh *ObjectThreeHiggerLevel) CheckObjectTwoIsReady() error {
    if err := oTh.ObjectTwoHigherLevel.CheckObjectOneIsReady(); err != nil {
        return errors.Wrap(err, "Object3 illegal state: Object2 is invalid")
    }
    return nil
}

请注意,由于 CheckXX() 方法不处理错误,它们不会记录任何内容.他们正在委派带注释的错误.

如果有人使用 ObjectThreeHiggerLevel 决定处理错误:

o3 := &ObjectThreeHiggerLevel{}
if err := o3.CheckObjectTwoIsReady(); err != nil {
    fmt.Println(err)
}

将呈现以下漂亮的输出:

Object3 illegal state: Object2 is invalid: Object2 illegal state: Object1 is invalid: Object1 illegal state: value is 0

没有多个日志的污染,所有的细节和上下文都被保留了,因为我们使用了 errors.Wrap() ,它产生了一个错误值,该错误值格式化为一个 string ,它递归地保留了包装的错误:错误堆栈.

您可以在博文中阅读有关此技术的更多信息:

戴夫·切尼:不要只需检查错误,优雅地处理它们

本文地址:https://www.itbaoku.cn/post/627701.html

问题描述

I wondering what is the best way to handle error form multiple level abstraction in go. Every time if I must add a new level abstraction to program, I am forced to transfer error code from level less to level high. Thereby is duplicate communitaces in log file or I must remmember to delete communicate form level low and transfer him to level higher. Below simply example. I skipped creating each object to more shortly and celar code, but I think You understand my problem

type ObjectOne struct{
    someValue int
}

func (o* ObjectOne)CheckValue()error{
    if o.someValue == 0 {
        SomeLogger.Printf("Value is 0 error program") // communicate form first level abstraction to logger
        return errors.New("Internal value in object is 0")
    }
    return nil
}

type ObjectTwoHigherLevel struct{
    objectOne ObjectOne
}

func (oT*  ObjectTwoHigherLevel)CheckObjectOneIsReady() error{
    if err := oT.objectOne.CheckValue() ; err != nil{
        SomeLogger.Printf("Value in objectOne is not correct for objectTwo %s" , err) //  second communicate
        return  err
    }
    return nil
}

type ObjectThreeHiggerLevel struct{
    oT ObjectTwoHigherLevel
}

func (oTh* ObjectThreeHiggerLevel)CheckObjectTwoIsReady()error{
    if err := oTh.oT.CheckObjectOneIsReady() ; err != nil{
        SomeLogger.Printf("Value in objectTwo is not correct for objectThree %s" , err)
    return err
    }
    return nil
}

In result in log file I get duplicate posts

Value is 0 error program 
Value in objectOne is not correct for objectTwo Internal value in object is 0 
Value in objectTwo is not correct for objectThree Internal value in object is 0

In turn if I only transfer some err to higher level without additional log I lost information what happend in each level.

How this solve ? How privent duplicate communicates ? Or My way is the good and the only ?

Problem is more frustrating if I create a few object which search something in database on a few abstraction level then I get also few lines form this same task in logFile.

推荐答案

You should either handle an error, or not handle it but delegate it to a higher level (to the caller). Handling the error and returning it is bad practice as if the caller also does the same, the error might get handled several times.

Handling an error means inspecting it and making a decision based on that, which may be you simply log it, but that also counts as "handling" it.

If you choose to not handle but delegate it to a higher level, that may be perfectly fine, but don't just return the error value you got, as it may be meaningless to the caller without context.

A really nice and recommended way of delegation is Annotating errors. This means you create and return a new error value, but the old one is also wrapped in the returned value - which provides the context.

There is a public library for annotating errors: github.com/pkg/errors; and its godoc: errors

It basically has 2 functions: 1 for wrapping an existing error:

func Wrap(cause error, message string) error

And one for extracting a wrapped error:

func Cause(err error) error

Using these, this is how your error handling may look like:

func (o *ObjectOne) CheckValue() error {
    if o.someValue == 0 {
        return errors.New("Object1 illegal state: value is 0")
    }
    return nil
}

And the second level:

func (oT *ObjectTwoHigherLevel) CheckObjectOneIsReady() error {
    if err := oT.objectOne.CheckValue(); err != nil {
        return errors.Wrap(err, "Object2 illegal state: Object1 is invalid")
    }
    return nil
}

And the third level: call only the 2nd level check:

func (oTh *ObjectThreeHiggerLevel) CheckObjectTwoIsReady() error {
    if err := oTh.ObjectTwoHigherLevel.CheckObjectOneIsReady(); err != nil {
        return errors.Wrap(err, "Object3 illegal state: Object2 is invalid")
    }
    return nil
}

Note that since the CheckXX() methods do not handle the errors, they don't log anything. They are delegating annotated errors.

If someone using ObjectThreeHiggerLevel decides to handle the error:

o3 := &ObjectThreeHiggerLevel{}
if err := o3.CheckObjectTwoIsReady(); err != nil {
    fmt.Println(err)
}

The following nice output will be presented:

Object3 illegal state: Object2 is invalid: Object2 illegal state: Object1 is invalid: Object1 illegal state: value is 0

There is no pollution of multiple logs, and all the details and context are preserved because we used errors.Wrap() which produces an error value which formats to a string which preserves the wrapped errors, recursively: the error stack.

You can read more about this technique in blog post:

Dave Cheney: Don’t just check errors, handle them gracefully