56条软件工程开发定律

34 阅读35分钟

软件工程定律

Laws of Software Engineering

56 条指导开发者的智慧法则

整理自 lawsofsoftwareengineering.com,该网站由 Dr. Milan Milanović 创建,系统性地收录了软件工程领域最重要的定律、原则与思维模型。无论你是初入行业的新手,还是经验丰富的资深工程师,这些定律都将帮助你在架构设计、团队协作、项目规划、代码质量、系统扩展和决策制定中做出更明智的选择。


为什么需要了解软件工程定律?

软件工程不仅仅是写代码,它更是一门关于人、系统和复杂性的学科。在漫长的软件发展史中,无数前辈工程师从失败与成功中总结出了宝贵的经验规律。这些定律不是教条,而是思维的灯塔——当你在技术选型中犹豫不决、在项目延期时焦头烂额、在代码审查中争论不休时,它们能为你提供清晰的思考框架。

这些定律涵盖了七大类别:架构 Architecture团队 Teams规划 Planning质量 Quality扩展 Scale设计 Design决策 Decisions


一、架构

Architecture — 9 条定律

架构是软件的骨架,决定了系统的上限。以下定律揭示了架构设计中的深层规律。


1. 海勒姆定律

Hyrum's Law

当一个系统拥有足够多的用户时,所有可观察到的行为都会被某人所依赖。

海勒姆定律描述了一个令人头疼的现象:无论你在 API 契约中承诺了什么,只要用户足够多,他们就会依赖你从未正式支持的行为。这意味着,即使是 bug 或副作用,也可能被某些用户当作"功能"来使用。当你要修改系统时,任何变更都可能破坏某些人的工作流。

💡 核心启示: 系统的实际契约不仅仅是官方文档,更是用户在真实场景中观察到的所有行为。维护者必须意识到,任何改动都可能产生意料之外的影响。


2. 盖尔定律

Gall's Law

一个可运行的复杂系统,总是从一个可运行的简单系统演化而来。

John Gall 观察到,成功的复杂系统都是从成功的简单系统开始成长的。如果你试图从零开始构建一个复杂系统,通常会失败,因为有太多未知因素未被验证。正确的做法是先构建一个能工作的简单版本,然后基于真实反馈逐步迭代和增加复杂度。

💡 核心启示: 不要追求一步到位的大设计,而应采用 MVP(最小可行产品)策略,先让核心功能跑起来,再逐步生长。成功的大型系统都是有机成长起来的。


3. 泄漏抽象定律

The Law of Leaky Abstractions

所有非平凡的抽象,在某种程度上都是泄漏的。

Joel Spolsky 在 2002 年提出这一定律。我们创建抽象来隐藏复杂性,但在某些场景下,这些抽象不可避免地会"泄漏",暴露出底层的复杂性。例如,ORM 让你像操作对象一样操作数据库,但当查询性能出现问题时,你不得不理解底层 SQL 和数据库索引机制。

💡 核心启示: 使用高层工具并不意味着你可以不了解底层原理。开发者应当对所用抽象的边界和潜在泄漏点保持警觉,在创建抽象时尽量减少泄漏,并文档化可能失效的场景。


4. 复杂度守恒定律

Tesler's Law / Conservation of Complexity

每个应用都有一定数量的不可约复杂度,只能转移,无法消除。

Larry Tesler 在 20 世纪 80 年代提出这个原则:对于任何系统,都存在无法去除的固有复杂度,关键在于谁来承担它。如果你让软件对用户极其简单,那意味着开发者要在内部处理更多复杂性。反之,如果用户需要管理大量设置和步骤,你就把复杂度留在了用户端。

💡 核心启示: 优秀的设计往往是开发者通过智能默认值、算法等方式吸收大部分复杂度,使用户交互更加简单。好的设计隐藏复杂度,而非消除复杂度。


5. CAP 定理

CAP Theorem

一个分布式系统最多只能同时保证一致性、可用性和分区容错性中的两个。

Eric Brewer 在 2000 年提出 CAP 定理。由于网络分区是不可避免的现实,系统设计者必须在一致性和可用性之间做出选择。当网络分裂发生时,你要么保持一致(所有节点数据一致,但部分请求可能失败),要么保持可用(每个请求都得到响应,但数据可能短暂不一致)。

💡 核心启示: 现实中的数据库都做出了取舍——MongoDB 偏向一致性,Cassandra 偏向可用性。架构师需要根据业务场景做出明智的权衡。


