在 Golang 中,错误处理和异常处理是两个不同的概念,分别用于处理不同类型的问题。Golang 的设计哲学强调显式错误处理,而异常机制主要用于不可恢复的严重问题。以下是它们的详细介绍:
1. 错误处理
在 Golang 中,错误处理是通过返回值实现的。函数通常返回一个 error 类型的值,调用者需要检查该值是否为 nil 来判断是否发生了错误。
1.1 基本错误处理模式
函数返回 (result, error),调用者通过检查 error 来决定如何处理。
示例:
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero") // 返回错误
}
return a / b, nil // 正常返回结果
}
func main() {
result, err := divide(10, 0) // 调用函数
if err != nil {
fmt.Println("Error:", err) // 错误处理
} else {
fmt.Println("Result:", result)
}
}
运行结果:
Error: division by zero
1.2 自定义错误
Golang 支持创建自定义错误类型,通过实现 error 接口的 Error() 方法来自定义错误信息。
示例:
package main
import "fmt"
// 自定义错误类型
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func doSomething() error {
return &MyError{Code: 404, Message: "Resource not found"} // 返回自定义错误
}
func main() {
err := doSomething()
if err != nil {
fmt.Println(err) // 打印自定义错误
}
}
运行结果:
Error 404: Resource not found
1.3 使用 fmt.Errorf 格式化错误
Golang 提供了 fmt.Errorf 来生成格式化的错误信息。
示例:
package main
import (
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide %d by zero", a) // 格式化错误信息
}
return a / b, nil
}
func main() {
_, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
}
}
运行结果:
Error: cannot divide 10 by zero
1.4 errors.Is 和 errors.As
在 Golang 1.13+ 中,errors 包增加了 errors.Is 和 errors.As 方法,用于检查和处理嵌套错误。
示例:
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findResource(id int) error {
if id == 0 {
return fmt.Errorf("resource error: %w", ErrNotFound) // 嵌套错误
}
return nil
}
func main() {
err := findResource(0)
if errors.Is(err, ErrNotFound) { // 检查是否是 ErrNotFound 错误
fmt.Println("Resource not found")
} else {
fmt.Println("Other error:", err)
}
}
运行结果:
Resource not found
2. 异常处理
Golang 的异常处理是通过 panic 和 recover 实现的。它们主要用于处理程序中的不可恢复错误,例如逻辑错误或严重的运行时问题。
2.1 panic
-
panic会立即停止当前函数的执行,并向调用栈上传播,最终导致程序崩溃。 - 通常用于不可恢复的错误,例如数组越界、空指针访问等。
示例:
package main
func main() {
panic("something went wrong") // 触发 panic
}
运行结果:
panic: something went wrong
goroutine 1 [running]:
main.main()
/path/to/main.go:4 +0x39
exit status 2
2.2 recover
-
recover用于捕获panic,从而恢复程序的正常运行。 - 必须在
defer中调用,否则无法捕获panic。
示例:
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r) // 捕获 panic
}
}()
panic("something went wrong") // 触发 panic
fmt.Println("This will not be printed")
}
运行结果:
Recovered from panic: something went wrong
2.3 panic 和 recover 的使用场景
- panic:用于不可恢复的错误,例如程序的内部逻辑错误。
-
recover:用于捕获
panic,从而在某些情况下恢复程序的运行。
示例:在子协程中捕获 panic
package main
import (
"fmt"
"time"
)
func safeGo(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
f()
}()
}
func main() {
safeGo(func() {
panic("something went wrong in goroutine") // 子协程触发 panic
})
time.Sleep(1 * time.Second) // 确保主协程不会过早退出
fmt.Println("Main function continues")
}
运行结果:
Recovered from panic: something went wrong in goroutine
Main function continues
3. 错误处理 vs 异常处理
| 特性 | 错误处理 | 异常处理 |
|---|---|---|
| 机制 | 函数返回值 (error) |
panic 和 recover
|
| 使用场景 | 常见的业务逻辑错误 | 程序中不可恢复的严重错误 |
| 处理方式 | 显式检查错误 | 隐式捕获(通过 recover) |
| 对程序的影响 | 不影响程序的正常运行 | 未捕获的 panic 会导致程序崩溃 |
| 推荐使用场景 | 推荐用于大多数场景 | 仅用于异常或不可恢复的错误 |
4. 总结
-
错误处理是 Golang 的主流方式,通常通过返回
error值让调用者显式处理。 -
异常处理(
panic和recover)仅用于不可恢复的错误,应该谨慎使用。 - 在 Golang 中,更推荐使用显式的错误处理方式,而不是依赖异常机制。
- 对于子协程中的
panic,可以通过recover捕获,避免影响主程序的运行。