golang 异常处理怎么做?
·
一、核心概念:Go 没有“异常”,只有“错误”
在其他语言(如 Java、Python)中,我们习惯用 try-catch-finally 来处理异常。但在 Go 语言中,没有这种机制,而是采用显式的错误返回方式。
简单理解:Go 认为错误是正常流程的一部分,而不是“异常情况”。函数执行出错了,就返回一个错误值,由调用方决定如何处理。
二、Go 的错误处理三件套
1️⃣ 错误类型:error 接口
type error interface {
Error() string // 返回错误描述信息
}
任何实现了 Error() string 方法的类型都可以作为错误。
2️⃣ 返回错误:函数返回 error
Go 函数通常最后一个返回值是 error 类型,如果没有错误就返回 nil。
3️⃣ 处理错误:检查 err != nil
调用函数后,立即判断 err 是否为 nil,不为 nil 就说明有错误。
三、基础示例:从简单到复杂
示例1:最基本的错误处理
package main
import (
"errors"
"fmt"
)
// 除法函数:如果除数为0,返回错误
func divide(a, b float64) (float64, error) {
if b == 0 {
// 创建一个错误对象
return 0, errors.New("除数不能为0")
}
return a / b, nil // nil 表示没有错误
}
func main() {
// 正常情况
result, err := divide(10, 2)
if err != nil {
fmt.Println("出错了:", err)
} else {
fmt.Println("结果:", result) // 输出:结果: 5
}
// 错误情况
result, err = divide(10, 0)
if err != nil {
fmt.Println("出错了:", err) // 输出:出错了: 除数不能为0
// 注意:有错误时,result 是零值(0),不能使用
}
}
示例2:读取文件(实际场景)
package main
import (
"fmt"
"os"
)
func readFile(filename string) {
// 打开文件
file, err := os.Open(filename)
if err != nil {
// 处理错误:打印并退出
fmt.Printf("打开文件失败: %v\n", err)
return
}
// 确保函数结束时关闭文件
defer file.Close()
// 读取文件内容...
fmt.Println("文件打开成功,可以读取了")
}
func main() {
readFile("不存在的文件.txt")
// 输出:打开文件失败: open 不存在的文件.txt: no such file or directory
readFile("存在的文件.txt") // 假设文件存在
// 输出:文件打开成功,可以读取了
}
示例3:自定义错误类型(携带更多信息)
package main
import (
"fmt"
"time"
)
// 自定义错误结构体,可以包含额外信息
type MyError struct {
Code int // 错误码
Message string // 错误信息
Time time.Time // 发生时间
}
// 实现 error 接口的 Error 方法
func (e *MyError) Error() string {
return fmt.Sprintf("[错误码 %d] %s (发生时间: %s)",
e.Code, e.Message, e.Time.Format("2006-01-02 15:04:05"))
}
// 业务函数
func process(age int) error {
if age < 0 {
return &MyError{
Code: 1001,
Message: "年龄不能为负数",
Time: time.Now(),
}
}
if age > 150 {
return &MyError{
Code: 1002,
Message: "年龄超出人类范围",
Time: time.Now(),
}
}
return nil
}
func main() {
err := process(-5)
if err != nil {
fmt.Println(err)
// 输出:[错误码 1001] 年龄不能为负数 (发生时间: 2024-01-15 14:30:25)
// 如果需要获取自定义字段,可以使用类型断言
if myErr, ok := err.(*MyError); ok {
fmt.Printf("错误码: %d\n", myErr.Code) // 输出:错误码: 1001
}
}
}
四、高级技巧:panic 和 recover(真正的“异常”)
重要提示:
panic和recover不是常规错误处理方式,只在程序无法继续运行的严重错误时使用(如数组越界、空指针等)。
对比表格
| 特性 | error(常规错误) | panic(严重错误) |
|---|---|---|
| 使用场景 | 可预见的错误(文件不存在、网络超时) | 不可恢复的致命错误(程序bug) |
| 处理方式 | 调用方主动判断 if err != nil |
程序崩溃或被 recover 捕获 |
| 是否终止程序 | 不会 | 会(除非 recover) |
| 推荐程度 | ✅ 经常使用 | ⚠️ 极少使用 |
示例:使用 panic 和 recover
package main
import "fmt"
// 模拟一个可能 panic 的函数
func dangerousFunction(i int) {
// 创建一个切片,长度只有3
arr := []int{1, 2, 3}
// 如果索引越界,会 panic
fmt.Println(arr[i])
}
// 用 recover 捕获 panic
func safeCall(i int) {
// defer 保证函数退出前执行
defer func() {
if r := recover(); r != nil {
// r 就是 panic 传入的值
fmt.Printf("捕获到 panic: %v\n", r)
fmt.Println("程序恢复了,不会崩溃")
}
}()
// 调用可能 panic 的函数
dangerousFunction(i)
}
func main() {
fmt.Println("程序开始")
safeCall(1) // 正常:输出 2
safeCall(5) // 越界:会触发 recover,不会崩溃
fmt.Println("程序正常结束") // 这一行依然会执行
}
输出:
程序开始
2
捕获到 panic: runtime error: index out of range [5] with length 3
程序恢复了,不会崩溃
程序正常结束
五、最佳实践总结(小白记住这些)
✅ 应该做的
-
函数设计:总是把
error作为最后一个返回值 -
立即检查:调用函数后立即检查
err != nil -
传递错误:自己无法处理的错误,用
return err向上传递 -
提供信息:用
fmt.Errorf()添加上下文信息
// 好的做法
func readConfig() error {
data, err := os.ReadFile("config.json")
if err != nil {
// 包装错误,添加有用信息
return fmt.Errorf("读取配置文件失败: %w", err)
}
// 处理 data...
return nil
}
❌ 不应该做的
-
不要忽略错误:千万不要用
_忽略error
// 坏做法
result, _ := divide(10, 0) // 错误被忽略了,result 是零值,可能导致后续bug
// 好做法
result, err := divide(10, 0)
if err != nil {
// 必须处理错误
}
-
不要滥用 panic:99% 的情况应该使用
error -
不要在非异常情况下用 recover:只用在无法提前预防的 panic 中
六、快速记忆口诀
错误处理不用怕,
error 返回带回家,
调用马上 if 检查,
nil 代表没问题啦。
panic 危险不要用,
recover 保命最后挂。
更多推荐


所有评论(0)