6. 第二系统效应

Second-System Effect

小型成功的系统之后,往往会跟着一个过度工程的臃肿替代品。

Fred Brooks 在 1975 年的《人月神话》中首次提出这一概念。当第一个简洁的系统取得成功后,设计者在第二个版本中往往会放开手脚,试图解决所有遗留问题、添加所有曾经期望的功能,结果常常产出一个臃肿、难以维护的庞然大物。

💡 核心启示: 过度自信是第二系统效应的根源。团队在第一次成功后,往往高估自己处理更大复杂度的能力。保持克制,聚焦核心需求,是避免第二系统效应的关键。


7. 分布式计算的谬误

Fallacies of Distributed Computing

新入行的分布式系统设计者常犯的八个错误假设。

这八条谬误由 L. Peter Deutsch 等人在 Sun Microsystems 时期总结:网络是可靠的、延迟为零、带宽无限、网络是安全的、拓扑不会改变、有一个管理员、传输成本为零、网络是同构的。这些假设在本地开发中可能成立,但在分布式环境中完全行不通。

💡 核心启示: 正确构建分布式系统必须考虑重试、超时、安全措施和动态发现。防御性设计是必须的——使用缓存应对带宽/延迟问题,构建冗余应对网络不可靠,处理动态成员变化。


8. 意外后果定律

Law of Unintended Consequences

每当你修改一个复杂系统时,请预期意外。

社会学家 Robert K. Merton 在 20 世纪普及了这个概念。在软件工程中,添加新功能可能意外地降低性能,简化 UI 可能导致用户困惑,修复一个 bug 可能引入两个新的 bug。复杂系统的相互依赖关系和人为因素使得结果难以完全预测。

💡 核心启示: 无论计划多么周密,复杂系统中的重大变更都可能产生意想不到的后果。这些后果可能是意外的收益、意外的副作用,甚至可能是让原本问题更严重的反效果。


9. 扎温斯基定律

Zawinski's Law

每个程序都会膨胀,直到可以读邮件为止。

Jamie Zawinski 在 Netscape 时期提出的这条幽默定律,深刻揭示了软件功能蔓延的本质:应用程序不断添加功能,直到它们涵盖了原本完全不在范围内的能力。用户和产品经理总是要求"再加一个功能",导致软件从简洁走向臃肿。

💡 核心启示: 功能蔓延是不可抗拒的力量。开发者应该捍卫工具的焦点,抵抗平台化的诱惑。每增加一个功能都会增加复杂度,使产品对用户而言变得更加困惑。


二、团队

Teams — 9 条定律

软件是由人构建的,团队动力学深刻影响着软件的形态与质量。


10. 康威定律

Conway's Law

组织设计的系统,映射了该组织的沟通结构。

Melvin Conway 在 1967 年提出这个洞见:软件系统的架构往往反映了构建它的组织的沟通结构。一个拥有独立前端、后端和数据库部门的公司,很可能会产生三层架构。小型分布式团队倾向于产出模块化服务架构,而大型集中式团队倾向于构建单体应用。

💡 核心启示: 团队可以使用"逆康威定律"——有意地调整组织结构以匹配期望的软件架构。当你拆分团队或部门的方式,很可能决定了软件边界的划分位置。


11. 布鲁克斯定律

Brooks's Law

为延期的软件项目增加人手,只会使其更加延期。

Frederick Brooks 在管理 IBM OS/360 项目时深刻体会到:软件开发不是可以完美分割的任务。增加一个人需要培训成本,增加的沟通路径数量以组合方式增长。如果项目已经延期,新人的加入在短期内会使情况更糟而非更好。

💡 核心启示: 与其期望增加人手解决延期问题,不如调整范围或时间表。警惕"多招几个程序员"这种简单粗暴的解决方案。


12. 邓巴数

Dunbar's Number

一个人能够维持的稳定社交关系存在约 150 人的认知上限。

人类学家 Robin Dunbar 在 1992 年发现,人类大脑的新皮层大小限制了社交关系的数量。在软件组织中,一个 150 人以内的工程部门可能还能靠非正式方式运作,但超过这个数字后,就需要更正式的规则、沟通渠道和管理层级。

💡 核心启示: 强协作发生在远低于 150 人阈值的小组中。小团队胜出。当组织增长超过这个临界点,必须主动拆分子组并建立有效的沟通机制。


13. 林格尔曼效应

