Go Error 最佳实践(上)

简介: Go 语言由于没有 try...catch 结构屡屡被诟病,Go 中的每一个错误都需要处理,而且错误经常是蹭蹭嵌套的。

image.png

Go 语言由于没有 try...catch 结构屡屡被诟病,Go 中的每一个错误都需要处理,而且错误经常是蹭蹭嵌套的。如下面的结构:

a, err := fn()
if err != nil {
  return err
}
func fn() error {
  b, err := fn1()
  if  err != nil {
    return err
  }
  if _, err = fn2(); err != nil {
  }
}

Go Error 也是接口

在 Go 语言中,Go Error 也是一个接口:

type error interface {
  Error() string
}

所以,实现、创建或抛出错误,实际上就是实现这个接口。最常见的三种方式是:

  • errors.New
  • fmt.Errof
  • implement error interface
var ErrRecordNotExist   = errors.New("record not exist")
func ErrFileNotExist(filename string) error {
  return fmt.Errorf("%s file not exist", filename)
}
type ErrorCallFailed struct {
  Funcname string
}
func (*ErrorCallFailed) Error() string {
  return fmt.Sprintf(“call %s failed”, funcname)
}
var ErrGetFailed error = &ErrorCallFailed{ Funcname: "getName", }

Go 错误只涉及以下两个逻辑:

  • 抛出错误,这涉及到我们如何定义错误。在实现功能时,异常情况需要返回合理的错误
  • 处理错误。调用函数时,要根据函数的返回实现不同的逻辑,考虑是否有错误,错误是否属于某种类型,是否忽略错误等。
func (d *YAMLToJSONDecoder) Decode(into interface{}) error {
  bytes, err := d.reader.Read()
  if err != nil && err != io.EOF {
    return err
  }
  if len(bytes) != 0 {
    err := yaml.Unmarshal(bytes, into)
    if err != nil {
      return YAMLSyntaxError{err}
    }
  }
  return err
}
type YAMLSyntaxError struct {
  err error
}
func (e YAMLSyntaxError) Error() string {
  return e.err.Error()
}

Kubernetes decode.go 中的这篇文章不仅可以直接返回错误,还可以包装错误,要么返回 YAMLSyntaxError,要么直接忽略 io.EOF

通常,有三种方法可以确定错误类型:

  • 直接通过 == , 例如:if err == ErrRecordNotExist {}
  • 类型推断,if _, ok := err.(*ErrRecordNotExist); ok {}
  • errors.Iserrors.As 方法. 从 Go 1.13 开始添加。 if errors.Is(err, ErrRecordNotExist) 涉及错误换行,解决了定位嵌套错误的麻烦。


遵循的规则

理解了 Go 错误的基本概念之后,是时候讨论可以遵循的规则以进行更好的实践了。让我们从定义开始,然后到错误处理。

定义错误

  • 使用 fmt.Errorf 而不是 errors.New

fmt.Errorf 提供拼接参数功能,并对错误进行包装。虽然我们在处理简单错误时发现这两种方法没有区别,但始终将 fmt.Errorf 设置为您的偏好可以保持代码统一。

封装相同的错误

封装同样的错误,比如上面提到的 ErrorCallFailed,是一种常见的代码优化,结合 errors.Is 或者errors.As 可以解包层,更好的判断错误的真正原因。 至于 errors.Iserrors.As 的区别,前者既需要类型匹配又需要消息匹配,而后者只需要类型匹配。

func fn(n string) error {
  if _, err := get(n); err != nil {
    return ErrorCallFailed("get n")
  }
}
func abc() error {
  _, err = fn("abc")
  if err != nil {
    return fmt.Errorf("handle abc failed, %w", err)
  }
}
func main() {
  _, err := abc()
  if errors.Is(err, ErrorCallFailed){
    log.Fatal("failed to call %v", err)
    os.Exist(1)
  }
}

使用 %w 而不是 %v

一个方法被多处调用时,为了得到完整的调用链,开发者会在返回错误的地方一层一层的包裹起来,通过fmt.Errorf 不断添加当前调用的唯一特征,可以是日志也可以是一个参数。在错误拼接中偶尔使用 %v 而不是 %w 会导致 Go 的错误包装功能在 Go1.13 和之后的版本中失效。正确换行后的错误类型如下

image.png

相关文章
|
1月前
|
测试技术 Shell Go
go 语言优雅地处理 error
go 语言优雅地处理 error
|
16天前
|
缓存 监控 Kubernetes
go-zero解读与最佳实践(上)
go-zero解读与最佳实践(上)
|
1月前
|
程序员 测试技术 Go
用 Go 编写简洁代码的最佳实践
用 Go 编写简洁代码的最佳实践
|
11天前
|
存储 缓存 安全
|
13天前
|
IDE Go 开发工具
Go Error module declares its path as but was required as解决方案
文章提供了一个解决方案,用于处理在Go工程中将依赖的仓库从A更换为B(即使它们完全相同)时遇到的路径声明错误,建议通过发布新版本来解决此问题。
34 0
|
1月前
|
存储 NoSQL 测试技术
go最佳实践:如何舒适地编码
go最佳实践:如何舒适地编码
|
1月前
|
存储 缓存 Java
涨姿势啦!Go语言中正则表达式初始化的最佳实践
在Go语言中,正则表达式是处理字符串的强大工具,但其编译过程可能消耗较多性能。本文探讨了正则表达式编译的性能影响因素,包括解析、状态机构建及优化等步骤,并通过示例展示了编译的时间成本。为了优化性能,推荐使用预编译策略,如在包级别初始化正则表达式对象或通过`init`函数进行错误处理。此外,简化正则表达式和分段处理也是有效手段。根据初始化的复杂程度和错误处理需求,开发者可以选择最适合的方法,以提升程序效率与可维护性。
34 0
涨姿势啦!Go语言中正则表达式初始化的最佳实践
|
16天前
|
Kubernetes Go 数据库
go-zero 分布式事务最佳实践
go-zero 分布式事务最佳实践
|
3月前
|
Unix Docker 容器
使用docker 启动naocs 报错出现:standard_init_linux.go:241: exec user process caused "exec format error"
```markdown Error in Docker container startup: "standard_init_linux.go:241: exec user process caused \"exec format error\"". Occurred at 2024-06-29 09:26:19.910, followed by a failed hook with a syslog delivery error at 09:27:20.193. Seeking solutions from experts. ```
|
4月前
|
安全 Go 调度