4. Go 语言基础回顾(三):流程控制、函数与错误处理
在报表回放工程中,逻辑分支、循环遍历、函数封装与错误处理是代码骨架。本章聚焦 Go 语言流程控制、函数特性及错误处理机制,为后续工程落地打下坚实基础。
4.1 流程控制:if/else、for、switch
Go 语言的流程控制语法简洁,没有括号包裹条件表达式,但强制使用花括号。这在报表回放中用于判断数据状态、循环处理记录集、分支选择执行路径。
4.1.1 if/else 条件分支
Go 的 if 支持在条件前执行一个简单语句,常用于初始化局部变量并立即判断。报表回放中,常用于检查文件是否存在、数据是否有效。
// 标准 if/else
if score >= 90 {
fmt.Println("优秀")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
// 带初始化语句的 if
if err := loadReport("daily.rpt"); err != nil {
fmt.Println("加载报表失败:", err)
return
}
4.1.2 for 循环
Go 只有 for 关键字,但支持三种形式:经典三段式、条件循环、无限循环。报表回放中,for range 用于遍历切片、map 和通道。
| 循环形式 | 语法示例 | 工程场景 |
|---|---|---|
| 经典 for | for i := 0; i < n; i++ { } |
按索引遍历固定长度的记录列表 |
| 条件 for | for retry > 0 { } |
重试机制,直到成功或次数耗尽 |
| 无限 for | for { select { ... } } |
事件循环、监听通道消息 |
| for range | for k, v := range reportMap { } |
遍历报表字段映射、数据行 |
// for range 遍历切片
records := []string{"record_001", "record_002", "record_003"}
for idx, record := range records {
fmt.Printf("索引 %d: %s\n", idx, record)
}
// 遍历 map
reportMeta := map[string]string{"title": "日报", "author": "张三"}
for key, value := range reportMeta {
fmt.Printf("%s = %s\n", key, value)
}
4.1.3 switch 分支选择
Go 的 switch 默认不穿透(无需 break),且支持表达式列表和类型断言。在报表回放中,常用于根据报表类型选择不同的解析器。
// 表达式 switch
switch reportType {
case "daily", "weekly":
fmt.Println("常规报表")
case "monthly":
fmt.Println("月度汇总")
default:
fmt.Println("未知类型")
}
// 类型 switch (interface 类型断言)
var data interface{} = "string_value"
switch v := data.(type) {
case string:
fmt.Println("字符串:", v)
case int:
fmt.Println("整数:", v)
default:
fmt.Println("未知类型")
}
switch 配合 fallthrough 关键字可实现穿透逻辑,但建议仅在需要连续执行多个 case 时使用,避免逻辑混乱。
4.2 函数:多返回值、defer、匿名函数
Go 函数是一等公民,支持多返回值、延迟执行(defer)和匿名函数(闭包)。在报表回放工程中,这些特性用于资源释放、错误传播和回调处理。
4.2.1 多返回值
Go 函数可以返回多个值,通常用于返回结果和错误。报表回放中,解析函数常返回数据对象和错误信息。
// 多返回值函数
func parseReport(path string) ([]Record, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取文件失败: %w", err)
}
records, err := unmarshalRecords(data)
if err != nil {
return nil, err
}
return records, nil
}
// 调用时使用多变量接收
records, err := parseReport("daily.rpt")
if err != nil {
log.Fatal(err)
}
4.2.2 defer 延迟执行
defer 将函数调用推迟到当前函数返回前执行,常用于关闭文件、解锁互斥锁、释放数据库连接。多个 defer 按后进先出(LIFO)顺序执行。
func processReport(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close() // 函数结束时关闭文件
// 加锁
mu.Lock()
defer mu.Unlock() // 函数结束时解锁
// 业务逻辑
return nil
}
4.2.3 匿名函数与闭包
匿名函数没有函数名,可直接赋值给变量或作为参数传递。闭包捕获外部变量,在报表回放中用于构造回调、延迟计算和中间件。
// 匿名函数赋值
maxFunc := func(a, b int) int {
if a > b {
return a
}
return b
}
result := maxFunc(10, 20)
// 闭包:计数器
func newCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
counter := newCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
4.3 错误处理:error 接口与 panic/recover
Go 没有异常机制,错误通过返回值传递。标准库 error 是一个内置接口,任何实现了 Error() string 方法的类型都可作为错误。报表回放工程中,错误处理是保证稳定性的关键。
4.3.1 error 接口与自定义错误
// 标准错误创建
err := errors.New("文件未找到")
// 带格式化信息的错误
err := fmt.Errorf("解析报表 %s 失败: %w", reportName, innerErr)
// 自定义错误类型
type ReportError struct {
Code int
Message string
}
func (e *ReportError) Error() string {
return fmt.Sprintf("错误码 %d: %s", e.Code, e.Message)
}
// 使用 errors.As 进行类型断言
var reportErr *ReportError
if errors.As(err, &reportErr) {
fmt.Println("错误码:", reportErr.Code)
}
4.3.2 panic 与 recover
panic 用于不可恢复的错误,recover 用于捕获 panic 并恢复执行。在报表回放中,仅在初始化阶段或严重异常时使用 panic,业务逻辑中优先返回 error。
func safeProcess(data []byte) (result string, err error) {
defer func() {
if r := recover(); r != nil {
// 将 panic 转为 error 返回
err = fmt.Errorf("处理过程发生 panic: %v", r)
}
}()
// 可能触发 panic 的代码
result = riskyOperation(data)
return result, nil
}
4.3.3 错误处理策略对比
| 策略 | 适用场景 | 工程示例 |
|---|---|---|
| 返回 error | 业务逻辑中可预期的错误 | 文件不存在、数据格式错误 |
| panic/recover | 不可恢复的严重错误 | 配置缺失、内存不足、断言失败 |
| errors.Is / As | 错误链判断与类型断言 | 判断是否为特定错误类型 |
| fmt.Errorf + %w | 错误包装,保留原始错误链 | 多层调用时传递上下文 |
// 错误链示例
func loadConfig(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("读取配置失败: %w", err)
}
// 解析配置...
return nil
}
func main() {
err := loadConfig("config.yaml")
if err != nil {
// 判断是否为文件不存在的错误
if errors.Is(err, os.ErrNotExist) {
fmt.Println("配置文件不存在,使用默认配置")
} else {
log.Fatal(err)
}
}
}
fmt.Errorf 的 %w 动词包装错误时,可形成错误链。后续调用 errors.Is 和 errors.As 可沿链查找,便于在多层调用中定位根因。