The Ringelmann Effect

随着团队规模增大,个人的生产力会下降。

Max Ringelmann 在 1913 年观察到:当人们一起拉绳子时,每个人施加的力比单独拉时更小。在软件团队中同样如此——大型团队中,部分人会减少努力(因为觉得他人会弥补),而协调开销(会议、对齐、同步)随人数增长急剧增加。

💡 核心启示: 存在一个临界点,超过它后增加人员带来的是边际递减甚至负回报。小型、专注的团队往往优于协调不力的大型团队。小型团队或个人有更强的主人翁意识。


14. 普莱斯定律

Price's Law

参与者总数的平方根完成了 50% 的工作。

物理学家 Derek de Solla Price 在研究学术界时发现了这个规律。在 100 人的工程组织中,大约 10 个人可能贡献了一半的产出。这意味着大部分关键工作依赖于少数核心人员。

💡 核心启示: 产出不会随人数线性增长。识别并留住那些对产出至关重要的人员是组织管理的关键。失去特定个人可能对生产力造成巨大影响。


15. 帕特定律

Putt's Law

懂技术的人不管理技术,管理技术的人不懂技术。

这条定律揭示了一个普遍现象:最优秀的工程师往往不在管理岗位,而管理者通常缺乏对技术细节的深入理解。这可能导致管理层设定不切实际的期望,工程师则感到被误解。

💡 核心启示: 成功的组织通过培养管理者的技术素养或提拔具有技术洞察力的领导者来弥合这一鸿沟。技术卓越和人员领导力需要不同的技能,在一方面失败并不自动具备另一方面的资格。


16. 彼得原理

Peter Principle

在等级制度中,每个员工都倾向于晋升到其不称职的级别。

Laurence J. Peter 在 1969 年提出了这个尖锐的观察:一位优秀的工程师被提升为技术主管,但在新角色中挣扎——公司失去了一个好工程师,却多了一个差管理者。

💡 核心启示: 组织应当提供双轨职业路径(技术路线 vs 管理路线),防止优秀技术人员因晋升而停滞。不是每个高级工程师都应该成为管理者。


17. 巴士系数

Bus Factor

使项目陷入严重困境所需的最少团队成员损失数量。

巴士系数揭示了项目中的"人类单点故障"。在很多软件团队中,一两个人可能掌握着遗留系统的全部知识、关键算法或部署流程。如果这些人离开,其他人无法轻易接手工作。

💡 核心启示: 巴士系数为 1 意味着一个人持有核心知识,他一走项目就停摆。团队应当通过知识共享、文档编写、代码审查和职责轮换来提高巴士系数。


18. 呆伯特原理

Dilbert Principle

公司倾向于将不称职的员工提升到管理层,以限制他们能造成的损害。

Scott Adams 在 1996 年的《呆伯特原理》中提出的这条讽刺性定律,反映了一个令人不安的组织缺陷:许多组织将管理岗位当作处理低绩效员工的方式,而非需要专业能力的角色。

💡 核心启示: 技术卓越和人员领导力需要不同的技能,在一方面不称职并不自动具备另一方面的资格。管理岗位不应是"失败者的收容所"。


三、规划

Planning — 6 条定律

项目规划是软件工程中最具挑战性的环节之一,以下定律揭示了时间估算和目标设定的本质困难。


19. 过早优化

Premature Optimization / Knuth's Optimization Principle

过早优化是万恶之源。

Donald Knuth 在 1974 年的论文中写道:"我们应该在约 97% 的时间里忘记小的效率问题:过早优化是万恶之源。然而,我们不应放弃那关键 3% 的机会。"他并非字面意义上说所有邪恶,而是提醒开发者:先让它工作,再让它正确,最后(如果需要)让它快。

💡 核心启示: 大多数代码并不运行在性能关键的热点上。优化应该基于实际性能分析,而非猜测。过早优化的代码往往更复杂、更难读、更难维护。


20. 帕金森定律

Parkinson's Law

工作会膨胀,直到填满所有可用时间。

Cyril Parkinson 在 1955 年发现:如果给开发者两周时间完成一个两天就能做完的任务,工作通常会慢下来,消耗掉大部分时间。人们可能花更多时间规划、纠结细节或干脆拖延到最后一刻。

💡 核心启示: 适度紧迫的截止日期可以对抗帕金森定律,创造紧迫感。过长的期限反而导致"镀金"——添加并非必要的改进,仅仅为了用完所有时间。


