如何处理Golang中的错误

与其他主流编程语言(例如 JavaScript(使用语句try… catch)或 Python(及其try… except块))中的传统方法不同,Go 中处理错误需要不同的方法。为什么?因为它的错误处理功能经常被误用。

在这篇博文中,我们将了解可用于处理 Go 应用程序中的错误的最佳实践。消化本文所需的只是对Go(https://golang.org/doc/)工作原理的基本了解- 如果您在某些时候感到陷入困境,可以花一些时间研究不熟悉的概念。

空白标识符

空白标识符(https://golang.org/doc/effective_go.html#blank)是匿名占位符。它可以像声明中的任何其他标识符一样使用,但它不引入绑定。空白标识符提供了一种忽略赋值中的左手值并避免有关程序中未使用的导入和变量的编译器错误的方法。将错误分配给空白标识符而不是正确处理它们的做法是不安全的,因为这意味着您决定显式忽略已定义函数的值。

result, _ := iterate(x,y)

if value > 0 {
  // 确保在结果之前检查错误。
}

您这样做的原因可能是您不希望函数出现错误(或可能发生的任何错误),但这可能会在您的程序中产生级联效应。最好的办法是尽可能处理错误。

通过多个返回值处理错误

处理错误的一种方法是利用 Go 中的函数支持多个返回值这一事实。因此,您可以将错误变量与您定义的函数的结果一起传递:

func iterate(x, y int) (int, error) {

}

error在上面的代码示例中,如果我们认为函数有可能失败,则必须返回预定义变量。error是 Go 包中声明的接口类型built-in,其零值为nil。

type error interface {
   Error() string
 }

通常,返回错误意味着有问题,返回nil意味着没有错误:

result, err := iterate(x, y)
 if err != nil {
  // 适当处理错误
 } else {
  // 你可以走了
 }

因此,每当函数iterate被调用并且err不等于 时nil,返回的错误都应该得到适当的处理——一个选项可以是创建重试或清理机制的实例。以这种方式处理错误的唯一缺点是 Go 编译器没有强制执行,您必须决定您创建的函数如何返回错误。您可以定义一个错误结构并将其放置在返回值的位置。一种方法是使用内置结构体(您也可以在Go 的源代码(https://golang.org/src/errors/errors.go)errorString中找到此代码):

package errors

 func New(text string) error {
     return &errorString {
         text
     }
 }

 type errorString struct {
     s string
 }

 func(e * errorString) Error() string {
     return e.s
 }

在上面的代码示例中,嵌入了该方法返回的errorStringa 。要创建自定义错误,您必须定义错误结构并使用方法集将函数与结构关联起来:stringError

// 定义一个错误结构体
type CustomError struct {
  msg string
}
// 创建一个函数 Error() 字符串并将其关联到结构体。
func(error * CustomError) Error() string {
  return error.msg
}
// 然后使用 MyError 结构创建一个错误对象。
func CustomErrorInstance() error {
  return &CustomError {
      "File type not supported"
  }
}

然后可以重组新创建的自定义错误以使用内置error结构:

import "errors"
func CustomeErrorInstance() error {
    return errors.New("File type not supported")
}

内置error结构的一个限制是它不带有堆栈跟踪。这使得定位错误发生的位置变得非常困难。该错误在打印出来之前可能会经过多个函数。为了解决这个问题,您可以安装pkg/errors提供基本错误处理原语的包,例如堆栈跟踪记录、错误包装、展开和格式化。要安装此软件包,请在终端中运行以下命令:

go get github.com/pkg/errors

当您需要添加堆栈跟踪或任何其他信息以便更轻松地调试错误时,请使用 或 函数New来Errorf提供记录堆栈跟踪的错误。Errorf实现fmt.Formatter允许您使用fmt包 runes(%s、等)格式化错误的接口%v:%+v

import(
  "github.com/pkg/errors"
  "fmt"
)
func X() error {
  return errors.Errorf("Could not write to file")
}

func customError() {
  return X()
}

func main() {
  fmt.Printf("Error: %+v", customError())
}

要打印堆栈跟踪而不是普通的错误消息,您必须在格式模式中使用%+v 而不是%v,堆栈跟踪将类似于下面的代码示例:

Error: Could not write to file
main.X
 /Users/raphaelugwu/Go/src/golangProject/error_handling.go:7
main.customError
 /Users/raphaelugwu/Go/src/golangProject/error_handling.go:15
main.main
 /Users/raphaelugwu/Go/src/golangProject/error_handling.go:19
runtime.main
 /usr/local/opt/go/libexec/src/runtime/proc.go:192
runtime.goexit
 /usr/local/opt/go/libexec/src/runtime/asm_amd64.s:2471

推迟、恐慌和恢复

尽管 Go 没有例外,但它有一种类似的机制,称为“延迟、恐慌和恢复”。Go 的理念是,添加异常(例如try/catch/finallyJavaScript 中的语句)会导致代码复杂,并鼓励程序员将太多基本错误(例如无法打开文件)标记为异常。你不应该defer/panic/recover像你想的那样使用throw/catch/finally;仅在意外的、不可恢复的故障的情况下。

Defer是一种将函数调用放入堆栈的语言机制。当主机函数完成时,无论是否调用恐慌,每个延迟函数都会以相反的顺序执行。defer机制对于清理资源非常有用:

package main

import (
        "fmt"
)

func A() {
        defer fmt.Println("Keep calm!")
        B()
}
func B() {
        defer fmt.Println("Else...")
        C()
}
func C() {
        defer fmt.Println("Turn on the air conditioner...")
        D()
}
func D() {
        defer fmt.Println("If it's more than 30 degrees...")
}
func main() {
        A()
}

这将编译为:

If it's more than 30 degrees...
Turn on the air conditioner...
Else...
Keep calm!

Panic是一个停止正常执行流程的内置函数。当您调用panic代码时,这意味着您已经确定调用者无法解决问题。因此,panic仅应在极少数情况下使用,在这种情况下,您的代码或集成您的代码的任何人在此时继续不安全。下面是描述工作原理的代码示例panic:

package mainimport (
package main

import (
        "errors"
        "fmt"
)

func A() {
        defer fmt.Println("Then we can't save the earth!")
        B()
}
func B() {
        defer fmt.Println("And if it keeps getting hotter...")
        C()
}
func C() {
        defer fmt.Println("Turn on the air conditioner...")
        Break()
}
func Break() {
        defer fmt.Println("If it's more than 30 degrees...")
        panic(errors.New("Global Warming!!!"))

}
func main() {
        A()
}

上面的示例将编译为:

If it's more than 30 degrees...
Turn on the air conditioner...
And if it keeps getting hotter...
Then we can't save the earth!
panic: Global Warming!!!

goroutine 1 [running]:
main.Break()
        /tmp/sandbox186240156/prog.go:22 +0xe0
main.C()
        /tmp/sandbox186240156/prog.go:18 +0xa0
main.B()
        /tmp/sandbox186240156/prog.go:14 +0xa0
main.A()
        /tmp/sandbox186240156/prog.go:10 +0xa0
main.main()
        /tmp/sandbox186240156/prog.go:26 +0x20

Program exited: status 2.

如上所示,当panic使用但未处理时,执行流程停止,所有延迟函数按相反顺序执行,并打印堆栈跟踪。

您可以使用recover内置函数来处理panic和返回从紧急调用传递的值。recover必须始终在函数中调用,defer否则它将返回nil:

package main

import (
        "errors"
        "fmt"
)

func A() {
        defer fmt.Println("Then we can't save the earth!")
        defer func() {
                if x := recover(); x != nil {
                        fmt.Printf("Panic: %+v\n", x)
                }
        }()
        B()
}
func B() {
        defer fmt.Println("And if it keeps getting hotter...")
        C()
}
func C() {
        defer fmt.Println("Turn on the air conditioner...")
        Break()
}
func Break() {
        defer fmt.Println("If it's more than 30 degrees...")
        panic(errors.New("Global Warming!!!"))

}
func main() {
        A()
}

从上面的代码示例中可以看出,recover这可以防止整个执行流程停止,因为我们放入一个panic函数,编译器将返回:

If it's more than 30 degrees...
Turn on the air conditioner...
And if it keeps getting hotter...
Panic: Global Warming!!!
Then we can't save the earth!Program exited.

要将错误报告为返回值,您必须在调用recover函数的同一goroutine中调用该函数panic,从函数中检索错误结构recover,并将其传递给变量:

package main

import (
        "errors"
        "fmt"
)

func saveEarth() (err error) {

        defer func() {
                if r := recover(); r != nil {
                        err = r.(error)
                }
        }()
        TooLate()
        return
}
func TooLate() {
        A()
        panic(errors.New("Then there's nothing we can do"))
}

func A() {
        defer fmt.Println("If it's more than 100 degrees...")
}
func main() {
        err := saveEarth()
        fmt.Println(err)
}

每个延迟函数都将在函数调用之后但 return 语句之前执行。因此,您可以在执行 return 语句之前设置返回变量。上面的代码示例将编译为:

If it's more than 100 degrees...
Then there's nothing we can doProgram exited.

错误换行

以前,Go 中的错误包装只能通过使用pkg/errors. 然而,Go 的最新版本 -版本 1.13提供了对错误包装的支持。根据发行说明:

一个错误可以通过提供返回 的方法来e包装另一个错误。和都可供程序使用,允许提供额外的上下文或重新解释它,同时仍然允许程序基于 做出决策。wUnwrapweweww

为了创建包装错误,fmt.Errorf现在有一个%w动词,并且为了检查和展开错误,已在包中添加了几个函数error:

errors.Unwrap:该函数主要检查并暴露程序中的潜在错误。Unwrap它返回调用上的方法的结果Err。如果 Err 的类型包含Unwrap返回错误的方法。否则,Unwrap返回nil。

package errors

type Wrapper interface{
  Unwrap() error
}

下面是该方法的示例实现Unwrap:

func(e*PathError)Unwrap()error{
  return e.Err
}

errors.Is:使用此功能,您可以将错误值与哨兵值进行比较。该函数与我们通常的错误检查的不同之处在于,它不是将哨兵值与一个错误进行比较,而是将其与错误链中的每个错误进行比较。它还实现了一个Is错误方法,以便错误可以将自己作为哨兵发布,即使它不是哨兵值。

func Is(err, target error) bool

在上面的基本实现中,Is检查并报告其链中的err任何一个是否errors等于目标(哨兵值)。

errors.As:该函数提供了一种转换为特定错误类型的方法。它查找错误链中与哨兵值匹配的第一个错误,如果找到,则将哨兵值设置为该错误值并返回true:

package main

import (
        "errors"
        "fmt"
        "os"
)

func main() {
        if _, err := os.Open("non-existing"); err != nil {
                var pathError *os.PathError
                if errors.As(err, &pathError) {
                        fmt.Println("Failed at path:", pathError.Path)
                } else {
                        fmt.Println(err)
                }
        }

}

你可以在Go的源代码中找到这段代码。(https://golang.org/pkg/errors/#As)

编译结果:

Failed at path: non-existing
Program exited.

如果错误的具体值可分配给哨兵值指向的值,则错误与哨兵值匹配。As如果哨兵值不是指向实现错误的类型或任何接口类型的非零指针,则会出现恐慌。如果是As则返回 false 。errnil

概括

Go 社区最近在支持各种编程概念并引入更简洁、更简单的错误处理方法方面取得了令人印象深刻的进步。您对如何处理 Go 程序中可能出现的错误有任何想法吗?请在下面的评论中告诉我。

资源:

关于类型断言的 Go 编程语言规范(https://golang.org/ref/spec#Type_assertions)

Go 1.13 发行说明(https://golang.org/doc/go1.13)


文章来源地址https://www.toymoban.com/diary/golang/346.html

到此这篇关于如何处理Golang中的错误的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

原文地址:https://www.toymoban.com/diary/golang/346.html

如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用
使用 Fast API 和 PostgreSQL 进行 DevOps:如何使用 Docker 容器化 Fast API 应用程序
上一篇 2023年10月04日 14:44
下一篇 2023年10月05日 18:13

相关文章

  • golang的错误处理

    在软件开发中,错误处理是一个非常重要的方面。它涉及到识别、捕获和处理在程序执行期间可能发生的错误。在Golang中,错误处理是一个值得关注的主题,因为它为开发人员提供了一种优雅且高效的方式来处理错误情况。本文将深入探讨Golang的错误处理机制,并为您提供一

    2024年02月08日
    浏览(34)
  • os.signal golang中的信号处理

    在程序进行重启等操作时,我们需要让程序完成一些重要的任务之后,优雅地退出,Golang为我们提供了signal包,实现信号处理机制,允许Go 程序与传入的信号进行交互。 Go语言标准库中signal包的核心功能主要包含以下几个方面: 1. signal处理的全局状态管理 通过handlers结构体跟

    2024年02月15日
    浏览(50)
  • 100天精通Golang(基础入门篇)——第23天:错误处理的艺术: Go语言实战指南

    🌷🍁 博主猫头虎🐅🐾 带您进入 Golang 语言的新世界✨✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通

    2024年02月07日
    浏览(64)
  • Golang中的管道(channel) 、goroutine与channel实现并发、单向管道、select多路复用以及goroutine panic处理

    目录 管道(channel) 无缓冲管道 有缓冲管道 需要注意 goroutine与channel实现并发 单向管道 定义单向管道 将双向管道转换为单向管道 单向管道作为函数参数 单向管道的代码示例 select多路复用 案例演示 goroutine panic处理 案例演示 管道(channel)是 Go 语言中实现并发的一种方式,

    2024年02月06日
    浏览(45)
  • Golang 一个支持错误堆栈, 错误码, 错误链的工具库

    来腾讯之后主要使用go, 在业务开发中需要一个支持错误码对外返回, 堆栈打印等能力的错误工具库, 先开始使用 pkg/errors , 但该项目已经只读, 上次更新是几年前, 而且有一些点比如调整堆栈深度等没有支持, 后续根据业务的需要抽取了一个通用库, 且做了一些优化, 详见下方.

    2024年02月11日
    浏览(45)
  • golang导入go-git错误记录

    代码: 导入: 编译,报错: 解决: 替换引用。 ref: https://github.com/src-d/go-git/issues/914

    2024年01月22日
    浏览(55)
  • golang validator v10 自定义验证方法和错误

    需要在初始化验证器时使用

    2024年02月09日
    浏览(45)
  • golang 字符串操作、处理

    1. len()内置系统函数,计算字符串结果是字符串的字节长度,不是字符长度 2. 计算带中文的字符串长度。 需要将字符串转为 rune类型(int32) 3.为什么字符串带中文,字符长度计算方式不一样? 因为golang默认的字符编码是utf-8,  字符串的底层是 []byte类型,英文及标点符号都是

    2024年02月14日
    浏览(46)
  • golang之json注释处理

    json 作为现代比较常用的文本格式,本身是不支持注释的,因为它的设计初衷是作为一种轻量级数据交换格式,只需要包含数据本身,而不应该包含注释或者其他无关的信息。 但是有时json内字段较多,想写一些注释说明,这些都是编程工具或者编辑器特有的功能,常见的注释

    2024年02月17日
    浏览(55)
  • Golang每日一练(leetDay0095) 第一个错误的版本、完全平方数

    目录 278. 第一个错误的版本 First Bad Version  🌟 279. 完全平方数 Perfect Squares  🌟🌟 🌟 每日一练刷题专栏 🌟 Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C++每日一练 专栏 Java每日一练 专栏 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你

    2024年02月09日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包