▌ Go函数的工程化设计
显式契约 → 正交组合 → 可测试性优先
声明格式:
// 具名函数
func Add(a int, b int) int {
return a + b
}
// 方法声明
type Calculator struct{}
func (c Calculator) Multiply(x, y int) int {
return x * y
}
参数传递规则:
参数类型 | 传递方式 | 修改影响范围 |
---|---|---|
基本类型 | 值拷贝 | 仅函数内有效 |
指针 | 地址拷贝 | 影响原始数据 |
切片/map | 结构头拷贝 | 可修改底层元素 |
大对象传递优化:
// 错误方式(值拷贝开销大)
func ProcessUser(u User) { ... }
// 正确方式(指针传递)
func ProcessUser(u *User) {
// 修改u.Name会影响原对象
}
// 只读场景优化
func ReadUser(u User) {
// 通过const语义限制修改
}
可变参数技巧:
func Sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 调用方式:Sum(1,2,3) 或 Sum(slice...)
Q1:如何实现函数可选参数?
参考答案:
type Config struct {
Timeout time.Duration
Retry int
}
type Option func(*Config)
func WithTimeout(t time.Duration) Option {
return func(c *Config) {
c.Timeout = t
}
}
func NewService(opts ...Option) *Service {
cfg := &Config{Timeout: 5 * time.Second}
for _, opt := range opts {
opt(cfg)
}
return &Service{config: cfg}
}
Q2:以下代码输出什么?
func update(s []int) {
s[0] = 100
s = append(s, 200)
}
func main() {
s := []int{1,2,3}
update(s)
fmt.Println(s)
}
答案:[100 2 3]
(append操作可能触发扩容,不影响原切片)
演进阶段:
int process()
try-catch
(Go不采用)result, err := process()
标准库实践:
file, err := os.Open("data.txt")
if err != nil {
return fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
错误链构建:
import "errors"
var ErrSystem = errors.New("系统错误")
func process() error {
if err := step1(); err != nil {
return fmt.Errorf("%w: 步骤1失败", ErrSystem)
}
return nil
}
// 使用:errors.Is(err, ErrSystem) 可检测错误类型
堆栈追踪增强:
import "github.com/pkg/errors"
func main() {
err := process()
fmt.Printf("%+v", errors.Wrap(err, "执行失败"))
}
Q1:如何避免错误处理代码冗余?
参考答案:
defer
统一处理公共错误Unwrap
接口Q2:errors.New
与fmt.Errorf
有何区别?
参考答案:
errors.New
创建静态错误实例fmt.Errorf
支持格式化且可通过%w
包装错误errors.New
,临时错误用fmt.Errorf
变量捕获机制:
func Counter() func() int {
var count int
return func() int {
count++
return count
}
}
c := Counter()
fmt.Println(c(), c()) // 输出1,2
内存泄漏场景:
func Process() {
data := make([]byte, 1e6)
go func() {
// 持有data引用,阻止GC回收
fmt.Println(len(data))
}()
}
竞态条件解决:
func SafeCounter() func() int {
var (
mu sync.Mutex
count int
)
return func() int {
mu.Lock()
defer mu.Unlock()
count++
return count
}
}
Q1:以下代码输出什么?为什么?
func main() {
for i := 0; i < 3; i++ {
defer func(){ fmt.Print(i) }()
}
}
答案:333
(闭包捕获循环变量最终值)
Q2:如何实现函数记忆化(Memoization)?
参考答案:
func Memoize(fn func(int) int) func(int) int {
cache := make(map[int]int)
return func(n int) int {
if v, ok := cache[n]; ok {
return v
}
v := fn(n)
cache[n] = v
return v
}
}
▌ 异常处理的三驾马车
defer → 资源保障
panic → 异常触发
recover → 恢复控制流
应用场景对比:
错误类型 | 处理方式 | 示例场景 |
---|---|---|
预期错误 | 多返回值error | 文件不存在 |
不可恢复错误 | panic | 数据库连接丢失 |
全局异常 | recover + 日志 | 第三方库未处理panic |
生产级恢复模式:
func SafeHandler() {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered panic: %v", r)
metrics.RecordPanic()
}
}()
// 业务逻辑...
}
合法panic场景:
// 1. 手动触发致命错误
if !validate(input) {
panic("invalid input")
}
// 2. 第三方库未处理错误
res := C.unsafe_call()
if res != 0 {
panic("CGO调用失败")
}
禁用场景:
// 常规错误应使用error返回
func OpenFile(path string) (File, error) {
if !fileExists(path) {
return nil, fmt.Errorf("文件不存在")
}
}
Q1:recover
为何只能在defer
中生效?
参考答案:
Q2:以下代码能否捕获panic?
func main() {
panic("test")
defer func() {
recover()
}()
}
答案:不能,defer
在panic
之后注册,未执行即崩溃
类型声明示例:
type FilterFunc func(string) bool
func RemoveEmpty(s []string, fn FilterFunc) []string {
res := make([]string, 0)
for _, v := range s {
if !fn(v) {
res = append(res, v)
}
}
return res
}
// 调用示例
filter := func(s string) bool { return s == "" }
cleaned := RemoveEmpty([]string{"a", "", "b"}, filter)
中间件模式:
type Handler func(http.ResponseWriter, *http.Request)
func LoggingMiddleware(next Handler) Handler {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() {
log.Printf("%s %s %v", r.Method, r.URL, time.Since(start))
}()
next(w, r)
}
}
// 注册路由
http.HandleFunc("/", LoggingMiddleware(userHandler))
函数柯里化:
func Add(a int) func(int) int {
return func(b int) int {
return a + b
}
}
add5 := Add(5)
fmt.Println(add5(3)) // 8
Q1:如何实现函数执行时间统计装饰器?
参考答案:
func TimeIt(fn func()) func() {
return func() {
start := time.Now()
defer func() {
fmt.Printf("耗时: %v\n", time.Since(start))
}()
fn()
}
}
// 使用
task := TimeIt(func(){ /* 耗时操作 */ })
task()
Q2:以下代码输出什么?
var funcs []func()
for _, v := range []int{1,2,3} {
funcs = append(funcs, func(){ fmt.Print(v) })
}
for _, f := range funcs {
f()
}
答案:333
(闭包捕获循环变量最终值)
第6章系统构建了Go函数式编程的完整体系: