📝 一个"偷懒型"开发者的效率秘籍 | ⏱️ 阅读 4 分钟 | ✨ 文档即测试成就解锁
🎬 起因:我又被文档坑了
上周写了一个"字符串工具包",想给同事用。打开 pkg.go.dev 一看文档...
结果:❌ 示例代码是半年前的;❌ 注释说"返回大写",实际返回小写;❌ 同事照着抄,线上 panic 了。
🤦♂️ 那一刻我悟了:文档不跑测试,等于没写。
于是我翻出了 Go 的"隐藏技能":可执行示例(Executable Examples)。用了一周,现在我的工具库文档,每个示例都能点"Run"直接跑,还能自动防过期。真香警告⚠️
🧱 第一步:3 分钟上手,把普通函数"变"成可运行示例
假设我写了个超简单的"字符串反转"函数:
// reverse/reverse.go
package reverse
func String(s 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]
}
return string(runes)
}
✅ 操作步骤:
1️⃣ 在 _test.go 文件里写示例函数
// reverse/reverse_test.go
func ExampleString() {
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.go
func Calc(n int) (int, error) {
if n < 0 {
return 0, fmt.Errorf("negative number: %d", n)
}
if n <= 1 {
return 1, nil
}
result := 1
for i := 2; i <= n; i++ {
result *= i
}
return result, nil
}
// factorial/factorial_test.go
func ExampleCalc_normal() {
result, _ := Calc(5)
fmt.Println(result)
// Output:
// 120
}
func ExampleCalc_error() {
_, err := Calc(-3)
fmt.Println(err)
// Output:
// negative number: -3
}
✨ 技巧:一个函数可以写多个示例,用
_后缀区分,文档里会并列展示,用户一眼看懂正常/异常场景。
📌 示例 2:处理"无序输出"的场景
切片去重后,如果用 map 做中间存储,遍历顺序不确定,怎么办?
// unique/unique.go
func Ints(nums []int) []int {
seen := make(map[int]bool)
var result []int
for _, n := range nums {
if !seen[n] {
seen[n] = true
result = append(result, n)
}
}
return result
}
// unique/unique_test.go
func ExampleInts() {
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.go
func Example() {
// 演示工具包常用组合
a := Add(2, 3) // 5
b := Multiply(a, 4) // 20
fmt.Println(IsEven(b))
// Output:
// true
}
🔹 给类型和方法写示例
// counter/counter.go
type Counter struct {
count int
}
func (c *Counter) Inc() {
c.count++
}
func (c *Counter) Value() int {
return c.count
}
// counter/counter_test.go
func ExampleCounter_basic() {
var c Counter
c.Inc()
c.Inc()
fmt.Println(c.Value())
// Output:
// 2
}
func ExampleCounter_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 秒后同事回复:"👍 懂了,返回空切片,文档真方便!"
那一刻我觉得:最好的文档,不是写得最漂亮的,而是用户能亲手"玩"起来的。