Golang中何时使用panic、log.Fatal()以及直接return

Golang中何时使用panic、log.Fatal()以及直接return 我正在用Go语言创建一个小型应用程序,想知道何时使用log.Fatal()、panic以及仅打印错误并返回函数而不触发panic。

例如:

//Configuration 表示从yaml配置文件中读取的数据
type Configuration struct {
    Certificate      string `yaml:"Certificate"`
    Key              string `yaml:"Key"`
    Hostname         string `yaml:"Hostname"`
    Port             string `yaml:"Port"`
    ConnectionString string `yaml:"ConnectionString"`
    DbType           string `yaml:"DbType"`
}

//SetConfiguration 从yaml配置文件获取配置
func SetConfiguration() *Configuration {
  config := new(Configuration)

  data, err := ioutil.ReadFile("config/server.yml")
  if err != nil {
	 log.Fatal(err)
 }

   ok := yaml.Unmarshal(data, config)
   if ok != nil {
	 log.Fatal(err)
   }

 return config
}

更多关于Golang中何时使用panic、log.Fatal()以及直接return的实战教程也可以访问 https://www.itying.com/category-94-b0.html

12 回复

我认为应该使用 log.Fatal(),因为程序无法打开一个关键依赖文件

更多关于Golang中何时使用panic、log.Fatal()以及直接return的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


所以我应该尽可能尝试恢复错误吗?

这实际上是一个非常实用的网站和解释,感谢您的帮助 ❤️!

感谢所有精彩的回复,我太爱这个社区了 ❤️!

感谢您详细且有用的回答 😊。但我很好奇您为什么不使用日志记录器呢?

Log.Fatal 不是记录器,它只是另一种形式的恐慌,我们不希望我们的库出现恐慌(停止执行)。

由于这是一个方法,我不会使用panic而是返回错误,并在调用方代码中决定如何处理。如果你决定使用panic,应该将方法重命名为MustSetConfiguration。

由于在我的领域需要将结构化日志记录为JSON格式,而使用标准日志记录器很难实现这一点。此外,我发现zap在添加任意元数据时的处理方式非常出色。

我之前提到过,从方法和函数中返回错误,如果无法初始化等情况,就在主函数中进行恐慌处理。

普遍认为库不应使用恐慌,但如果调用 log.Fatal 具有相同的效果,那么这显然也应该被禁止。

func main() {
    fmt.Println("hello world")
}

链接

我不使用 Golang 的日志记录器,因此我将问题重新表述为:

何时应该打印输出,何时应该打印并退出,以及何时应该返回错误?

我通常在以下情况打印错误:要么可以轻松恢复,要么可以回退到其他方案。例如,无法连接数据库时,我可以在几毫秒后重试。

当无法恢复时,我会打印错误并退出程序。例如,无法打开重要配置文件的情况。

当我不在应用程序的主控制循环中或编写库代码时,我会选择返回错误。因为我讨厌库代码直接在我宝贵的标准输出中打印格式混乱且无结构的内容(我主要使用 JSON 格式记录日志)。

func main() {
    fmt.Println("hello world")
}

在Go语言中,paniclog.Fatal()和直接return的使用场景有所不同,主要取决于错误的性质和应用程序的上下文。

panic的使用场景

panic通常用于不可恢复的错误,比如程序状态不一致或严重逻辑错误。在标准库中,panic常用于初始化阶段的致命错误。

func initDatabase() {
    if db == nil {
        panic("database connection is nil - initialization failed")
    }
}

log.Fatal()的使用场景

log.Fatal()会记录错误并调用os.Exit(1)终止程序。它适用于启动阶段的致命错误,如配置加载失败、关键服务无法启动等。

在你的代码示例中,使用log.Fatal()是合适的:

func SetConfiguration() *Configuration {
    config := new(Configuration)
    
    data, err := ioutil.ReadFile("config/server.yml")
    if err != nil {
        log.Fatal("Failed to read config file:", err)
    }
    
    err = yaml.Unmarshal(data, config)
    if err != nil {
        log.Fatal("Failed to parse YAML config:", err)
    }
    
    return config
}

直接return错误的使用场景

对于可恢复的错误或函数调用链中的常规错误,应该返回错误给调用者处理。

func ProcessData(filename string) error {
    data, err := ioutil.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("failed to read file: %w", err)
    }
    
    // 处理数据...
    return nil
}

func main() {
    err := ProcessData("data.txt")
    if err != nil {
        log.Printf("Processing failed: %v", err)
        // 可以选择继续执行其他任务或优雅退出
    }
}

具体建议

  1. 启动阶段的关键错误:使用log.Fatal(),如配置加载、数据库连接初始化失败
  2. 不可恢复的程序状态错误:使用panic,但尽量避免在业务逻辑中使用
  3. 常规函数中的错误:返回错误给调用者处理
  4. 需要清理资源的场景:使用defer配合错误返回,而不是paniclog.Fatal()
func ProcessWithCleanup() error {
    file, err := os.Open("data.txt")
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 处理文件内容
    return nil
}

在你的配置加载场景中,使用log.Fatal()是正确的选择,因为配置加载失败通常意味着程序无法继续运行。

回到顶部