软件设计原则 · 26 条设计准则 · 七组分类
Java Design Patterns · Principles

软件设计原则:26 条写给开发者的设计准则

把 java-design-patterns.com 的「编程原则」页整理成一份中文速览:按简洁、解耦、内聚、扩展、契约、质量、定律七组归类,每条给出核心、为什么与怎么做,并保留原文出处便于回溯。它们不是教条,而是判断设计取舍时的常用透镜。

来源:java-design-patterns.com/zh/principles 条目:26 条 分组:7 类 整理:2026-09-21

00导读与分组

本文整理自 java-design-patterns.com/zh/principles/(其材料源自 webpro/programming-principles)。原页按字母顺序平铺了 26 条原则,本文按主题重新归为七组,便于成体系地阅读与检索。

阅读立场:原则之间存在张力,且几乎都有代价。例如「DRY」要求消除重复,但盲目抽象会把巧合的重复焊死成错误耦合;「鲁棒性」要求宽容接收,但过度宽容会掩盖调用方的错误(与隐式接口定律冲突)。请把它们当作权衡的起点,而不是必须遵守的规则。

七组分类(共 26 条)

简洁与直接 3 条 分离与解耦 6 条 内聚与职责 4 条 扩展与替换 3 条 契约与协作 3 条 质量与维护 4 条 工程定律 3 条
图 1 · 七组分布。可见该页重心在「分离与解耦」(6 条)——降低模块间的相互依赖,是这批判则最集中的主题。
组 主题 条数 包含
一 简洁与直接 3 KISS · YAGNI · 做最简单可行之事
二 分离与解耦 6 关注点分离 · 最小化耦合 · 得墨忒耳 · 组合优于继承 · 正交性 · 隐藏实现细节
三 内聚与职责 4 最大化内聚 · 单一职责 · 柯里定律 · 接口隔离
四 扩展与替换 3 开闭原则 · 里氏替换 · 封装变化
五 契约与协作 3 鲁棒性原则 · 控制反转 · 命令查询分离
六 质量与维护 4 避免过早优化 · DRY · 为维护者编码 · 童子军规则
七 工程定律 3 墨菲定律 · 布鲁克斯法则 · 林纳斯定律

01简洁与直接

用最少的机制表达最直接的意图——复杂度是成本,不是能力证明。

P01保持简单KISS — Keep It Simple, Stupid

大多数系统,在保持简单而非刻意复杂时,运行得最好。

为什么
  • 更少的代码:写得更快、缺陷更少、更容易修改。
  • 简单本身即终极的精致。
  • 完美不在于无可增添,而在于无可删减。

原文:KISS · 参见 Wikipedia

P02你不会需要它YAGNI — You Aren't Gonna Need It

在确有必要之前,不要实现任何功能。

为什么
  • 为「明天才需要的功能」投入,等于挤占当前迭代真正该做的功能的时间。
  • 它会带来代码膨胀,软件变得更大、更复杂。
怎么做
  • 只在实际需要时实现,绝不在「预见可能需要」时就实现。

原文:YAGNI · 参见 Wikipedia

P03做最简单可行之事Do The Simplest Thing That Could Possibly Work

只做真正解决问题所需要的最简方案——来自极限编程(XP)的传统准则。

为什么
  • 只有直面真正的问题本身,针对真实问题的进展才会最大化。
怎么做
  • 问自己:「能行的最简单做法是什么?」

原文:Do The Simplest Thing…

02分离与解耦

本组是原页的重心(6 条)。核心只有一个:让「不该相互影响的东西」彼此无关,使变更的影响面可控。

P04关注点分离Separation of Concerns

把程序拆成各司其职的部分,每一部分只处理一个关注点——例如「业务逻辑」与「用户界面」是两个关注点:改界面不应要求改业务逻辑,反之亦然。

为什么
  • 简化软件的开发与维护。
  • 当关注点分离良好时,各部分可被独立复用、独立开发与更新。
怎么做
  • 把程序功能拆分进彼此重叠尽可能少的模块。

Dijkstra(1974)称之为「有效组织思维的唯一切实手段」。原文:Separation of Concerns

P05最小化耦合Minimise Coupling

耦合是模块之间相互依赖的程度——代码单元 A 变更后,代码单元 B「坏掉」的概率高低。耦合越低越好。

