乐于分享
好东西不私藏

用 Go 可执行示例,让文档自己跑测试!

用 Go 可执行示例,让文档自己跑测试!

📝 一个”偷懒型”开发者的效率秘籍 | ⏱️ 阅读 4 分钟 | ✨ 文档即测试成就解锁


🎬 起因:我又被文档坑了

上周写了一个”字符串工具包”,想给同事用。打开 pkg.go.dev 一看文档…

结果:❌ 示例代码是半年前的;❌ 注释说”返回大写”,实际返回小写;❌ 同事照着抄,线上 panic 了。

🤦‍♂️ 那一刻我悟了:文档不跑测试,等于没写

于是我翻出了 Go 的”隐藏技能”:可执行示例(Executable Examples)。用了一周,现在我的工具库文档,每个示例都能点”Run”直接跑,还能自动防过期。真香警告⚠️


🧱 第一步:3 分钟上手,把普通函数”变”成可运行示例

假设我写了个超简单的”字符串反转”函数:

// reverse/reverse.gopackage reversefuncString(string)string{    runes :=[]rune(s)for i, j :=0,len(runes)-1; i < j; i, j = i+1, j-1{        runes[i], runes[j]= runes[j], runes[i]}returnstring(runes)}

✅ 操作步骤:

1️⃣ 在 _test.go 文件里写示例函数

// reverse/reverse_test.gofuncExampleString(){    fmt.Println(String("hello"))// Output:// olleh}

2️⃣ 关键点:// Output: 注释不是装饰!

  • 这是测试期望值,go test 会拿实际输出跟它比对
  • 输出不匹配?测试直接红,逼你更新文档或修代码

3️⃣ 跑起来验证

$ go test -v ./reverse=== RUN   ExampleString--- PASS: ExampleString (0.00s)PASS

💡 爽点:这个示例会自动出现在 pkg.go.dev 的文档里,用户点”Run”就能在浏览器里试玩,不用装 Go 环境!


🎨 第二步:给常用工具函数加”活”文档

📌 示例 1:计算阶乘(带边界处理)

// factorial/factorial.gofuncCalc(int)(int,error){if n <0{return0, fmt.Errorf("negative number: %d", n)}if n <=1{return1,nil}    result :=1for i :=2; i <= n; i++{        result *= i}return result,nil}
// factorial/factorial_test.gofuncExampleCalc_normal(){    result,_:=Calc(5)    fmt.Println(result)// Output:// 120}funcExampleCalc_error(){_, err :=Calc(-3)    fmt.Println(err)// Output:// negative number: -3}

✨ 技巧:一个函数可以写多个示例,用 _ 后缀区分,文档里会并列展示,用户一眼看懂正常/异常场景。

📌 示例 2:处理”无序输出”的场景

切片去重后,如果用 map 做中间存储,遍历顺序不确定,怎么办?

// unique/unique.gofuncInts(nums []int)[]int{    seen :=make(map[int]bool)var result []intfor_, n :=range nums {if!seen[n]{            seen[n]=true            result =append(result, n)}}return result}
// unique/unique_test.gofuncExampleInts(){    nums :=[]int{3,1,2,1,3}    unique :=Ints(nums)// 排序后再输出,保证顺序固定    sort.Ints(unique)    fmt.Println(unique)// Output:// [1 2 3]}

✅ 如果实在不想排序,也可以加 // Unordered output:,测试只检查”这些值都有”,不关心顺序。


📦 第三步:进阶玩法,示例也能”组团”

🔹 给整个包写”入门示例”

// 不带函数名,就是包级示例// mathutil/mathutil_test.gofuncExample(){// 演示工具包常用组合    a :=Add(2,3)// 5    b :=Multiply(a,4)// 20    fmt.Println(IsEven(b))// Output:// true}

🔹 给类型和方法写示例

// counter/counter.gotype Counter struct{    count int}func(*Counter)Inc(){    c.count++}func(*Counter)Value()int{return c.count}
// counter/counter_test.gofuncExampleCounter_basic(){var c Counter    c.Inc()    c.Inc()    fmt.Println(c.Value())// Output:// 2}funcExampleCounter_reset(){    c := Counter{count:10}    c.count =0// 直接重置内部字段(演示用,生产环境建议加 Reset() 方法)    fmt.Println(c.Value())// Output:// 0}

🎯 命名口诀:Example + 类型名 + _ + 方法名,文档工具自动关联,不用手动配置。


💡 第四步:集成到我的开发流

🔄 本地开发:示例即测试

# 每次改代码,跑测试顺便验证文档$ go test ./... -run Example# ✅ 所有示例通过,文档放心发布

🚀 CI/CD:防”文档腐烂”

# .github/workflows/test.yml- name: Verify examples  run: go test -run Example ./...# 💥 示例挂了?合并请求直接拦下,逼你更新文档

🌐 线上文档:用户能”玩”的代码

  • 用户访问 pkg.go.dev/我的工具库
  • 看到示例 → 点”Run” → 浏览器里直接看效果
  • 不用 git clone,不用 go install,零门槛体验函数行为

🎯 复盘:为什么我强烈安利这个功能?

✅ 真香点
💡 适用场景
📚 文档自动同步代码,永不”过期”
写工具库/算法包,希望用户快速上手
🧪 示例即测试,改代码先过文档关
团队协作,减少”文档谁维护”的扯皮
🌍 用户浏览器里直接试玩,降低使用门槛
开源项目,需要大量代码演示
⚡ 写一次,两用(文档+测试),效率翻倍
个人项目,想偷懒但又要专业感

🤔 我的血泪经验:以前写文档像”写情书”,写完就忘;现在写示例像”写剧本”,每次跑测试都是”彩排”,上线自然稳。


🎬 尾声:让代码自己”说话”

上周有同事问:“这个 UniqueInts 函数,输入空切片会返回 nil 还是空切片?”

以前我得:查代码→写回复→贴截图→等反馈。

这次我直接回:“点这个示例链接,改输入为 []int{} 点 Run,看输出👇”

30 秒后同事回复:“👍 懂了,返回空切片,文档真方便!”

那一刻我觉得:最好的文档,不是写得最漂亮的,而是用户能亲手”玩”起来的。

来源:华为云社区,golang学习记,https://bbs.huaweicloud.com/blogs/476452

文末福利

金三银四招聘季,今天就给大家分享一份免费的Go工程师面试题包含:

  • 字节,腾讯,百度.. Go工程师面试题大公开

  • Go语言必考理论题+详细解析

  • Go语言语法高频问题+详细解析

  • Go语言常见面试题+详细解析

文章有限,在此只展示部分内容,完整版领取见文末!!

100道一线大厂Go工程师面试题合集

  • Go内存逃逸分析
  • 进程,协程,线程各自的优缺点
  • slice和array区别
  • 向为nil的channel发送数据会怎么样
  • ……

50道Golang必考理论+常见语法问题解析

  • Goroutine调度策略
  • 在golang协程和channel配合使用
  • golang并发题目测试
  • 重要的全局变量
  • 多协程查询切片问题

25道 Go并发和Redis高频测试题

  • Mysql一条SQL加锁分析
  • MySQL遇到过死锁问题吗,该如何何解决的?
  • MySQL中DATETIME和TIMESTAMP的区别
  • ……

【配套PDF版资料】

免费领取,仅限前 100

扫描上方二维码~

备注:go工程师面试题

100%免费领取!!