21. 九十-九十法则

The Ninety-Ninety Rule

前 90% 的代码占用了前 90% 的开发时间;剩下的 10% 占用了另外 90% 的开发时间。

Tom Cargill 在 Bell Labs 提出的这条法则量化了项目在最后阶段卡住的程度。团队在项目初期快速构建核心功能,产生乐观情绪。然后集成、边界情况、性能调优和 bug 修复消耗了意外大量的时间。

💡 核心启示: 看起来像"收尾工作"的部分实际上涉及许多棘手问题和未知数。达到"几乎完成"状态可能只意味着在时间上完成了一半。


22. 侯世达定律

Hofstadter's Law

它总是比你预期的要长,即使你考虑了侯世达定律。

Douglas Hofstadter 在 1979 年的《哥德尔、艾舍尔、巴赫》中提出这条递归定律,捕捉了估算的悖论。无论你多么有经验,项目总是倾向于延期。即使你说"这可能比我预期的长 2 倍",它可能还是会花 3 倍的时间。

💡 核心启示: 人类普遍不擅长估算复杂任务的耗时,即使知道这一点也依然如此。计划中应包含应急缓冲,但即使这些缓冲也常常被消耗殆尽。不要过度乐观。


23. 古德哈特定律

Goodhart's Law

当一个衡量指标成为目标时,它就不再是一个好的衡量指标。

经济学家 Charles Goodhart 提出的定律在软件团队中极为相关。例如,管理者设定"本月必须关闭 100 个 bug 工单"的目标,开发者可能会关闭未真正解决的工单或拆分单个 bug 为多个工单来达标。

💡 核心启示: 指标是所重视事物的代理。一旦指标变成目标,人们就会找到方式优化该指标,即使这会损害原始意图。应该结合多个指标并辅以定性判断,避免单一聚焦。


24. 吉尔布定律

Gilb's Law

任何你需要量化的东西,都可以用某种方式衡量,且总比不衡量好。

Tom Gilb 的这一定律是对古德哈特定律可能导致瘫痪的回应。即使是不精确或间接的衡量,也比完全没有好。当某件事至关重要(性能、客户满意度、代码可维护性),你应该尝试衡量它,否则你就是在盲目行事。

💡 核心启示: 从基础衡量开始,随时间逐步优化。衡量的行为本身就能帮助团队聚焦和识别趋势。近似或间接的衡量也好过没有衡量。不要因为古德哈特定律就完全抛弃指标。


四、质量

Quality — 11 条定律

代码质量是软件长期健康的基础,以下定律为质量保障提供了多维度的思考。


25. 童子军法则

The Boy Scout Rule

让代码比你发现它时更好。

这条法则源自童子军的信条"让营地比你发现时更干净"。在软件中,它不是要求大规模重写,而是倡导持续的、渐进的改进。每当开发者遇到一段代码——无论是添加功能、修复 bug 还是代码审查——都应该至少做一件小改进:重命名一个变量、简化一段逻辑、添加一条注释。

💡 核心启示: 不要让坏东西在项目中腐烂。更干净的代码库更易读、更易维护。当每个开发者都遵循这条规则,就创造了一种代码所有权的文化。


26. 墨菲定律

Murphy's Law / Sod's Law

凡是可能出错的事,一定会出错。

在软件中,空指针、竞态条件、网络中断——只要可能出现的问题,最终都会在大规模用户基数下显现,而且往往是在最糟糕的时间。这条定律鼓励开发者进行防御性编码:添加错误处理、备份和检查。

💡 核心启示: 如果一个错误可能发生,它就会发生。为边界情况编写测试,添加错误处理和检查机制。


27. 波斯泰尔定律

Postel's Law

对自己发送的内容要保守,对接收的内容要宽容。

Jon Postel 在 1980 年的 TCP 规范中写下这条鲁棒性原则。如果你的服务器发送 HTTP 响应,应该严格按照规范格式化;但如果接收到格式不完全标准的请求,只要能安全解读,就应该处理而非拒绝连接。

💡 核心启示: 发送数据时严格遵循协议和标准;接收数据时尽可能容忍偏差和异常。但在现代,过度宽容有时会掩盖错误,因此需要与安全考量相平衡。


28. 破窗理论

Broken Windows Theory

不要留下未修复的破窗(糟糕的设计、错误的决策或糟糕的代码)。

