惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Recent Announcements
Recent Announcements
B
Blog
D
Docker
V
V2EX
GbyAI
GbyAI
L
LangChain Blog
博客园 - Franky
U
Unit 42
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
博客园_首页
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
量子位
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例)
善用闭包(closure)让Go代码更优雅
Jiajun Huang · 2020-04-21 · via Jiajun的技术笔记

通常来说我们降低一个函数的复杂度的方法是拆分。即大事化小,各个击破的原理。不过拆分成函数调用有一个问题,那就是修改 函数参数的时候很蛋疼。

比如,原本由于各种原因我们有一个巨长的函数,他的作用是发工资:

func loooooooooooog(userID int) {
    // 比如1000行代码
}

因为我们要拆分成若干个小函数,这自然是一件好事情,降低了变量之间的依赖:

func loooooooooooog(userID int) {
    checkUser(userID)
    checkSalary(userID)
    Send(userID, salary)
    Notify(userID)
}

但是现在我们决定引入 Context,用以随时取消Goroutine的执行,当然啦,在Go里没法自动取消,只能手动档:

func checkUser(ctx context.Context, userID int) error {
    select {
        case <-ctx.Done():
            err := ctx.Err()
            log.Printf("ctx is done for %s", err)
            return err
        default:
    }

    // blablabla 业务代码
}

同样的代码还会出现在若干个子函数中,例如 checkSalary, Send, Notify等等。难道有谁愿意写这么多重复的代码吗? 至少我不愿意,所以捏,我决定创造一个函数,取缔这些重复的context检查,幸好Go支持闭包,要不然就不好弄了:

// Execute execute fn until ctx.Done() is received
func Execute(ctx context.Context, fn func()) error {
    select {
    case <-ctx.Done():
        err := ctx.Err()
        log.Printf("ctx is done for %s", err)
        return err
    default:
        return fn()
    }
}

那么接下来我就可以这样用了:

func loooooooooooog(ctx context.Context, userID int) {
    Execute(ctx, func() error {/*业务代码在这里*/})
    Execute(ctx, func() error {/*业务代码在这里*/})
}

不过,没有银弹。这样做又带来了一个副作用,由于闭包是可以访问外层函数的变量的,也就意味着还是有可能产生变量混用的可能性, 但是我认为这个可以人为消除,也就是说,写的时候注意,不是公共变量的,不要乱用,尽量在匿名函数内使用内部变量,当然也不是 没有解决方案,那就是外边包装一层匿名函数+调用,不过那样的缺点就是更复杂了,没有关系,我们手动控制好即可。

所以说是 “善用”闭包,任何东西,都要拿捏好,不能滥用的。


参考资料: