【译】架构设计原则
设计应用场景
通用
- KISS原则(保持简单愚蠢)
- YAGNI原则
- 做最简单的事可能有效
- 关注点分离
- 保持DRY
- 站在维护者角度撸码
- 避免过早优化
- 童子军规则
模块间/类
- 最小化耦合
- 得墨忒耳定律
- 组合优于集成
- 正交
- 稳健性原则
- 控制反转
模块/类
- 最大化内聚
- 里式替换原则
- 开放/封闭原则
- 单一责任原则
- 隐藏实施细节
- 科里定律
- 封装变化
- 接口隔离原理
- 命令查询分离
KISS原则(Kepp It Simple Stupid)
如果保持简单的实现方式而不是将其复杂化,大多数系统都能发挥出最佳性能。
为什么
- 更少的代码花费更少的时间来编写,具有更少的错误,并且更容易修改
- 至繁归于至简
- 最完美是似乎没有任何东西需要添加,但也没有什么需要删除
参考资料
YAGNI原则(you aren’t gonna need it)
YAGNI代表“你不会需要它”:在必要做之前不要实施某些东西。
为什么
- 任何仅用于明天需要的功能的工作意味着需要从当前迭代的工作中需要完成的功能中花费精力
- 它导致代码膨胀; 软件变得更大,更复杂
怎么样
- 船到桥头自然直。当你真正需要的时候,总会想办法去实施,而不是你预见需要它们的时候就去执行
参考资料
做最简单的事可能有效
为什么
- 如果我们只是解决问题的真正原因,那么真正问题的真正进展就会最大化
怎么样
参考资源
关注点分离
关注点分离是将计算机程序分成不同部分的设计原则,这样每个部分都解决了一个单独的问题。例如,应用程序的业务逻辑是一个关注点,用户界面是另一个关注点。更改用户界面不应要求更改业务逻辑,反之亦然。
引用Edsger W. Dijkstra
(1974):
这就是我有时称之为“关注点的分离”,即使不完全可能,它仍然是我所知道的有效调整一个人思想的唯一可行技术。这就是我所说的“将注意力集中在某些方面”:它并不意味着忽视其他方面,它只是公正地从这个方面来看,另一个是无关紧要的事实。
为什么
- 简化软件应用程序的开发和维护
- 当问题分离时,各个部分可以重复使用,也可以独立开发和更新
怎么样
资源
保持DRY(Dont Repeat Yourself)
每个知识点都必须在系统中具有单一、明确、权威的表现。
程序中的每个重要功能都应该只在源代码中的一个地方有实现。在通过不同的代码片段执行类似的功能的情况下,通过抽象出变化的部分将它们组合成一个整体通常是有好处的。
为什么
- 重复(无意或有目的的重复)可能导致维护噩梦、不良架构和逻辑矛盾
- 对系统的任何单个元素的修改不需要改变其他逻辑上不相关的元素
- 另外,逻辑上相关的元素都可以预测和统一地改变,因此被保持同步
怎么样
- 只在一个地方放置业务规则、长表达式、if语句、数学公式、元数据等
- 确定系统中使用的每一条知识的单一、权威来源,然后使用该源生成该知识的适用实例(代码,文档,测试等)
- 适用三条规则
参考资源
相关
- 抽象原则
- Once And Only Once是DRY的一个子集(也称为重构的目标)
- 唯一真想来源
- 违反DRY是WET(Write Everything Twice)
站在维护者的角度去撸代码
为什么
怎么样
- 做维护者
- 撸代码时想象好像最终维护你的代码的人是一个知道你住在哪里的暴力精神病患者
- 站在少数初级开发都能接受的方式去撸码和写注释,他们会很乐意阅读并从中学习
- 不要让我思考
- 使用最小惊讶原则
参考资源
避免过早优化
引用Donald Knuth
:
程序员浪费了大量时间来思考或担心程序中非关键部分的效率,而这些效率尝试实际上在考虑调试和维护时会产生很大的负面影响。我们应该忘记小部分的效率,说大约97%的时间里:过早的优化是万恶之源。然而,我们不应该错过那个至关重要的3%的机会。
理解怎样才算是”过早”是至关重要的
为什么
- 事先不知道瓶颈在哪里
- 优化后,可能更难以阅读并因此维护
怎么样
- Make It Work Make It Right Make It Fast
- 在您需要之前不要进行优化,并且只有在进行性能分析后才发现瓶颈才能优化
参考资源
最小化耦合
模块/组件之间的耦合是它们相互依赖的程度; 较低的耦合更好。换句话说,耦合是指代码单元”A”发生未知更改会对代码单元”B”造成影响
为什么
- 一个模块的变化通常会对其他模块的变化产生连锁反应
- 由于模块间依赖性的增加,模块的配置可能需要花费更多的精力或时间
- 特定模块可能更难以重用或测试,因为必须包含依赖模块
- 开发人员可能害怕更改代码,因为他们不确定可能会受到什么影响
怎么样
- 消除、最小化和减少必要关系的复杂性
- 过隐藏实现细节,减少了耦合
- 应用得墨忒耳定律/最少知识原则
参考资源
得墨忒耳定律/最少知识原则
不要和陌生人说话
为什么
怎么样
- 对象的方法只能调用方法通过以下几个点:
- 对象本身
- 方法中一个参数
- 在方法中创建的任何对象
- 对象的任何直接属性或字段
参考资源
组合优于继承
为什么
- 类之间的耦合较少
- 使用继承,子类很容易做出假设,并破坏LSP(里氏代换原则)
怎么样
- 测试LSP(可替代性)以决定何时继承
- 当存在“具有”(或“使用”)关系时选择组合,在“是”时选择继承
参考资源
正交
正交性的基本思想是,在概念上不相关的事物不应该与系统相关
来源:Be Orthogonal
它与简单相关; 设计得越正交,异常越少。这使得学习、读和写程序变得更容易。正交特征的含义与上下文无关; 关键参数是对称性和一致性
资料来源:正交性
稳健性原则
对你的所作所为保守,对接受别人开放
协作服务取决于彼此的接口。通常导致另一端接收未指定的数据的接口需要改进。如果收到的数据不严格遵循规范,那么天真的实现就会拒绝接收数据。更复杂的实现仍然会忽略它无法识别的数据。
为什么
- 为了能够发展服务,您需要确保提供商可以进行更改以支持新需求,同时最大限度地减少对现有客户的破坏
怎么样
- 将命令或数据发送到其他机器(或同一台机器上的其他程序)的代码应完全符合规范,但只要含义明确,接收输入的代码就应接受不符合要求的输入
参考资源
控制反转
控制反转也被称为好莱坞原则,“不要打电话给我们,我们会打电话给你”。这是一种设计原则,其中计算机程序的定制编写部分通过一个通用框架去接收控制流。控制反转带有强烈的内涵——即使它们在应用程序中一起运行,但可重用代码和特定于问题的代码是独立开发的
为什么
- 控制反转用于增加程序的模块性并使其可扩展
- 将任务的执行与实现分离
- 将模块集中在它所针对的任务上
- 使模块免于假设其他系统如何做他们所做的事情,而是依赖契约
- 防止替换模式时产生副作用
怎么样
- 使用工厂模式
- 使用服务定位器模式
- 使用依赖注入
- 使用上下文查找
- 使用模板方法模式
- 使用策略模式
参考资源
- 维基百科中的控制反转
- 控制容器的反转和依赖注入模式
最大化内聚
单个模块或组件的内聚是其职责构成有意义单元的程度; 凝聚力越高越好
为什么
- 理解模块的难度增加
- 维护系统的难度增加,因为域中的逻辑更改会影响多个模块,并且因为一个模块中的更改需要更改相关模块
- 由于大多数应用程序不需要模块提供的随机操作集,因此增加了重用模块的难度
怎么样
怎么样
里氏替换原则
LSP完全是关于对象的预期行为:
程序中的对象应该可以替换其子类型的实例,而不会改变该程序的正确性
参考资源
开放/封闭原则
软件实体(例如类)应该是可以扩展的,但是关闭以进行修改。即,这样的实体可以允许在不改变其源代码的情况下修改其行为
为什么
怎么样
- 编写可以扩展的类(与可以修改的类相比较)
- 仅暴露需要更改的移动部件,隐藏其他所有内容
参考资源
单一责任原则
一个类永远不应该有多个改变的理由
长版本:每个类都应该承担一个责任,并且该责任应该由类完全封装。责任可以被定义为改变的理由,因此一个类或模块应该只有一个改变的理由
为什么
怎么样
参考资源
隐藏实施细节
软件模块通过提供接口隐藏信息(即实现细节),而不泄漏任何不必要的信息
为什么
怎么样
- 最小化类和成员的可访问性
- 不要公开公开成员数据
- 避免将私有实现细节放入类的接口中
- 减少耦合以隐藏更多实现细节
参考资源
科里定律
科里定律是为任何特定的代码选择一个明确定义的目标:做一件事
封装变化
一个好的设计可以识别最有可能改变的热点,并将它们封装在API之后。当发生预期的变化时,这些修改被保持在本地
为什么
怎么样
- 封装API背后变化操作
- 尽可能将变化的操作分到其自身的模块
参考资源
接口隔离原理
将胖接口减少为多个更小、更具体的客户端特定接口。接口应该更多地依赖于调用它的代码而不是实现它的代码
为什么
- 如果一个类实现了不需要的方法,则调用者需要知道该类的方法实现。例如,如果一个类实现一个方法但只是抛出,那么调用者将需要知道实际上不应该调用此方法
怎么样
参考资源
童子军规则
美国童子军有一个简单的规则,我们可以适用于我们的职业:“离开宿营地前进行清扫活动”。童子军规则规定我们应该始终保持代码整洁而不是等到发现再去整改
为什么
- 在对现有代码库进行更改时,代码质量往往会降低,从而累积技术债务。按照童子军规则,我们应该注意每次提交的质量。无论多么小,技术债务都会受到持续重构的抗拒
怎么样
- 每次提交都要确保它不会降低代码库质量
- 每当有人看到一些不太清晰的代码时,他们应该抓住机会在那里修复它
参考资源
- Opportunistic Refactoring
命令查询分离
命令查询分离原则指出每个方法应该要么是执行操作的命令要么是数据查询(修改或查询),而不是两者同时又执行又查询。提出问题不应该修改答案。
应用此原则后,程序员可以更自信地编写代码。查询方法可以在任何地方以任何顺序使用,因为它们不会改变状态。使用命令必须更加小心
为什么
- 通过将方法明确地分离为查询和命令,程序员可以在不知道每个方法的实现细节的情况下进行编码
怎么样
- 将每个方法实现为查询或命令
- 将命名约定应用于方法名称,该方法名称暗示该方法是查询还是命令
参考资源
- 维基百科中的命令查询分离
- Martin Fowler命令查询分离