破窗理论源自犯罪学,Andy Hunt 和 Dave Thomas 在《程序员修炼之道》中将其引入软件领域。在城市中,一扇未修复的破窗暗示着疏于管理,会招来更多破坏。在代码中,一个明显的 bug 或混乱的代码段如果不处理,会让更多开发者绕过流程,导致代码质量持续恶化。

💡 核心启示: 在问题还小的时候就修复它们。重构破坏性代码、更新过时文档,防止代码健康度的下行螺旋。干净的代码库鼓励工程师保持干净。


29. 技术债务

Technical Debt

技术债务是在开发软件时减慢我们速度的一切。

Ward Cunningham 在 1992 年的 OOPSLA 会议上创造了这个金融隐喻。当你在代码中走捷径,你就向未来借了时间——获得即时收益,但欠下了本金(修复工作)加上利息(因代码粗糙而额外花费的时间)。

💡 核心启示: 技术债务不还,利息会不断累积。并非所有技术债务都是坏事——有时为了市场时机或原型验证是必要的。偿还技术债务的方式是重构代码、补充缺失的测试和改进设计。


30. 李纳斯定律

Linus's Law

给定足够多的眼睛,所有 bug 都是浅显的。

Eric S. Raymond 在《大教堂与集市》中这样描述 Linux 的开源开发模式。当许多人使用和审查一个软件时,问题对某个人来说就变得明显了。对你来说困惑的 bug,对另一个程序员可能轻而易举就能发现。

💡 核心启示: 同行审查和社区的力量是软件质量的重要保障。开源软件的优势在于积累了大量贡献者的智慧。但前提是那些"眼睛"真的在看——仅仅开源并不能自动修复 bug。


31. 柯尼汉定律

Kernighan's Law

调试的难度是编写代码的两倍。

Brian Kernighan 指出:编码时,你带着特定的心理模型和完整上下文;调试时,你可能面对的是别人的代码或自己已经遗忘的代码。如果你在编写代码时已经用尽了全部智力,那你将无法理解或排查它。

💡 核心启示: 简单的代码加上良好的结构和文档更容易调试,长远来看节省时间。永远不要写出超越自己理解能力的代码。


32. 测试金字塔

Testing Pyramid

一个项目应该有大量快速的单元测试、较少的集成测试和少量的 UI 测试。

Mike Cohn 在 2009 年左右推广了测试金字塔的概念。最底层是单元测试(快速且数量最多),中间层是集成测试(数量较少),顶层是端到端/UI 测试(最少但最接近真实场景)。越往上,测试在时间、精力和脆弱性上的成本越高。

💡 核心启示: 单元测试是基础——快速、独立、大量。集成测试验证模块协作。端到端测试模拟真实用户场景,不可或缺但代价高昂。按此组织测试,你将获得快速反馈,且当 UI 测试失败时更有可能是真实问题。


33. 杀虫剂悖论

Pesticide Paradox

反复运行相同的测试,效果会随时间递减。

Boris Beizer 提出的这个类比来自农业:反复使用同一种杀虫剂,害虫会产生抗药性。在软件中,新的测试套件首次运行可能发现很多 bug,修复后继续运行相同的测试就不再发现新问题——不是因为软件完美,而是因为测试覆盖的路径已经被修复。

💡 核心启示: 过时的测试套件识别新 bug 的能力会下降。需要不断注入新的测试数据、扩展新功能的测试、尝试新的输入组合或探索新的边界条件,"刷新杀虫剂"以发现新 bug。


34. 莱曼软件演化定律

Lehman's Laws of Software Evolution

反映真实世界的软件必须演化,且这种演化有可预测的限度。

Manny Lehman 在 1974 年基于对 IBM OS/360 的研究总结出这些定律。持续变更定律指出,如果系统不持续更新以满足新需求,用户满意度会下降。复杂度递增定律指出,变更不断累积使系统更加复杂,除非开发者投资重构。

💡 核心启示: 成功的软件永远不会"完成"。必须持续投入维护和重构,否则复杂度会不可控地增长,开发速度会持续下降。


35. 斯特金定律

Sturgeon's Law

百分之九十的一切都是垃圾。

科幻作家 Theodore Sturgeon 在 1957 年回应"90% 的科幻小说都是垃圾"的批评时说:"90% 的一切都是垃圾。"在软件领域,这意味着大部分代码或功能可能并不必要,只有少数驱动着真正的价值。

