Go语言因其精简的特性设计,使得逻辑表达往往需要依赖显式的重复代码;而Rust选择了丰富的语言特性(宏、泛型、Trait),其结果则是开发者必须编写大量的结构性代码来支撑这些特性。
大家好,我是Tony Bai。
在编程语言的鄙视链中,Go语言常因其看似“繁琐”而饱受诟病。
“if err != nil写到手断”、“缺少语法糖”、“到处都是重复的样板代码”……这些几乎已经成了Go的标志性吐槽。
相比之下,Rust则常被视为“表达力”的代表,拥有强大的宏、模式匹配和类型系统,被认为能用更少的代码做更多的事。
然而,Ben Boyter最近的一项硬核研究,通过分析GitHub上各语言Top 100仓库(总计约4亿行代码),得出了一个令编程社区大跌眼镜的结论:
在代码重复率和“样板代码”密度上,Go和Rust几乎处于同一水平线。
不仅是行数:ULOC 指标
传统的SLOC(源代码行数)往往无法真实反映项目的复杂度和冗余度。Ben Boyter使用了他开发的工具scc中的一个特殊指标:ULOC (Unique Lines of Code,唯一代码行数)。
ULOC指标并非简单的“全量去重”,而是通过剥离“结构性噪音”来更精准地衡量系统的真实复杂度。其计算逻辑是:
剔除结构化冗余:不仅排除了空行,还排除了单纯的闭合大括号行(})以及在不同文件中大量出现的公共引用代码(如include或import)。过滤文件级模板:有效识别并扣除在项目中每个文件顶部几乎完全相同的License(许可证)声明头,避免这些非逻辑性的“样板文字”虚增代码总量。计入注释成本:与传统SLOC不同,ULOC会保留注释统计。作者认为,注释与代码一样需要同等的维护精力,反映了开发者的思考过程,因此属于“有效工作量”。
通过这种方式计算出的Dryness(干度),代表了剔除了“语法支架”和“版权模板”后,真正的业务逻辑与注释在代码中的占比。百分比越高,说明重复代码越少,信息密度越高;百分比越低,说明“样板代码”或重复结构越多。
令人震惊的对比:Go vs Rust
让我们直接看数据(数据来源:GitHub Top 100 仓库分析,2026年2月):
发现了吗?Rust (60.5%) 和 Go (58.78%) 的差距微乎其微,甚至可以说在统计学上是等价的。
Ben Boyter在文章中坦言,他之前也持有“Go的样板代码比Rust多得多”的刻板印象。但数据表明,虽然两者的“啰嗦”方式不同,但结果是一样的:
Go的啰嗦:体现在显式的错误处理、显式的循环结构,以及为了简单性而不得不写的重复逻辑。Rust的啰嗦:体现在复杂的类型系统设置、Trait的实现(impl blocks),以及为了满足借用检查器而编写的“仪式性”代码。
正如作者所总结的:
Go狂热者:“Go很简单!” -> “是的,简单到你需要把同一件事写很多遍。” Rust狂热者:“Rust表达完美!” -> “是的,但你花了40%的时间在写setup代码和trait实现。”
其他颠覆性的发现
除了Go和Rust的“握手言和”,这份报告还有几个极具冲击力的发现:
1. Lisp 家族是“干度之王”
Clojure 以 77.91% 的惊人密度位居榜首。Haskell 紧随其后。
这验证了一个古老的观点:如果你想要最高的“人类思想 vs 击键次数”比率,Lisp和函数式语言依然是王者。它们几乎每一行代码都是纯粹的业务逻辑。
2. Java 居然比 Go 和 Rust 都“干”?
Java的得分为65.72%,显著高于Go、Rust和C#。
这听起来反直觉,毕竟Java以PublicStaticVoidMain这种冗长著称。但这可能说明:现代Java及其生态(Spring等)通过注解等方式极大地消除了样板代码。或者,Top 100的Java项目多为成熟的业务系统,核心逻辑占比大,而Go/Rust项目中系统级代码(通常包含更多底层重复逻辑)较多。
3. 脚本语言的特性
Shell Script的密度极高(72.24%),但这主要是因为Shell脚本通常很短且高度定制化(Bespoke),很难复用,因此“唯一性”很高。
小结:复杂度的守恒
这个研究告诉我们一个道理:语言特性(Features)并不一定能消除复杂度,它往往只是转移了复杂度。
Go选择了少量的特性,导致逻辑必须通过显式的重复代码来表达;Rust选择了丰富的特性(宏、泛型、Trait),导致开发者必须编写大量的结构性代码来支撑这些特性。
对于Gopher来说,这或许是一种宽慰:别再为if err != nil感到羞愧了。隔壁写Rust的兄弟,虽然代码看起来很酷,但他们为了让编译器开心而敲击键盘的次数,并不比你少。
