如何处理 Go 中的错误

错误处理是添加到许多现代编程语言中的一种机制,它通常定义代码在意外情况下将如何反应。一些语法和语义错误由编译器直接处理,当它们被静态检查器检测到时,编译器会报告它们。但是有一些需要特殊处理。许多语言使用专门的语法来处理语法结构有点复杂的错误。Go 更加简单,它使用值来表示从函数返回的错误类型,就像任何其他值一样。文章通过一些例子介绍了 Go 的错误处理机制。

如果您不熟悉使用函数,您可能需要阅读我们关于该主题的文章:了解 Go 中的函数。

使用 errors.New 函数进行错误处理

Go 标准库中有一个名为errors的包,它实现了处理错误的函数。errors.New是一个内置函数,用于创建错误并指定在发生此类错误时可以向用户显示的自定义错误消息此函数采用单个参数字符串,该字符串通常是我们想要显示并警告用户有关错误的消息。

让我们编写一个简单的程序来检查电子邮件地址的有效性,并使用 Go 显示由于电子邮件地址无效而导致的内置错误消息:

package main

import (
	"fmt"
	"net/mail"
)

func isMailAddressValid(email string) (string, bool) {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return err.Error(), false		
	}
	return addr.Address, true
}

func main() {
	em, ok := isMailAddressValid("mymail**gmail.com")
	if ok {
		fmt.Println(em)
	} else {
		fmt.Println(em)
	}
}

如果您在集成开发环境 (IDE) 或代码编辑器中运行此代码,您将获得以下输出:

mail: missing '@' or angle-addr

上面的例子只是简单地显示了mail.ParseAddress()函数返回的错误信息。此函数将字符串值作为参数并根据RFC 5322地址规则进行解析。它返回一个结构类型的地址 对象和一个用字符串消息补充的错误接口对象。 ParseAddress邮件包中的一个函数。

我们可以使用errors.New函数创建我们自己的错误对象和自定义错误消息。让我们修改上面的程序以显示我们自己的自定义错误消息:

package main

import (
	"errors"
	"fmt"
	"net/mail"
)

func isMailAddressValid(email string) (string, bool) {
	addr, err := mail.ParseAddress(email)
	customErrMsg := errors.New("please provide a valid email address")
	if err != nil {		
		return customErrMsg.Error(), false
	}
	return addr.Address, true
}

func main() {
	em, ok := isMailAddressValid("mymail**gmail.com")
	if ok {
		fmt.Println(em)
	} else {
		fmt.Println(em)
	}
}

再一次,在您的代码编辑器中运行此代码将产生以下输出:

please provide a valid email address

使用 fmt.Errorf 函数在 Go 中进行错误处理

fmt包中还有另一个名为fmt.Errorf的函数。此函数使我们能够动态创建错误消息并根据格式说明符格式化错误消息,返回一个字符串值。该函数接受一个带有占位符的字符串参数,非常类似于函数fmt.printf

根据Go 文档“如果格式说明符包含带有错误操作数的 %w 动词,则返回的错误将实现返回操作数的 Unwrap 方法。包含多个 %w 动词或为其提供不实现错误接口的操作数是无效的。%w 动词是 %v 的同义词。”

这是一个对上述代码稍作修改的简单示例:

package main

import (
	"fmt"
	"net/mail"
)

func isMailAddressValid(email string) (string, bool) {
	addr, e := mail.ParseAddress(email)

	if e != nil {
		err := fmt.Errorf("you provided %s, valid format is xyz@somemail.com", email)
		return err.Error(), false
	}
	return addr.Address, true
}

func main() {
	em, ok := isMailAddressValid("mymail**gmail.com")
	if ok {
		fmt.Println(em)
	} else {
		fmt.Println(em)
	}
}

以下是上述代码的输出:

you provided mymail**gmail.com, valid format is xyz@somemail.com

Go 函数返回错误值

实际上,当出现问题时,函数会返回错误。函数的调用者通常使用if语句来查看错误或nil值的发生,这本质上意味着没有发生错误。一个返回单个错误值的函数,仅表示一些有状态的更改——例如套接字连接是否成功或数据库行发生更改等等。一个函数通常返回一个错误值来指示它的成功或指示一个简单地意味着函数失败的错误。Go 编程允许一个函数返回多个值。例如,下面的intDiv函数返回两个值(分子和分母)的整数除法的结果。如果分母值为 0(因为不允许除以零),则会返回错误以及无效值(例如 -999)。如果一切正常,则返回nil作为错误值。

package main

import (
	"errors"
	"fmt"
)

func intDiv(num, denom int) (int, error) {
	if denom == 0 {
		return -999, errors.New("divide by zero is no allowed")
	}
	return num / denom, nil
}

func main() {
	n := 20
	d := 0

	result, err := intDiv(n, d)
	if err == nil {
		fmt.Println("Result : ", result)
	} else {
		fmt.Println(err.Error())
	}
}

以下是运行上述 Go 代码示例的结果:

divide by zero is no allowed

如果函数返回多个值,函数返回的错误是最后一项,这只是一个约定。

在 Go 中使用匿名函数进行错误处理

我们可以使用匿名函数来减少错误处理中的样板代码。以下是对上述代码的轻微修改,用于在 Go 中使用匿名函数处理错误:

package main

import (
	"errors"
	"fmt"
)

func intDiv(num, denom int) (int, error) {

	he := func(err error) (int, error) {
		return -999, err
	}
	if denom == 0 {
		return he(errors.New("divide by zero is no allowed"))
	}
	return num / denom, nil
}

func main() {

	n := 20
	d := 3

	result, err := intDiv(n, d)
	if err == nil {
		fmt.Println("Result : ", result)
	} else {
		fmt.Println(err.Error())
	}

}

使用空白标识符进行错误处理

Go 使开发人员能够完全忽略从函数接收到的错误。这似乎不是一个好主意,但有时它可能是必要的。例如,我们可能会在上面的代码中收到整数除法的结果,但忽略接收错误。以下是在 Go 中使用空白标识符的示例:

//...

func main() {
	n := 20
	d := 3
	result, _ := intDiv(n, d)	
	fmt.Println("Result : ", result)
}

下划线(_)表示一个空白标识符,可以用任何类型的任何值分配或声明,并且该值被无害地丢弃。在上面的代码中,我们需要在左侧分配多个值。由于我们想忽略错误值,我们可以使用下划线(_)。它有助于避免创建虚拟变量,并清楚地表明该值将被丢弃。因此,从语法上讲,以下内容也是正确的:

//...
	_, err := intDiv(n, d)	
//...

但是,以下是不正确的:

//...
	_, _ := intDiv(n, d) // not allowed
//...

然而,我们可以这样写,这意味着完全丢弃函数返回的任何东西:

//...
	intDiv(n, d) // OK
//...

Go 错误处理技术

正如我们所看到的,Go 编程语言提供了许多通过标准库创建错误的方法,以及许多构建可以处理错误并将错误返回给调用者的函数的方法。还可以根据开发人员的需要创建和处理更复杂的自定义错误。这种灵活性是 Golang 固有的,我们将在以后的文章中详细介绍这些细节。在这里,我们通过一些相关示例快速了解了 Go 中实现的错误处理机制。

同时,请随意阅读更多关于 Golang 编程的教程:

  • Go 中的方法解释
  • Go 中的数组和切片
  • 在 Go 中使用数学
  • 在 Go 中使用字符串