文章目录
错误与panic
Go 具有 panic
和 recover
之类的内置函数来管理程序中的异常或意外行为。 但错误是已知的失败,你的程序应该可以处理它们。
错误处理
使用以下代码片段来练习各种错误处理策略
package main
import (
"fmt"
"os"
)
type Employee struct {
ID int
FirstName string
LastName string
Address string
}
func main() {
employee, err := getInformation(1001)
if err != nil {
// Something is wrong. Do something.
} else {
fmt.Print(employee)
}
}
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
return employee, err
}
func apiCallEmployee(id int) (*Employee, error) {
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
我们将重点介绍如何修改 getInformation
、apiCallEmployee
和 main
函数,以展示如何处理错误。
错误处理策略
直接返回错误给调用者
将错误返回给调用方,而不执行其他任何操作
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, err // Simply return the error to the caller.
}
return employee, nil
}
返回错误给调用者,但添加更多信息
使用 fmt.Errorf()
函数,向错误添加更多上下文,但仍返回原始错误
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, fmt.Errorf("Got an error when getting the employee information: %v", err)
}
return employee, nil
}
暂时性错误进行重试逻辑
使用重试策略调用函数三次并等待两秒钟
func getInformation(id int) (*Employee, error) {
for tries := 0; tries < 3; tries++ {
employee, err := apiCallEmployee(1000)
if err == nil {
return employee, nil
}
fmt.Println("Server is not responding, retrying ...")
time.Sleep(time.Second * 2)
}
return nil, fmt.Errorf("server has failed to respond to get the employee information")
}
创建可重用的错误
使用 errors.New()
函数创建错误并重复使用
var ErrNotFound = errors.New("Employee not found!")
func getInformation(id int) (*Employee, error) {
if id != 1001 {
return nil, ErrNotFound
}
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
判断错误类型
使用errors.Is()
函数允许你比较获得的错误的类型
employee, err := getInformation(1000)
if errors.Is(err, ErrNotFound) {
fmt.Printf("NOT FOUND: %v\n", err)
} else {
fmt.Print(employee)
}
错误处理的推荐做法
在 Go 中处理错误时,请记住下面一些推荐做法:
- 始终检查是否存在错误,即使预期不存在。 然后正确处理它们,以免向最终用户公开不必要的信息。
- 在错误消息中包含一个前缀,以便了解错误的来源。 例如,可以包含包和函数的名称。
- 创建尽可能多的可重用错误变量。
- 了解使用返回错误和 panic 之间的差异。 不能执行其他操作时再使用 panic。 例如,如果某个依赖项未准备就绪,则程序运行无意义(除非你想要运行默认行为)。
- 在记录错误时记录尽可能多的详细信息(我们将在下一部分介绍记录方法),并打印出最终用户能够理解的错误。
面试题
以下是5道关于Go语言错误处理的面试题:
Go语言中如何处理错误?
解释Go语言中的panic和recover机制。
如何在Go语言中实现自定义错误类型?
Go语言中的错误处理与异常处理有什么区别?
在Go语言中,如何处理函数返回的错误?
这些题目涵盖了Go语言中错误处理的基本概念、自定义错误类型、异常处理以及函数返回错误的处理方式等方面。通过回答这些问题,可以考察面试者对Go语言错误处理机制的理解和掌握程度。
1.Go语言中如何处理错误
在Go语言中,错误处理是一种重要的编程范式,通过使用错误值和错误类型来处理程序中可能出现的异常情况。以下是在Go语言中处理错误的常见方法:
-
返回错误值:函数可以返回一个错误值作为函数结果的一部分,调用者可以使用这个错误值来判断函数执行是否成功。错误值通常是一个空接口类型(interface{})或者具体的错误类型。
func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil }
在上面的例子中,
divide
函数返回两个值:商和错误。如果除数为零,则返回一个非空的错误值。 -
检查错误:调用者需要检查函数返回的错误值,并根据错误类型采取相应的处理措施。可以使用
if
语句和==
操作符来检查错误是否为nil
。result, err := divide(10, 0) if err != nil { fmt.Println("Error:", err) return } fmt.Println("Result:", result)
在上面的例子中,如果
divide
函数返回的错误不为nil
,则打印错误信息并提前返回。 -
错误类型:Go语言提供了内置的
error
类型,用于表示错误信息。开发者也可以定义自己的错误类型,以便更好地描述错误的性质和原因。type MyError struct { Message string } func (e *MyError) Error() string { return e.Message }
在上面的例子中,定义了一个自定义的错误类型
MyError
,它有一个字符串类型的字段Message
和一个实现了Error()
方法的方法。通过实现Error()
方法,可以将自定义错误类型转换为字符串。 -
延迟处理:有时我们希望延迟错误处理,将错误传递给上层调用者或者进行其他处理。在这种情况下,可以使用空接口类型(
interface{}
)来传递错误值。func doSomething() (interface{}, error) { // ...执行一些操作... return result, nil // 返回结果和空错误值 }
在上面的例子中,函数
doSomething
返回一个空接口类型和一个错误值。调用者可以根据需要检查这个错误值。如果调用者不需要关心错误信息,可以将其忽略。 -
恢复和传播错误:当程序出现未处理的异常情况时,可以使用
panic
函数触发程序中断。在函数中可以使用recover
函数来捕获并处理panic
。同时,Go语言的运行时系统会传播错误到调用堆栈的上一层,直到找到处理错误的代码或程序被中断。
这些是在Go语言中处理错误的常见方法。通过合理地使用这些方法,可以有效地处理程序中的异常情况,提高代码的健壮性和可靠性。
2.解释Go语言中的panic和recover机制
Go语言中的panic和recover机制是用于错误处理和异常处理的机制。
Panic
Panic是一种会立即停止程序执行的机制。当程序遇到严重错误或无法恢复的错误时,可以使用panic
函数触发panic。一旦panic被触发,程序将立即停止执行当前函数的剩余代码,并向上层调用栈传播panic。如果上层调用者没有使用recover
捕获panic,程序将终止。
Panic通常用于处理严重错误或无法恢复的错误,例如内存不足、文件找不到等。
Recover
Recover是一种用于捕获和处理panic的机制。它只能在defer函数或匿名函数中被调用,并且只能捕获当前函数调用栈中的panic。如果在defer函数中调用了recover
函数,它会捕获到当前函数调用栈中的panic,并返回该panic的值。然后,defer函数可以对该值进行处理,例如记录日志、返回错误等。
Recover通常用于处理可以恢复的错误或异常情况,例如用户输入验证失败、数据库连接失败等。通过使用recover,可以将错误处理逻辑与正常的程序逻辑分离,提高代码的可读性和可维护性。
func doSomething() {
panic("Something went wrong!")
}
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Recovered:", err)
}
}()
doSomething()
fmt.Println("This line will not be executed")
}
在main函数中,使用defer和recover来捕获并处理panic。通过recover函数,可以恢复程序的控制流程,并打印错误消息。
总结:
Panic用于触发程序中断,而Recover用于捕获和处理panic。合理地使用panic和recover机制可以帮助我们更好地处理错误和异常情况,提高程序的健壮性和可靠性。在Go语言中,建议对于严重错误或无法恢复的错误使用panic,而对于可以恢复的错误或异常情况使用Recover。