程序员必知的 15 条软件工程定律
程序员必知的 15 条软件工程定律
在AI变革之际,传统软件工程范式重构前,我们来回顾下程序员必知的 15 条软件工程定律,来聊聊这些定律背后的故事。
软件开发几十年,无数天才程序员和管理者总结出了一系列”定律”。这些定律不是数学公式,却比公式更真实——因为它们来自无数个加班的深夜、延期的项目和崩溃的系统。
一、Brooks 定律:人多不一定力量大
定义
“向一个已经延期的软件项目增加人手,只会让它更加延期。”
产生背景
1975 年,Fred Brooks 出版了被誉为”软件工程圣经”的《人月神话》。Brooks 曾是 IBM System/360 操作系统的项目经理,这个项目是当时最大的软件项目之一,最终严重延期。
当时的管理层做了一个”直觉正确”的决定——加人。结果项目不但没有加速,反而更慢了。Brooks 深刻反思后,总结出了这条定律。
为什么会这样?
-
沟通成本爆炸:2 个人只需要 1 条沟通线,5 个人就需要 10 条,10 个人需要 45 条。人数越多,开会、对齐、扯皮的时间越多。 -
新人需要学习期:新加入的人需要老成员花时间来带,老成员的产出反而下降了。 -
任务不可无限拆分:就像 9 个孕妇不能在 1 个月内生出一个孩子。
解决什么问题
提醒管理者:项目延期时,不要盲目加人。更应该做的是砍需求、调整优先级、或者给团队排除障碍。
二、Conway 定律:你的代码长得像你的组织架构
定义
“设计系统的组织,其产生的系统架构等价于该组织的沟通结构。”
产生背景
1967 年,程序员 Melvin Conway 在一篇论文中提出了这个观察。他注意到,一个由 4 个团队开发的编译器,最终产出了一个 4 步骤的编译器——不是因为 4 步骤是最优设计,而是因为有 4 个团队。
这篇论文最初被《哈佛商业评论》拒稿,理由是”作者没有证明自己的论点”。但几十年后,无数案例反复验证了这条定律的正确性。
现实中的例子
-
如果前后端是两个团队,系统大概率会分成前后端两层。 -
如果公司按地区划分团队(深圳团队、北京团队),系统架构很可能也会按地区拆分。 -
微服务架构的流行,很大程度上与小团队自治的组织模式相互促进。
解决什么问题
如果你想要什么样的系统架构,先调整你的组织架构。这就是所谓的”逆 Conway 定律”——用组织结构来驱动技术架构。
三、Gall 定律:复杂系统不是设计出来的
定义
“一个切实可行的复杂系统,总是从一个切实可行的简单系统演化而来。从头开始设计的复杂系统永远不会work,也无法通过修补让它 work。”
产生背景
John Gall 是一名儿科医生(没错,不是程序员),他在 1975 年出版的《系统学》一书中提出了这条定律。作为医生,他观察到人体这个复杂系统是经过亿万年演化而来的,而非一次性设计出来的。
这个观察被软件工程界广泛接受,因为软件系统也是如此。
现实中的例子
-
淘宝最初只是一个简单的 PHP 网站,后来才逐步演化成今天的超级架构。 -
微信最初只能发文字消息,朋友圈、支付、小程序都是后来逐步加上去的。 -
很多公司花几年时间想”从零设计一个完美的新系统来替换老系统”,最终几乎都失败了。
解决什么问题
告诫架构师和管理者:不要试图一步到位设计一个完美系统。先做一个能跑的简单版本,然后持续迭代。MVP(最小可行产品)思维的理论基础之一就是 Gall 定律。
四、Hofstadter 定律:你永远低估工期
定义
“事情总是比你预期的要花更长时间,即使你已经考虑了 Hofstadter 定律。”
产生背景
Douglas Hofstadter 是一位认知科学家,他在 1979 年的名著《哥德尔、艾舍尔、巴赫》中提出了这条定律。这条定律之所以有趣,在于它是一个递归定律——你觉得已经考虑到了延期的可能,但实际上还是会延期。
为什么总是低估?
-
乐观偏见:人类天生倾向于高估自己的能力。 -
未知的未知:你不知道你不知道什么。一个看似简单的功能,做着做着会冒出无数意想不到的问题。 -
需求变更:客户说”就改一点点”,但这一点点可能牵一发而动全身。 -
技术债务:老代码的坑只有踩到才知道有多深。
解决什么问题
做项目估期时,在你认为合理的时间上再乘以 1.5 到 2 倍。不要相信”这次一定能按时完成”的幻觉。经验丰富的项目经理都会主动预留缓冲时间。
五、Knuth 优化原则:别急着优化
定义
“过早优化是万恶之源。”(Premature optimization is the root of all evil.)
产生背景
Donald Knuth 是计算机科学的泰斗,著有《计算机程序设计艺术》。他在 1974 年的论文中写下了这句被引用了无数次的话。
完整的原文其实是:”我们应该忘记小的效率提升,大约 97% 的时候:过早优化是万恶之源。但我们不应该放弃那关键的 3%。”
为什么过早优化有害?
-
增加复杂度:为了优化而引入的复杂代码,往往更难维护和调试。 -
优化错了地方:没有性能分析数据支撑的优化,很可能在优化不是瓶颈的地方。 -
浪费时间:花了大量时间优化的功能,可能根本不是用户常用的功能。
解决什么问题
写代码时的优先级应该是:先让它工作,再让它正确,最后让它快。需要优化时,先用 profiler 工具找到真正的瓶颈,再有针对性地优化。
六、Linus 定律:人多眼尖
定义
“只要有足够多的眼睛,所有 bug 都是浅显的。”(Given enough eyeballs, all bugs are shallow.)
产生背景
这条定律以 Linux 之父 Linus Torvalds 的名字命名,但实际上是由 Eric Raymond 在 1997 年的《大教堂与集市》一书中提出的。
Raymond 观察到 Linux 的开发模式与传统商业软件截然不同。Linux 的源码对所有人开放,任何人都可以查看和提交修复。这种”集市”模式让 bug 被更快地发现和修复。
现实中的验证
-
开源软件的安全漏洞发现速度通常比闭源软件快。 -
Code Review(代码审查)已经成为现代软件开发的标准实践,本质上就是在利用这条定律。 -
GitHub 上的开源项目,经常有陌生人提交 bug fix。
解决什么问题
鼓励团队建立代码审查文化,重要的代码至少要有两个人看过。同时也是开源运动的理论基础之一。
七、Murphy 定律:会出错的,一定会出错
定义
“凡是可能出错的事,就一定会出错。”(Anything that can go wrong will go wrong.)
产生背景
1949 年,美国空军工程师 Edward Murphy 在一次火箭减速实验中发现,技术人员把所有 16 个传感器都装反了。他说了一句:”如果有什么方法可以把事情搞砸,那一定会有人去搞砸。”
这句吐槽后来被总结为 Murphy 定律,成为人类历史上最著名的定律之一。
在软件工程中的体现
-
那个”不可能触发”的分支,终究会在生产环境被触发。 -
那个”没人会这样操作”的用户行为,总有用户会这样做。 -
那个”不需要备份,不会出问题”的数据库,一定会在某天崩溃。
解决什么问题
养成防御性编程的习惯:
-
所有外部输入都要校验 -
所有可能失败的操作都要有异常处理 -
定期做备份和灾难恢复演练 -
设计系统时要考虑各种边界情况
八、Pareto 原则:80/20 法则
定义
“80% 的结果来自 20% 的原因。”
产生背景
1896 年,意大利经济学家 Vilfredo Pareto 发现意大利 80% 的土地被 20% 的人拥有。后来人们发现这个比例在各个领域都惊人地适用。
在软件工程中的体现
-
80% 的 bug 集中在 20% 的代码中:微软和 IBM 的研究都证实了这一点。 -
80% 的用户只使用 20% 的功能:所以不要试图把每个功能都做到极致。 -
80% 的性能问题来自 20% 的代码:所以优化要找到那关键的 20%。 -
80% 的工单来自 20% 的模块:重点关注高频出问题的模块。
解决什么问题
帮助我们分配有限的资源。不要平均用力,而要找到那关键的 20%,集中精力攻克。无论是修 bug、做性能优化还是做产品规划,都应该先找到最有价值的那 20%。
九、YAGNI 原则:你不会需要它的
定义
“You Aren’t Gonna Need It — 你不会需要它的。”
产生背景
YAGNI 原则来自极限编程(XP)方法论,由 Ron Jeffries 等人在 1990 年代末提出。
他们观察到一个普遍现象:程序员总喜欢”未雨绸缪”,提前写好各种可能用到的功能。但事实证明,这些提前写好的代码大部分永远不会被用到。
真实场景
-
“万一以后要支持多语言呢?先把国际化框架搭好吧。”——然后这个项目三年了还只有中文版。 -
“万一以后数据量大了呢?先上分布式数据库吧。”——然后用户总共就几百人。 -
“万一以后要换数据库呢?先写一个抽象层吧。”——然后 MySQL 用了十年也没换过。
解决什么问题
只实现当前确定需要的功能。未来的需求等到真正需要时再实现。这样可以:
-
减少代码量,降低维护成本 -
避免过度设计带来的复杂性 -
把时间花在真正有价值的事情上
十、DRY 原则:不要复制粘贴
定义
“Don’t Repeat Yourself — 系统中的每一个知识点,都应该有一个单一、明确、权威的表述。”
产生背景
Andy Hunt 和 Dave Thomas 在 1999 年出版的经典著作《程序员修炼之道》中正式提出了 DRY 原则。
注意,DRY 不仅仅是”不要复制粘贴代码”这么简单。它说的是”知识”不要重复——包括业务逻辑、配置信息、文档等一切信息。
违反 DRY 的后果
-
修改一处逻辑时,忘了改另一处相同的逻辑,导致 bug。 -
同一份配置出现在三个地方,改了两个忘了一个。 -
同一个公式在代码里写了五遍,需求变更时要改五个地方。
解决什么问题
通过抽取公共方法、使用配置中心、模板继承等方式,确保每个知识点只出现一次。修改时只改一处,降低出错概率。
但也要注意:不要为了 DRY 而 DRY。如果两段代码只是碰巧相似,但代表不同的业务含义,强行合并反而会带来不必要的耦合。
十一、KISS 原则:简单就是美
定义
“Keep It Simple, Stupid — 保持简单,傻瓜。”
产生背景
KISS 原则据说起源于美国海军,由飞机工程师 Kelly Johnson 提出。他要求设计的飞机必须简单到普通机械师用基本工具就能修理——因为在战场上没有复杂的维修设备。
这个原则后来被广泛应用于软件工程。
在软件中的体现
-
能用简单 if-else 解决的,不要用设计模式。 -
能用一个函数实现的,不要搞三层抽象。 -
能用现成轮子的,不要自己造。 -
代码写出来是给人看的,顺便让机器执行。
解决什么问题
降低系统复杂度,让代码更容易理解、维护和调试。简单的代码有更少的 bug、更好的可读性、更低的维护成本。
记住 Dijkstra 的话:”简单是可靠的先决条件。”
十二、Lehman 软件演化定律:不进则退
定义
“一个正在被使用的软件系统必须持续适应变化,否则会变得越来越不能令人满意。”
产生背景
Meir Lehman 从 1960 年代开始在 IBM 研究软件演化问题,经过 20 多年的持续研究,他总结出了 8 条软件演化定律。其中最核心的就是”持续变化定律”。
他研究了大量长期运行的软件系统后发现,软件所处的环境(操作系统、硬件、用户需求、竞争对手)在不断变化,如果软件不跟着变化,就会逐渐被淘汰。
另一条重要的 Lehman 定律
“随着系统的演化,其复杂度会不断增加,除非有专门的工作来维护或减少它。”
这就是为什么老系统越来越难维护——每次添加新功能都会增加一点复杂度,日积月累就变成了”屎山”。
解决什么问题
提醒团队:
-
要持续投入精力做重构,控制复杂度增长 -
要定期偿还技术债务 -
“能跑就不要动”是一种慢性自杀
十三、Postel 定律:宽进严出
定义
“发送时要保守,接收时要开放。”(Be conservative in what you send, be liberal in what you accept.)
产生背景
Jon Postel 是互联网先驱之一,TCP/IP 协议的主要贡献者。他在 1980 年的 RFC 761(TCP 协议规范)中提出了这条原则,也被称为”鲁棒性原则”。
在互联网早期,各种系统的实现参差不齐。为了让不同系统能够互联互通,Postel 提出了这条实用的设计原则。
在软件开发中的应用
-
API 设计:接口返回的数据要严格遵守约定(保守发送),但接收请求时要尽量兼容各种格式(开放接收)。 -
数据处理:解析 JSON 时,遇到多余的字段不要报错,直接忽略。 -
用户输入:用户输入” hello “,系统应该能自动 trim 后正确处理。
解决什么问题
提高系统的健壮性和兼容性。在一个多系统协作的环境中,你无法控制别人怎么发数据,但你可以让自己更加包容。
十四、Peter 原则:晋升到不胜任
定义
“在层级组织中,每个员工都会被提拔到他无法胜任的职位上。”
产生背景
Laurence Peter 在 1969 年的著作《彼得原理》中提出了这个观察。他发现,组织通常根据员工在当前岗位的表现来决定是否晋升——但上一个岗位的胜任不代表下一个岗位也能胜任。
在软件行业的体现
这条定律在技术行业尤为常见:
-
一个优秀的程序员被提拔为技术经理,但他可能不擅长管理人。 -
一个出色的架构师被提拔为 CTO,但他可能不懂商业战略。 -
一个高效的项目经理被提拔为部门负责人,但他可能不善于政治博弈。
解决什么问题
-
组织应该建立技术专家和管理者双通道的晋升体系。 -
不是每个优秀的程序员都应该去做管理,做技术专家同样有价值。 -
晋升决策不应该只看过去的表现,还要评估候选人是否具备新岗位需要的能力。
十五、Goodhart 定律:指标一旦成为目标就会失效
定义
“当一个度量指标变成目标时,它就不再是一个好的度量指标。”
产生背景
Charles Goodhart 是英国经济学家,他在 1975 年研究英国货币政策时提出了这个观察。当政府把某个经济指标作为调控目标时,人们就会想方设法去”优化”这个指标,而不是改善它本来要衡量的东西。
在软件工程中的血泪案例
-
代码覆盖率:要求覆盖率必须 90% 以上,于是团队写了大量没有断言的”假测试”,覆盖率达标了,但 bug 一个没少。 -
代码行数:按代码行数考核产出,于是有人把一行代码拆成五行写。 -
Bug 数量:以修复 bug 数量来评估绩效,于是开发者开始自己制造 bug 再自己修复。 -
上线速度:以发布频率来衡量效率,于是团队频繁发布半成品。
解决什么问题
-
度量指标应该是用来观察和辅助决策的,而不是用来考核的。 -
如果一定要用指标来考核,应该使用多个互相制衡的指标,避免被单一指标”游戏化”。 -
关注结果而不是过程指标:比如关注”客户满意度”比关注”代码覆盖率”更有意义。
最后
这 15 条定律,横跨了项目管理、架构设计、编码实践和组织管理四大领域。它们不是书本上的教条,而是无数软件从业者在真实项目中用时间和教训换来的智慧。
你可能会觉得这些定律都是”常识”。但正如马克·吐温说的:
“常识其实一点也不常见。”
在日常工作中,我们总是不自觉地违反这些定律——加班加人赶进度、提前优化还没上线的功能、复制粘贴”临时”用一下、设计一个”一步到位”的完美系统。
或许,这些定律最大的价值不在于让我们知道什么是对的,而是在我们即将犯错的那一刻,能有一个声音提醒自己:
“等等,这个坑前人已经踩过了。”
夜雨聆风