💡 核心启示: 这是帕累托法则的极端形式。关键挑战是找到最大化那高影响力 10% 的方式。大多数新概念或技术无法兑现承诺,只有卓越的才能脱颖而出。


五、设计

Design — 6 条定律

好的设计让代码易于理解、扩展和维护,以下定律是软件设计的基石。


36. 你不会用到它

YAGNI — You Aren't Gonna Need It

不要在必要时之前添加功能。

YAGNI 是极限编程(XP)的核心哲学之一。如果你在实现模块 A 时想"将来可能需要模块 B 来做 X,不如现在就写些钩子",YAGNI 建议你放弃这个念头。因为将来可能不需要,即使需要,需求也很可能与预期不同,而你提前写的代码现在就成了维护负担。

💡 核心启示: YAGNI 鼓励迭代开发——实现最小化方案,在需要时再精炼或扩展。预期未来需求往往导致过度工程化,增加复杂度和维护负担。


37. 不要重复自己

DRY — Don't Repeat Yourself

每一块知识都必须有单一的、明确的、权威的表示。

Andy Hunt 和 Dave Thomas 在 1999 年的《程序员修炼之道》中定义了 DRY 原则。如果你在多个模块中发现相同的代码、公式或规则,你就在违反 DRY。当业务规则变更时,你必须找到每一处重复并逐一修改,遗漏任何一处都会导致不一致。

💡 核心启示: DRY 是关于知识重复而非代码重复。有时两段看起来相似的代码做着不同的事,强行合并反而会过度复杂化。应用 DRY 时要明智。


38. 保持简单

KISS — Keep It Simple, Stupid

设计和系统应尽可能简单。

KISS 原则源自美国军方,由洛克希德臭鼬工厂的首席工程师 Kelly Johnson 在 1960 年代提出。如果一个问题可以用 50 行脚本解决,就不要用 500 行的复杂方案。每一行代码都有出错的可能,简洁的解决方案更易理解、调试和维护。

💡 核心启示: 简洁的代码更容易理解、更快调试,即使是未来的你也更容易理解。聪明的代码乍看之下令人印象深刻,但往往掩盖问题。任何不必要地增加复杂度的东西都违背了 KISS 原则。


39. SOLID 原则

SOLID Principles

五条增强软件设计的主要指导原则,使代码更可维护和可扩展。

SOLID 是五个面向对象设计原则的首字母缩写:单一职责原则(SRP,一个类只关注一个职责)、开闭原则(OCP,对扩展开放,对修改关闭)、里氏替换原则(LSP,子类必须能替换其父类)、接口隔离原则(ISP,不应强迫客户端依赖它不使用的接口)和依赖反转原则(DIP,依赖抽象而非具体实现)。

💡 核心启示: 遵循 SOLID 可以产出更易扩展、测试和重构的软件,而不破坏现有功能。每个原则解决了面向对象设计的不同方面,协同运用时效果最佳。


40. 迪米特法则

Law of Demeter

一个对象应该只与其直接的朋友交互,而不与陌生人交互。

迪米特法则也被称为"最少知识原则"。如果对象 A 调用对象 B 的方法,而 B 又调用 C 的方法(如 a.getB().getC().doSomething()),那就是在违反这条法则——A 对 C 的存在和接口产生了不必要的依赖。

💡 核心启示: 减少对象之间的耦合。每个模块应该只了解它直接协作的对象的内部结构,而不是整个对象图的细节。这提高了封装性和可维护性。


41. 最小惊奇原则

Principle of Least Astonishment

软件和界面应该以最少让用户和其他开发者感到惊讶的方式行事。

如果一个方法叫 getDay(),它应该返回星期几,而不是返回一年中的第几天。按钮标签为"删除"就应该删除内容,而不是先弹出一个不相关的向导。函数的行为应该与其名称和上下文所暗示的一致。

💡 核心启示: 一致性和可预测性是优秀 API 和 UI 设计的标志。当行为与直觉相符时,用户和学习代码的开发者都不需要反复查阅文档。


六、扩展

Scale — 3 条定律

当系统需要处理更大的规模时,以下定律揭示了扩展的机遇与限制。


42. 阿姆达尔定律

Amdahl's Law

并行化的加速比受限于不可并行化的工作量比例。

Gene Amdahl 在 1967 年提出:当你增加 CPU 核心时,只有可并行化的代码部分会加速,串行部分保持不变并最终主导总执行时间。如果串行比例为 s,无限并行资源的最大加速比为 1/s——如果 10% 是串行的,最大加速比就是 10 倍。

