将外部错误映射到Golang中的域错误

时间:2018-10-03 07:15:42

标签: go error-handling abstraction

我有一个称为ComputeService的服务类型,该服务类型实现了某些域逻辑。服务本身取决于接口Computer的实现,该接口具有方法Computer.Compute(args...) (value, error)。如图所示,Compute本身可能返回某些错误。

ComputeService需要使用适当的域错误代码从一组域错误中发送适当的错误,以便可以进行翻译,并且客户端也可以适当地处理错误。

我的问题是,Computer实现应该将其失败包装在域错误中,还是应该ComputeService来做到这一点。如果ComputeService是这样做的话,那么它将必须知道Computer接口的不同实现所返回的不同错误,我认为这会破坏抽象。两种方式都显示如下:

package arithmetic
type Computer struct {
}
func (ac Computer) Compute(args ....) (value, error) {
     // errors is a domain-errors package defined in compute service project
     return errors.NewDivideByZero()
}

OR

package compute
type Service struct {
}
func (svc Service) Process(args...) error {
    computer := findComputerImplementation(args...)
    val, err := computer.Compute(args...)
    if err != nil {
       if err == arith.ErrDivideByZero {
          // converting an arithmetic computer implementation 
          // specific error to domain error
          return errors.NewDivideByZero()
       } else if err == algebra.ErrInvalidCoEfficient {
          // converting an algebraic computer implementation 
          // specific error to domain error
          return errors.NewBadInput()
       }
       // some new implementation was used and we have no idea
       // what errors it could be returning. so we have to send
       // a internal server error equivalent here
       return errors.NewInternalError()
    }

}

2 个答案:

答案 0 :(得分:1)

Computer的实现者应以域错误作为响应,因为它们是最接近操作并最能确定什么是错误的错误。就像您说的那样,在 ComputeService中加入逻辑将破坏抽象。如果您需要将代码从特定的Computer错误映射到域错误,请创建将主要逻辑与此错误包装代码区分开的包装器结构。

要保留内部错误上下文,只需将原始错误嵌入域错误中,并提供IsSpecificDomainError助手。

type MyDomainError struct {
    Err error
}

func NewMyDomainErr(err error) error {
    return &MyDomainError{err}
}

func IsMyDomainError(e error) bool {
    _, ok := err.(*MyDomainError)
    return ok
}

答案 1 :(得分:0)

  

要保留内部错误上下文,只需将原始错误嵌入域错误

这可以使用包装错误,这些错误正在issue 29934中以detailed here的形式出现在Go 1.13(2019年第四季度)中。

err.Is()

作为Russ Cox mentions

  

我想我们都同意strings.Contains(err.Error(), "not found")是易碎的代码。

     

我希望我们也同意,我们希望看到像errors.Is(err, os.ErrNotExist)这样的代码。

     

但是关键是,在很多情况下,对于包的未来发展至关重要的是,即使今天这是当今的根本原因,也要防止调用者依赖于满足errors.Is(err, os.ErrNotExist)的特定错误结果的结果。
  就像查看未导出的字段或比较错误文本一样-这可能会更改。

     

尽管strings.Contains看起来很脆弱,但errors.Is看起来也不脆弱。
  如果我们要避免它易碎,那么我们需要为软件包提供一种报告详细信息的方法,而不必让客户对其进行测试。这样的错误是无法解决的。

err.As()

var pe *os.PathError
if errors.As(err, &pe) {
     use(pe)
}

%w

func inner() error { return errors.New("inner error") }
func outer() error { return fmt.Errorf("outer error: %w", inner()) }

fmt.Fprintf("%+v", outer())
// outer error:
//     /path/to/file.go:123
//   - inner error:
//     /path/to/file.go:122

current status for Go 1.13

  

只需说明一下我所认为的团队提供的折衷解决方案:

     
      
  • fmt.Errorf当前广泛用于包装错误并返回新的(不透明的)错误(因为您无法访问基础错误)。
      现在,可以使用“ %w”显式选择加入以返回可以解包的错误。
  •   
  • errors被设计为没有依赖关系的基本程序包,因此每个程序包都可以依赖它。
  •   
  • 团队同意就存在广泛分歧的领域进行讨论,并希望发布足够多的内容(错误,错误,错误,扩展大多数人包装错误的方式),以便人们可以实现目标。
  •   
  • 泛型还没有出现,我们也不知道何时会出现:关于这一点的激烈讨论将使这个关于“错误2值”的讨论看起来像孩子们的游戏。
      errors.Iserrors.As干净简洁,足以长时间保持舒适。
  •   
     

大多数有争议的事情都被推到了1.14。

     
      
  • Wrapf不能出错,因为它是基本软件包。
  •   
  • Wrapf意味着团队必须决定在传递nil错误时会发生什么:在其上平底。
  •   
  • 包装可能与正在考虑本地化,国际化等的想法相冲突。
  •   
  • ErrorFormatterErrorPrinter尚未得到更深入的使用,并存在疣。平底锅。
  •