脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - 要么返回错误值,要么输出日志,别两样都做

要么返回错误值,要么输出日志,别两样都做

2024-04-15 18:02TonyBai Golang

在编写Go代码时,请记住要么返回错误值,要么输出日志,不要两者都做。通过合理地处理错误,我们可以编写出更可靠、更易于调试的代码。

1. 缘起

这周,一个产品团队内进行Go代码评审时,得到了一个结论:所有的if err != nil的地方都应该输出错误日志。然而,这种做法并不是最佳实践,它存在一些问题。

首先,打印过多的错误日志会导致日志文件变得冗长和难以阅读。其次,重复的错误信息会增加冗余。此外,每一层都打印错误日志,一旦错误信息设计不当,可能会导致上下文信息的丢失。

让我们来看一个示例,说明为什么同时输出错误日志和返回错误值会导致问题。假设我们有一个五层的Go函数调用栈,其中最底层的函数level4Function出现了一个错误:

package main

import (
 "fmt"
 "log"
)

func main() {
 if err := topFunction(); err != nil {
  log.Printf("Error: %v", err)
 }
}

func topFunction() error {
 err := level1Function()
 if err != nil {
  log.Printf("topFunction: %v", err)
  return err
 }
 return nil
}

func level1Function() error {
 err := level2Function()
 if err != nil {
  log.Printf("level1Function: %v", err)
  return err
 }
 return nil
}

func level2Function() error {
 err := level3Function()
 if err != nil {
  log.Printf("level2Function: %v", err)
  return err
 }
 return nil
}

func level3Function() error {
 err := level4Function()
 if err != nil {
  log.Printf("level3Function: %v", err)
  return err
 }
 return nil
}

func level4Function() error {
 err := fmt.Errorf("something went wrong")
 log.Printf("level4Function: %v", err)
 return err
}

在这个示例中,我们在每个函数中都输出错误日志并返回错误值。我们运行一下这个程序:

$go run main.go 
2024/04/14 23:10:05 level4Function: something went wrong
2024/04/14 23:10:05 level3Function: something went wrong
2024/04/14 23:10:05 level2Function: something went wrong
2024/04/14 23:10:05 level1Function: something went wrong
2024/04/14 23:10:05 topFunction: something went wrong
2024/04/14 23:10:05 Error: something went wrong

当我们运行程序时,日志文件会出现重复的错误信息,并且上下文信息不易于进行链式追踪,因为每个函数只打印了特定错误的信息,而没有提供之前错误的上下文。

2. 好的实践技巧

为了解决上述问题,我们需要采用一种更好的实践方法。面向调用层次较深的函数调用栈,我们应该只在最顶层的函数中输出错误日志,而在下层函数中返回错误值。但是,我们需要精心构造错误值,以形成基于wrapped error的错误链。

让我们修改示例代码,按照最佳实践进行错误处理:

package main

import (
 "fmt"
 "log"
)

func main() {
 if err := topFunction(); err != nil {
  log.Printf("Error: %v", err)
 }
}

func topFunction() error {
 err := level1Function()
 if err != nil {
  return fmt.Errorf("topFunction: %w", err)
 }
 return nil
}

func level1Function() error {
 err := level2Function()
 if err != nil {
  return fmt.Errorf("level1Function: %w", err)
 }
 return nil
}

func level2Function() error {
 err := level3Function()
 if err != nil {
  return fmt.Errorf("level2Function: %w", err)
 }
 return nil
}

func level3Function() error {
 err := level4Function()
 if err != nil {
  return fmt.Errorf("level3Function: %w", err)
 }
 return nil
}

func level4Function() error {
 err := fmt.Errorf("something went wrong")
 return fmt.Errorf("level4Function: %w", err)
}

在这个修改后的示例中,我们在每个函数中使用fmt.Errorf+%w将错误包装为一个wrapped error,并将前一层的错误作为参数传递。通过这种方式,我们构建了一个错误链,其中每个错误都包含了之前发生的错误上下文。在最顶层的main函数中,我们使用日志库输出错误日志,下面是示例程序的运行结果:

2024/04/14 23:12:16 Error: topFunction: level1Function: level2Function: level3Function: level4Function: something went wrong

我们看到:通过这种方法,我们避免了重复的错误日志,并保留了错误的上下文信息,快速定位了根因。当运行修改后的程序时,我们会看到日志文件中只打印了完整的错误链,而不是重复的错误信息。通过调用链和精心设计的错误上下文,我们还可以看到函数调用链,这使得错误的调试和处理变得更加方便和可靠。

3. 小结

在前面的示例中,我们展示了同时输出错误日志和返回错误值的问题,并介绍了如何使用wrapped error来构建错误链。通过合理地处理错误,我们可以提高代码的可读性和可维护性,同时也有助于快速定位和解决问题。

总之,在编写Go代码时,请记住要么返回错误值,要么输出日志,不要两者都做。通过合理地处理错误,我们可以编写出更可靠、更易于调试的代码。

原文地址:https://mp.weixin.qq.com/s/HvWBKN4Kk2rwHamvSTEUQQ

延伸 · 阅读

精彩推荐
  • GolangGolang中堆排序的实现

    Golang中堆排序的实现

    堆是一棵基于数组实现的特殊的完全二叉树,本文主要介绍了Golang中堆排序的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小...

    zhijie6392022-09-28
  • Golanggolang类型转换组件Cast的使用详解

    golang类型转换组件Cast的使用详解

    这篇文章主要介绍了golang类型转换组件Cast的使用详解,帮助大家更好的理解和学习使用golang,感兴趣的朋友可以了解下...

    三十三重天8832021-03-30
  • Golanggolang中实现给gif、png、jpeg图片添加文字水印

    golang中实现给gif、png、jpeg图片添加文字水印

    这篇文章主要介绍了golang中实现给gif、png、jpeg图片添加文字水印,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    huangfenhu4002021-06-01
  • Golanggo按行读取文件的三种实现方式汇总

    go按行读取文件的三种实现方式汇总

    最近有遇到需要用go读取文件的情况,下面这篇文章主要给大家介绍了关于go按行读取文件的三种实现方式,文中通过实例代码介绍的非常详细,需要的朋友可以...

    CK持续成长5432022-11-20
  • GolangGolang 日期/时间包的使用详解

    Golang 日期/时间包的使用详解

    这篇文章主要介绍了Golang 日期/时间包的使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...

    big_cat4432020-05-23
  • Golanggorm update传入struct对象,零值字段不更新的解决方案

    gorm update传入struct对象,零值字段不更新的解决方案

    这篇文章主要介绍了gorm update传入struct对象,零值字段不更新的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    qauzy16032021-05-30
  • GolangGolang学习之平滑重启

    Golang学习之平滑重启

    这篇文章主要介绍了Golang学习之平滑重启,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...

    疯狂的原始人4722020-05-18
  • Golanggo zero微服务高在请求量下如何优化

    go zero微服务高在请求量下如何优化

    这篇文章主要为大家介绍了go zero微服务高在请求量下的优化处理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    kevinwan3932022-07-05