💡 核心启示: 串行工作是天花板,再多并行也无法突破。先修复再扩展:先减少串行路径,并行化其次。这个定律也适用于团队——决策瓶颈在大规模团队中往往主导一切。


43. 古斯塔夫森定律

Gustafson's Law

通过增加问题规模,可以在并行处理中实现显著的加速。

John Gustafson 在 1988 年提出了一种更乐观的视角。阿姆达尔定律假设问题规模固定,得出加速比受限于串行工作的悲观结论;古斯塔夫森定律则改变视角——当更多处理器可用时,开发者通常处理更大的问题而非更快地解决同样的问题。

💡 核心启示: 随着计算资源增长,可以在同等时间内计算更多问题,而非仅仅更快地解决同样的问题。软件和算法应设计为"横向扩展"——随着处理器核心或机器增加,可完成的工作量也应相应增长。


44. 梅特卡夫定律

Metcalfe's Law

网络的价值与用户数量的平方成正比。

Robert Metcalfe 在 1980 年左右提出这个概念。5 个节点最多可有 10 对连接,12 个节点则有 66 对。用户翻倍,潜在连接数大致翻四倍。这解释了为什么社交媒体和消息平台在达到临界点后可以爆发式增长。

💡 核心启示: 网络效应的力量:每个新用户通过创造新的交互机会为现有用户增加价值。一旦达到临界点,产品或平台的价值将呈指数级增长。


七、决策

Decisions — 12 条定律

决策是软件工程的核心活动,以下定律和思维模型帮助你更清醒地思考。


45. 达克效应

Dunning-Kruger Effect

你对某件事了解越少,就越自信。

Dunning-Kruger 效应描述了一个反直觉的现象:初学者往往过度自信,因为他们不知道自己不知道什么;而专家反而更谨慎,因为他们了解领域的复杂性。在软件工程中,刚学完一个框架的新手可能觉得自己什么都能做,而资深工程师面对同样的任务会更谨慎评估风险。

💡 核心启示: 保持谦逊,持续学习。当你觉得自己对某个领域非常自信时,可能恰恰是你最需要深入了解的时候。


46. 汉隆剃刀

Hanlon's Razor

永远不要将可以用愚蠢或粗心来解释的事情归咎于恶意。

在代码审查或团队协作中,当你发现一个糟糕的设计决策时,不要假设对方是故意搞破坏。更可能的是缺乏经验、疏忽或信息不对称。以善意的假设为起点,可以避免不必要的冲突,促进建设性的讨论。

💡 核心启示: 在团队合作中,假设善意的意图。大多数问题源于沟通不畅或知识差距,而非恶意。


47. 奥卡姆剃刀

Occam's Razor

最简单的解释往往是最准确的。

在排查 bug 时,如果有一个简单的解释和一个复杂的解释,应该优先考虑简单的那个。是缓存没有失效?还是数据库集群发生了脑裂?大多数时候,问题出在最简单的原因上。

💡 核心启示: 当多个假设都能解释同一现象时,选择假设最少的那个。在技术决策中,简单的方案应该被优先考虑。


48. 沉没成本谬误

Sunk Cost Fallacy

因为已经投入了时间或精力而坚持一个选择,即使放手更有利。

项目已经开发了 6 个月,虽然方向明显错误,但因为投入太多而不愿放弃。或者坚持使用一个不合适的技术栈,仅仅因为团队已经写了大量代码。沉没成本谬误让决策被过去的投入绑架,而非面向未来做最优选择。

💡 核心启示: 已投入的成本无法收回。决策应基于未来的预期收益和成本,而非过去的投入。及时止损是一种智慧。


49. 地图不是疆域

The Map Is Not the Territory

我们对现实的表示,不等于现实本身。

模型、文档、架构图、UML、API 规范——这些都是"地图",它们简化并抽象了真实的系统。但地图总会遗漏某些细节,而那些被遗漏的细节在关键时刻可能至关重要。不要把模型与真实系统混为一谈。

💡 核心启示: 文档和设计图是必要的导航工具,但它们永远无法完全替代对真实系统的理解。在依赖模型做决策时,要意识到模型的局限性。


50. 确认偏误

Confirmation Bias

倾向于偏袒支持我们既有信念或观点的信息。

当你在评估两个技术方案时,如果你已经偏好方案 A,你会不自觉地搜索支持 A 的论据,忽略支持 B 的证据。确认偏误让决策过程变成自我验证而非客观评估。

