Go 语言函数¶
函数是 Go 中组织行为、建立 API 和隔离测试边界的基本单元。
本篇介绍声明、参数、返回值、多返回值、可变参数、函数值、闭包、递归和 defer。
基本声明¶
相邻参数类型相同时可合并:
Go 不支持函数重载。相同包作用域中不能仅靠参数类型声明多个同名函数。
多返回值¶
func divide(left, right float64) (float64, error) {
if right == 0 {
return 0, fmt.Errorf("division by zero")
}
return left / right, nil
}
调用者同时接收结果和错误:
多返回值是语言特性,不是返回一个隐式元组对象。
命名返回值¶
命名返回值可用于短函数、文档语义和 deferred 修改。
长函数中使用裸 return 会让返回值来源不清,应显式返回。
参数是值复制¶
修改参数变量不影响调用者变量。 切片、映射、指针等值复制后可能仍引用共享数据,因此函数能通过它们修改底层对象。
可变参数¶
func sum(values ...int) int {
total := 0
for _, value := range values {
total += value
}
return total
}
调用方式:
可变参数在函数内表现为切片,并且必须是最后一个参数。
函数值与高阶函数¶
函数是一等值,可以赋值、传参和返回:
func apply(value int, operation func(int) int) int {
return operation(value)
}
double := func(value int) int {
return value * 2
}
fmt.Println(apply(5, double))
函数值可为 nil,调用 nil 函数会 panic。
闭包¶
闭包捕获外层变量,使变量生命周期延长。 多个 goroutine 调用同一闭包并修改捕获变量时,需要同步,否则会产生数据竞争。
defer¶
defer 在外围函数返回前执行调用,参数在注册 defer 时求值:
func readFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return io.ReadAll(file)
}
多个 defer 按后进先出执行。
关闭操作也可能返回错误;写文件等场景不能总是无条件忽略 Close 错误。
递归边界¶
递归写法简洁,但深度不受信任时可能耗尽栈,且示例还会发生整数溢出。 普通线性任务通常优先迭代;树遍历等自然递归结构也应设置深度或输入限制。
差异示例一:值参数与切片参数¶
两种参数都按值传递,但切片副本仍指向同一底层数组。 若函数会修改传入切片,应在命名、文档或 API 设计中明确。
差异示例二:普通返回与命名返回¶
两者都合法。普通显式返回更容易追踪;命名返回适合结果含义重要或 deferred 错误处理,但不应为省字符使用。
差异示例三:立即调用与 defer¶
输出顺序是 now、end、later。
defer 绑定外围函数,不绑定词法块,因此循环中的 defer 不会在每次迭代末自动执行。
常见错误¶
- 忽略错误返回,或只记录错误后继续使用无效结果。
- 裸返回出现在长函数中,难以确认实际返回值。
- 假设切片和映射参数完全隔离。
- 在大循环中累计 defer,延迟资源释放。
- 闭包捕获共享变量后并发修改,没有同步。
- 使用
panic代替可预期的错误返回。
工程实践¶
- 函数保持单一职责,参数和返回值表达清晰的数据流。
- 错误使用
%w包装上下文,调用者用errors.Is/As判断错误链。 - 接受接口、返回具体类型通常更利于调用方解耦,但应按实际需求设计。
- 对可能阻塞的调用接受
context.Context作为首个参数。 - 资源获取成功后尽快注册 defer,并处理重要的关闭错误。
- 通过表驱动测试覆盖正常值、边界值和错误路径。
函数 API 的质量取决于副作用、错误、所有权和取消语义是否明确,而不只取决于参数数量。