为什么
  • 一个模块的改动往往引发其他模块的连锁修改。
  • 模块组装因依赖增多而更费力、更耗时。
  • 某个模块更难复用、更难测试(必须把依赖的模块一并带上)。
  • 开发者不敢改代码,因为不确定会波及什么。
怎么做
  • 消除、减少必要关系的复杂度。
  • 通过隐藏实现细节来降低耦合。
  • 应用得墨忒耳定律(见 P06)。

原文:Minimise Coupling

P06得墨忒耳定律Law of Demeter — Don't Talk to Strangers

「别和陌生人说话。」一个对象只应与直接协作方交互,避免 a.b.c.d 式的链式调用。

为什么
  • 链式调用通常收紧耦合。
  • 它可能暴露过多实现细节。
怎么做
  • 对象的方法只可调用以下四类对象的方法:① 对象自身;② 方法的入参;③ 方法内创建的对象;④ 对象的直接属性/字段。

原文:Law of Demeter

P07组合优于继承Composition Over Inheritance

优先用组合(持有并委托)表达复用,而非继承。

为什么
  • 类之间耦合更少。
  • 使用继承时,子类容易做出错误假设,从而破坏里氏替换原则(LSP)。
怎么做
  • 用 LSP(可替换性)测试来决定是否该继承。
  • 「有一个/用一个(has-a / uses-a)」关系用组合;「是一个(is-a)」关系才用继承。

原文:Composition Over Inheritance

P08正交性Orthogonality

概念上不相关的事物,在系统中也不应彼此相关。

说明
  • 正交性与简单性相关:设计越正交,例外越少,程序越易学、易读、易写。
  • 正交特性的含义与上下文无关,其关键参数是对称与一致。

原文:Orthogonality

P09隐藏实现细节Hide Implementation Details

模块通过提供接口来隐藏信息(实现细节),不泄露任何不必要的信息。

为什么
  • 当实现变化时,客户端所依赖的接口无需随之改变。
怎么做
  • 尽量缩小类与成员的可访问性。
  • 不要把成员数据公开。
  • 不要把私有实现细节放进类的接口。
  • 通过降低耦合,来隐藏更多实现细节。

原文:Hide Implementation Details

03内聚与职责

与「解耦」相对的另一半:让「本就该在一起的东西」牢牢聚在一处,并让它只有一个变更理由。

P10最大化内聚Maximise Cohesion

模块的内聚,是其各项职责构成一个有意义的整体的程度。内聚越高越好。

为什么(低内聚的代价)
  • 模块更难被理解。
  • 系统更难维护:领域逻辑变更会牵动多个模块,一个模块的改动又牵连相关模块。
  • 模块更难复用:多数应用并不需要它提供的那些杂乱无章的操作集合。
怎么做
  • 把共享同一职责的相关功能归入一处(例如同一个类)。

原文:Maximise Cohesion

P11单一职责原则SRP — Single Responsibility Principle

一个类永远不应有超过一个变更理由。

展开
  • 每个类应只有一个职责,且该职责应被这个类完整封装。所谓「职责」,可定义为「一个变更理由」——因此一个类或模块应当有且仅有一个变更理由。
为什么
  • 可维护性:变更只需发生在一个模块或类之中。
怎么做
  • 应用柯里定律(见 P12)。

原文:Single Responsibility Principle

P12柯里定律Curly's Law — Do One Thing

为任何一段代码选定唯一、清晰的目标:只做一件事(Do One Thing)。

原文:Curly's Law

P13接口隔离原则ISP — Interface Segregation Principle

把「胖接口」拆成多个更小、更贴近具体客户端的接口。接口应更依赖「调用它的代码」,而非「实现它的代码」。

为什么
  • 如果一个类实现了根本用不到的方法,调用方就不得不了解该方法的实现(例如某方法只是抛异常,调用方还得额外知道「它不该被调用」)。
怎么做
  • 避免胖接口;类永远不应被迫实现违反单一职责原则的方法。

原文:Interface Segregation Principle

04扩展与替换

如何在不改坏既有代码的前提下延展系统:对扩展开放、让子类型可替换、把变化点封装起来。

P14开闭原则OCP — Open/Closed Principle

软件实体(如类)应当对扩展开放、对修改关闭——无需改动源码,即可修改其行为。

为什么
  • 通过最小化对既有代码的改动,提升可维护性与稳定性。
怎么做
  • 写出「可被扩展」的类,而不是「可被修改」的类。
  • 只暴露需要变化的部分,其余一律隐藏。

原文:Open/Closed Principle

P15里氏替换原则LSP — Liskov Substitution Principle

程序中的对象,应能被其子类型的实例替换,而不改变该程序的正确性。

说明
  • LSP 讨论的是对象的预期行为:子类可以改变实现,但不能破坏父类向调用方承诺的语义(因此「正方形继承长方形」是典型反例)。

原文:Liskov Substitution Principle

P16封装变化Encapsulate What Changes

好的设计能识别出最可能变化的热点,并把它们封装在 API 之后;当预期的变化发生时,修改被限制在局部。

为什么
  • 最小化变化发生时所需要做的修改。
怎么做
  • 把「会变化的概念」封装在 API 之后。
  • 必要时,把该变化的概念拆分为独立模块。

原文:Encapsulate What Changes

05契约与协作

模块之间如何「打交道」:接收输入与发送输出的宽严尺度、控制权归属、以及方法与副作用的界限。

P17鲁棒性原则Robustness Principle — Postel's Law

对自己所做的要保守,对他人所给的要宽容。

为什么
  • 接口需要演进,接收方常会拿到未按规格发送的数据。要让服务能够演进,就必须保证提供方在支持新需求时,对既有客户端的破坏最小。
怎么做
  • 发送命令/数据的代码应完全符合规范;接收输入的代码只要含义清晰,就应接受不完全符合规范的输入。
边界提示
  • 宽容需有度:无原则地接受非法输入,会掩盖发送方的错误(与「隐式接口定律」相冲突)。现代实践更强调「对非法输入明确拒绝」。

原文:Robustness Principle

P18控制反转Inversion of Control — Hollywood Principle

又称「好莱坞原则」——别找我们,我们会找你。定制代码从通用框架那里获取控制流;可复用代码与问题特定代码即使协同工作,也各自独立开发。

为什么
  • 提升程序的模块化与可扩展性。
  • 把「任务的执行」与「具体实现」解耦。
  • 让模块聚焦于它被设计来做的事。
  • 让模块摆脱「其他系统如何做」的假设,转而依赖契约。
  • 替换某个模块时避免副作用。
怎么做(常见实现手段)
  • 工厂模式(Factory)· 服务定位器(Service Locator)· 依赖注入(DI)
  • 上下文查找(contextualized lookup)· 模板方法(Template Method)· 策略模式(Strategy)

原文:Inversion of Control

P19命令查询分离CQS — Command Query Separation

每个方法要么是执行动作的命令,要么是返回数据的查询,二者不可兼得——提问不应改变答案。

为什么
  • 把方法明确分成查询与命令后,程序员无需了解每个方法的实现细节,也能更有把握地编写调用代码:查询可任意顺序、任意位置调用(不改状态),命令则需更谨慎。
怎么做
  • 把每个方法实现为查询或命令,二者只居其一。
  • 用命名约定暗示该方法究竟是查询还是命令。

原文:Command Query Separation

06质量与维护

代码会被读的次数远多于被写的次数——本组四条都指向同一个现实:维护才是主战场。

P20避免过早优化Avoid Premature Optimization

在知道瓶颈之前就优化,是浪费甚至有害的。高德纳:约 97% 的情况下,「过早优化是万恶之源」。

为什么
  • 一开始根本不知道瓶颈在哪里。
  • 优化之后的代码可能更难读,因而更难维护。
怎么做
  • 先让它能工作,再让它正确,最后才让它快(Make It Work → Make It Right → Make It Fast)。
  • 不到必要时不优化;只有在性能剖析(profiling)发现瓶颈后,才针对该瓶颈优化。

原文:Avoid Premature Optimization

P21不要重复自己DRY — Don't Repeat Yourself

系统中每一处知识,都应有唯一、无歧义、权威的表达。

为什么
  • 重复(无论有意还是无意)会导致维护噩梦、糟糕的分解与逻辑矛盾。
  • 修改系统中任一元素时,无需改动其他逻辑上无关的元素。
  • 逻辑相关的元素会一致、可预期地共同变化,从而保持同步。
怎么做
  • 业务规则、长表达式、if 语句、数学公式、元数据等,只放一处。
  • 找出系统中每处知识的唯一定义源,再由它生成相应的实例(代码、文档、测试……)。
  • 应用「三次法则」(Rule of Three):重复到第三次时才抽象。

原文:Keep things DRY

P22为维护者编码Code For The Maintainer