💡 核心启示: 在做重要技术决策时,主动寻找反对自己观点的证据。让持不同意见的人参与讨论,对抗确认偏误的倾向。


51. 技术成熟度曲线与阿马拉定律

The Hype Cycle & Amara's Law

我们倾向于高估技术的短期影响,而低估其长期影响。

Roy Amara 的这一定律完美描述了技术炒作的周期。一项新技术出现时,媒体和投资者疯狂追捧(期望膨胀期),然后发现它并没有立即改变世界(泡沫破裂期),最终在默默无闻中稳步发展,多年后才真正改变行业(稳步爬升期)。区块链、AI、微服务都经历过这个周期。

💡 核心启示: 对新技术保持理性。不要被短期炒作冲昏头脑,也不要在泡沫破裂时完全否定。真正有价值的技术的长期影响往往远超最初预期。


52. 林迪效应

The Lindy Effect

某样东西使用的时间越长,它继续被使用的可能性就越大。

林迪效应指出,对于不会自然老化的事物(如技术、书籍、理念),其未来预期寿命与其当前存在时间成正比。一门存在了 30 年的编程语言(如 C)比一门刚出现 3 年的语言更有可能再存在 30 年。

💡 核心启示: 在技术选型中,被时间验证的技术通常更可靠。选择成熟的技术栈不是保守,而是理性。这并不意味着拒绝新事物,而是在关键系统中给予经过时间考验的技术更多权重。


53. 第一性原理思维

First Principles Thinking

将复杂问题分解为最基础的构建模块,然后从那里重新构建。

第一性原理思维要求你放下现有的假设和惯例,回到最基本的真理,从零开始推理。与其问"别人是怎么做的",不如问"从根本上看,这个问题需要什么"。埃隆·马斯克在降低火箭成本时用的就是这个方法——不参考现有火箭价格,而是计算原材料的基础成本。

💡 核心启示: 当面对复杂问题时,不要被现有方案束缚。回到基本原理,从根本出发重新思考,可能发现全新的解决方案。


54. 逆向思维

Inversion

通过考虑相反的结果并从那里倒推来解决问题。

逆向思维不是问"如何让系统更快",而是问"什么会让系统变慢"。不是问"如何写出好代码",而是问"什么会导致糟糕的代码"。通过思考你不想要的结果,然后避免那些导致它的因素,你往往能找到更清晰的前进路径。

💡 核心启示: 当正面思考陷入困境时,试着翻转问题。避免失败的策略有时比追求成功的策略更有效。


55. 帕累托法则

Pareto Principle / 80·20 Rule

80% 的问题源于 20% 的原因。

在软件工程中,80% 的 bug 可能来自 20% 的代码;80% 的用户可能只使用 20% 的功能;80% 的性能问题可能集中在 20% 的热点代码。识别那关键的 20%,你就能以最小的投入获得最大的改善。

💡 核心启示: 聚焦高影响力的少数因素。在时间有限的情况下,优先解决那 20% 的问题,而不是试图面面俱到。


56. 坎宁安定律

Cunningham's Law

在互联网上获得正确答案的最佳方式不是提问,而是发布一个错误答案。

Ward Cunningham 观察到,人们纠正错误的意愿远高于回答问题的意愿。如果你在论坛上问"这个 API 怎么用",可能没人理你;但如果你自信地发布一个错误的用法,立刻就有人跳出来纠正你。

💡 核心启示: 在知识社区中,有时提出一个假设(即使可能是错的)比直接提问更能激发讨论和获得正确答案。这也是一种低成本的验证策略。


总结

Summary

这 56 条软件工程定律构成了一个丰富的思维工具箱:

类别英文定律数量核心关注点
架构Architecture9系统设计的边界与权衡
团队Teams9人与组织对软件的影响
规划Planning6时间估算与目标设定的陷阱
质量Quality11代码质量的维护与保障
设计Design6简洁与可维护的设计原则
扩展Scale3系统扩展的机遇与限制
决策Decisions12更清醒的思考与判断

这些定律不是教条,而是地图。正如第 49 条定律所说——地图不是疆域,这些定律是对现实的抽象和简化,它们帮助你导航,但永远不能替代你面对真实问题时的独立思考。在恰当的情境中灵活运用这些定律,才是真正的工程智慧。


参考来源:Laws of Software Engineering by Dr. Milan Milanović