Go 错误
最后修改时间 2024 年 4 月 11 日
在本文中,我们将展示如何在 Golang 中处理错误。
错误
错误是指程序中发生的异常、意外情况。在编程中,错误通常被称为 bug。查找和解决 bug 的过程称为调试。Go 提供了处理错误的工具。Go 中的错误是普通的数值。因此,错误可以存储在变量中,作为参数传递给函数,或者从函数返回。
错误使用内置的 error 类型表示。
大多数 Go 函数会在其返回值中返回一个错误值。(Go 支持多个返回值。)检查此错误值是我们的责任。nil 值表示没有错误。按照惯例,错误值是返回值中最右边的值。
content, err := ioutil.ReadFile("thermopylae.txt")
if err != nil {
log.Fatal(err)
}
处理错误的惯用方法是在函数调用后立即检查错误。
panic 是我们无法优雅处理的运行时错误。例如,当我们尝试除以零或访问不存在的数组索引时,可能会发生 panic。
堆栈跟踪 是程序执行过程中某个时间点上活动堆栈帧的报告。panic 时,Go 会将堆栈跟踪打印到控制台,从而帮助我们进行调试。
Go ioutil.ReadFile
ioutil.ReadFile 读取文件并返回其内容。成功调用会将 err 设置为 nil。
The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
这是要读取的文本文件。
package main
import (
"fmt"
"io/ioutil"
"log"
)
func main() {
content, err := ioutil.ReadFile("thermopylae.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(content))
}
读取文件时可能会发生许多事情。例如,我们可能没有足够的权限读取文件,提供了错误的文件路径,或者磁盘可能已满。
if err != nil {
log.Fatal(err)
}
log.Fatal 函数将错误打印到控制台,并通过调用 os.Exit 来终止程序。
$ go run read_file.go The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
如果没有错误,我们将获得文件的内容。
$ go run read_file.go 2021/01/25 11:40:26 open thermopyla.txt: no such file or directory exit status 1
错误的文件名会导致错误。
Go panic 示例
Panic 是一种运行时错误。Panic 通常很难或不可能恢复。
package main
import "fmt"
func main() {
var x int = 10
var y int = 0
fmt.Println(x / y)
}
零除会导致运行时错误。
$ go run zero_div.go
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.main()
/root/Documents/prog/golang/errors/zero_div.go:10 +0x11
exit status 2
这种错误必须由程序员修复。他必须始终检查分母,以确保这种情况不会发生。
Go errors.New
可以使用 errors.New 函数创建新错误。它返回一个格式化为给定文本的错误。即使文本相同,每次调用也会返回一个不同的错误值。
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("Some error")
if err != nil {
fmt.Println(err)
}
}
该示例使用 errors.New 创建了一个新错误。
package main
import (
"errors"
"fmt"
"log"
)
func divide(x int, y int) (int, error) {
if y == 0 {
return 0, errors.New("not possible to divide by zero")
} else {
return (x / y), nil
}
}
func main() {
if res, err := divide(6, 3); err != nil {
log.Fatal(err)
} else {
fmt.Println("The answer is", res)
}
if res, err := divide(6, 0); err != nil {
log.Fatal(err)
} else {
fmt.Println("The answer is", res)
}
}
在此示例中,我们确保分母不为零。
func divide(x int, y int) (int, error) {
if y == 0 {
return 0, errors.New("not possible to divide by zero")
} else {
return (x / y), nil
}
}
如果分母为零,我们将使用 errors.New 生成一个新错误。
$ go run zero_div2.go The answer is 2 2021/01/25 11:58:57 not possible to divide by zero exit status 1
这次没有 panic。我们自己处理了错误。
Go errors.Is
errors.Is 函数检查错误是否为指定类型。
package main
import (
"errors"
"fmt"
"log"
"os"
)
func main() {
if _, err := os.Open("data.txt"); err != nil {
if errors.Is(err, os.ErrNotExist) {
log.Fatal("file does not exist\t", err)
} else if errors.Is(err, os.ErrPermission) {
log.Fatal("insufficient permissions\t", err)
} else {
log.Fatal(err)
}
}
fmt.Println("...")
}
在代码示例中,我们检查错误是否为 os.ErrNotExist 或 os.ErrPermission 类型。
Go fmt.Errorf
使用 fmt.Errorf,我们可以创建一个带有格式化错误消息的新错误。
package main
import (
"fmt"
"math"
"os"
)
func area(radius float64) (float64, error) {
if radius < 0 {
return 0, fmt.Errorf("radius %0.2f is less than zero", radius)
}
return math.Pi * radius * radius, nil
}
func main() {
radius := -7.0
area, err := area(radius)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("Area of circle %0.2f", area)
}
在代码示例中,我们计算圆的面积。其中也包含一些错误处理,因为我们无法计算负半径的圆面积。
func area(radius float64) (float64, error) {
area 函数返回两个值:计算出的面积和错误。
if radius < 0 {
return 0, fmt.Errorf("radius %0.2f is less than zero", radius)
}
对于负的 radius 值,将使用 fmt.Errorf 创建一个新错误。
return math.Pi * radius * radius, nil
对于正确的 radius 值,我们将返回计算出的面积,并将错误设置为 nil。
area, err := area(radius)
我们调用 area 函数并获取返回值。
if err != nil {
fmt.Println(err)
os.Exit(1)
}
我们检查错误值;如果它不是 nil,我们将打印错误消息并退出程序。
$ go run circle_area.go radius -7.00 is less than zero exit status 1
Go 自定义错误类型
要定义自定义错误,我们需要实现 error 接口。
package main
import (
"fmt"
"log"
)
func enterAge(age int) (string, error) {
if age < 0 || age > 130 {
return "", &wrongAge{age, "wrong age value"}
}
return fmt.Sprintf("processing %d age value", age), nil
}
type wrongAge struct {
age int
msg string
}
func (e *wrongAge) Error() string {
return fmt.Sprintf("%d: %s", e.age, e.msg)
}
func main() {
var age int = 18
msg, err := enterAge(age)
if err != nil {
log.Fatal(err)
}
fmt.Println(msg)
age = 178
msg, err = enterAge(age)
if err != nil {
log.Fatal(err)
}
fmt.Println(msg)
}
在代码示例中,我们实现了一个自定义错误。
func enterAge(age int) (string, error) {
if age < 0 || age > 130 {
return "", &wrongAge{age, "wrong age value"}
}
return fmt.Sprintf("processing %d age value", age), nil
}
enterAge 函数接受一个年龄值;对于超出预期年龄范围的值,将生成一个错误。
type wrongAge struct {
age int
msg string
}
定义了 wrongAge 类型。
func (e *wrongAge) Error() string {
return fmt.Sprintf("%d: %s", e.age, e.msg)
}
实现了 error 接口。
$ go run custom_error.go processing 18 age value 2021/01/25 12:26:29 178: wrong age value exit status 1
来源
The Go Programming Language Specification
在本文中,我们研究了 Golang 中的错误处理。