维护是任何项目中最昂贵的阶段——所以代码要为后来接手的人而写。

怎么做
  • 去成为那个维护者(换位思考)。
  • 「始终假设最终维护你代码的人,是一个知道你家在哪儿的暴力精神病。」
  • 以「比你低几级的同事也能愉悦地阅读并从中学习」的方式编写代码与注释。
  • 别让接手者思考(Don't make me think)。
  • 遵循「最小惊讶原则」(Principle of Least Astonishment)。

原文:Code For The Maintainer

P23童子军规则Boy-Scout Rule

「离开营地时,让它比你来时更干净。」——每次改动,都应让代码比被发现时更干净。

为什么
  • 改动既有代码库时,代码质量天然会退化、累积技术债。遵循童子军规则,就是每次提交都留意质量;靠持续重构(无论多小)来抵抗技术债。
怎么做
  • 每次提交都确保它不降低代码库质量。
  • 任何时候看到不够清晰的代码,就顺手把它改好。

原文:Boy-Scout Rule

07工程定律

三条跨越具体技术、描述「人与项目」客观规律的法则。原页把它们列在原则页中,本质上是不可违背的经验规律。

P24墨菲定律Murphy's Law

凡可能出错的,终将出错。

说明
  • 只要存在哪怕最微小的出错可能,最终它就会出错——若把无穷多次试验与概率放在一起看,这完全说得通。该定律同样适用于软件开发:把「异常路径」当作一等公民来设计。

原文:Murphy's Law

P25布鲁克斯法则Brooks's Law

向一个已经延期的软件项目增加人手,只会让它更晚完成。

说明
  • 出自 Fred Brooks《人月神话》。要点在于:新增开发者不会立即可用、立即产出,反而因沟通开销占用团队其他成员的时间。

原文:Brooks's Law

P26林纳斯定律Linus's Law

足够多的眼睛,就能让所有缺陷都变得浅显。

说明
  • 出自 Eric S. Raymond《大教堂与集市》,以 Linux 之父命名。它是对代码评审过程的赞誉:在代码被接纳合并之前,由多位开发者共同审查。前提是代码可读——与「柯林汉定律」互为约束。

原文:Linus's Law

08速查表

26 条一句话速览,按分组排列。

编号 中文名 英文名 一句话
P01 保持简单 KISS 系统在保持简单时运行得最好。
P02 你不会需要它 YAGNI 必要之前不要实现。
P03 做最简单可行之事 Do The Simplest Thing 只做真正解决问题的最简方案。
P04 关注点分离 Separation of Concerns 每个部分只处理一个关注点。
P05 最小化耦合 Minimise Coupling 模块间相互依赖越低越好。
P06 得墨忒耳定律 Law of Demeter 别和陌生人说话,避免链式调用。
P07 组合优于继承 Composition Over Inheritance 用组合复用,少用继承。
P08 正交性 Orthogonality 不相关的事物在系统中也不应相关。
P09 隐藏实现细节 Hide Implementation Details 只暴露接口,不泄露实现。
P10 最大化内聚 Maximise Cohesion 模块职责应构成有意义的整体。
P11 单一职责原则 SRP 一个类只有一个变更理由。
P12 柯里定律 Curly's Law 每段代码只做一件事。
P13 接口隔离原则 ISP 拆胖接口,不强迫依赖无用方法。
P14 开闭原则 OCP 对扩展开放,对修改关闭。
P15 里氏替换原则 LSP 子类型可替换基类型而不破坏正确性。
P16 封装变化 Encapsulate What Changes 把易变点封装在 API 之后。
P17 鲁棒性原则 Postel's Law 发送保守,接收宽容。
P18 控制反转 Inversion of Control 别找我们,我们会找你。
P19 命令查询分离 CQS 方法非命令即查询,不可兼得。
P20 避免过早优化 Avoid Premature Optimization 先跑通,profiling 后再优化。
P21 不要重复自己 DRY 每处知识只有一个权威表达。
P22 为维护者编码 Code For The Maintainer 维护是最贵阶段,为接手者写。
P23 童子军规则 Boy-Scout Rule 每次提交让代码更干净一点。
P24 墨菲定律 Murphy's Law 凡可能出错的终将出错。
P25 布鲁克斯法则 Brooks's Law 给延期项目加人只会更晚。
P26 林纳斯定律 Linus's Law 足够多的眼睛,缺陷皆浅显。