每个程序员都有过这种体验:打开半年前自己写的代码,盯着某个函数看了五分钟,心里冒出一句"这写的什么玩意儿"——然后 git blame 发现是自己写的。
这个瞬间包含了软件工程里最重要的一个事实:代码被阅读的次数,远多于被编写的次数。 写一次,读几十次。优化阅读体验,就是优化整个项目的生命周期成本。
命名是 80% 的战斗
坏代码有各种各样的坏法,但几乎都始于命名。data、temp、handleStuff——这类名字的共同点是:它们解释了"这里有个东西",却没有解释"这东西是干什么的"。
好命名的标准是:读者不需要跳到定义处,就能正确使用它。 getAllPosts() 比 fetch() 好,postsDirectory 比 dir 好,isDraft 比 flag 好。长一点没关系——现代编辑器有补全,而读者的理解力没有补全。
我自己的一条规则:如果给变量起名时犹豫了超过十秒,通常说明我还没想清楚它的职责。命名困难是设计问题的早期警报,值得认真对待,而不是随便起个名字绕过去。
结构:让人能"跳着读"
没有人线性地读代码。真实的阅读路径是:从入口开始,扫一眼结构,跳到关心的那个函数,再顺着调用链往下钻。
所以结构设计的出发点是:让读者在每一层都能快速判断"我要找的东西在不在这里"。 具体做法都老生常谈,但真正做到的项目不多:
- 一个文件一个主题。三百行以上的文件就该问自己:这里面是不是藏了两件事?
- 函数按抽象层级排列。高层函数在上,它调用的细节在下——读者从上往下读,就是一个逐渐深入的过程,不需要来回跳。
- 目录结构即文档。
app/blog/[slug]/page.js这条路径本身就是最好的说明:不需要注释,你就知道它是博客文章页。
注释:解释"为什么",而不是"是什么"
坏注释复述代码:i++ // i 加一。好注释解释代码无法表达的东西——为什么这样做、为什么不那样做、这段看似多余的代码在防什么。
这个博客的 Keystatic 配置里有一条注释:"extension: 'md' —— 让 Keystatic 直接读写仓库里的 .md 文件,与前端 gray-matter 读取逻辑保持一致。"删掉它,代码照样工作;但三个月后的我看到 fields.mdx({ extension: "md" }) 这个怪异的组合时,一定会困惑:为什么是 mdx 又为什么是 md?这条注释就是那个"为什么"。
写给六个月后的自己
有一种说法:写代码时,假装维护它的是一个知道你住哪里的暴力狂。我更愿意温和一点:假装读者是六个月后的自己——他聪明,但忘了所有上下文;他时间紧张,只想改一个 bug 就走;他不会感激你的精妙技巧,但会感激你的直白。
可读性不是锦上添花,是工程素养的核心。毕竟,机器只在乎代码能不能跑,在乎代码好不好读的,是人。