在不改变软件可观察行为的前提下,对软件内部进行调整(使用重构手法),以提高其可理解性,降低其修改成本。 -- 重点在两点: 1. 不改变软件的可观察行为。2. 提高其可理解性。
两个目的:1. 添加新功能。 2. 重构。 --重构就只管修改程序结构,不要添加新功能。 添加新功能就不要修改既有代码。两者混合进行会使得程序朝不可理解的方向发展。
ps: 重构与设计模式具有辩证的关联性,模式是目的,重构是到达之路。重构促进设计模式的形成与稳定,模式为重构提供前进方向,二者相辅相成,具有统一性。
1. 面对迅速变化的需求,对原有的代码进行修改十分困难(逻辑复杂,条理不清晰,很难兼顾;改动接口多,测试困难), 尤其对于某些无法限定影响面的接口修改。
2. 使得原有设计保持本真意义。代码结构的流失具有累积性,原有的设计及意图难以保持,阅读源代码很难理解原来的设计。
3. 消除重复代码,重复代码越多,修改的风险越大,修改带来的不一致性可能越大(修改一处,未修改另一处)
4. 使得软件更容易理解,结构更清晰,代码更简洁。
5. 帮助找到bug
6. 提高编程速度 -- 维持良好设计,清晰的意图,从而使得编程更加容易。
7. 重构与新功能 -- 如果你发现自己需要添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,使特性比较容易进行,然后再添加特性。
8. 重构与性能 -- 重构调整代码结构,使性能调优更加方便的进行。
为什么要进行架构设计: 采用好的架构可以大大节省软件项目构建与维护的人力成本。每次变更都短小简单,易于实施,并且避免缺陷,用最小的成本,最大程度的满足功能性的需求。在软件的全生命周期内,最大化程序员的生产力,同时最小化系统的总运营成本。
怎样才是良好的架构设计: 良好的架构设计让系统便于理解,易于修改,方便维护,轻松部署,不依赖于成堆的脚本与配置文件,也不需要用户创建有严格要求的目录与文件。
怎样才是优秀的架构设计师:架构师的职责在场景需求中,整理出核心的业务流程。对业务进行边界划分,同时管理好各个边界的依赖。优秀的架构师往往对业务场景有较好的抽象能力,能够cover住场景类各业务实体变化的需求。 同时优秀的架构师能够预留出不完全边界,在以后业务拆分重构中以微小易实施的改动进行。最后优秀的架构师为系统预留了许多的选项(方便的添加),将技术手段的决策延后(使用哪种框架等决策),并做到设备无关,使业务的发展不依赖于具体的框架、语言、数据库等技术细节。
1. 行为价值: 按照需求文档写代码,修复Bug
2. 架构价值: 软件必须可以以一种灵活的方式来改变机器的工作行为 (ps: 这种灵活体现在对需求的扩展性上, 而场景抽象能力是一位架构师的基础能力)
总结: 一次性消费的代码,在需求变更过程中价值趋于0, 而良好设计的架构能够满足不断变化的需求(场景内), 从而带来持续的价值。
一个软件架构师,更应该关注系统的整体架构,而不是具体的功能和系统行为的实现。软件架构师必须创建出一个可以让功能实现起来容易、修改起来更简单、扩展起来更轻松的软件架构。
如果系统越来越难维护,终导致无法修改,说明软件工程师没有完成自己应尽的责任,是软件开发的失败。
2.1.3.1 结构化编程: 对程序控制器直接转移的限制
将一段程序递归降解为一系列可证明的小函数,然后通过编写相关的测试来证明这些函数都是错误的。如果无法证伪这些函数,那么久可以认为这些函数是足够正确的。
可以说结构化编程,功能性降解,然后证伪仍然是软件架构设计领域的最佳实践。
2.1.3.2 面向对象编程: 对程序控制器间接转移的限制
依赖反转:让对源代码实现的依赖变成对接口的依赖,而源代码实现依赖接口,让源代码实现可以随意替换。
多态可以对源代码的中的依赖关系进行控制,从而让代码成为一种插件式的架构,对于组件的替换基本不需要进行代码改动。经过高层策略组件与底层实现的分离,从而做到真正的独立部署、独立开发。
同时面向对象的多态通过隐式的方式来使用函数指针,从而保证了函数指针的安全性。
2.1.3.3 函数式编程: 对赋值操作的限制
如果计算能力很大,存储能力足够。对于任何可变变量,都可以通过函数计算来获取。这样程序始终处于无状态,无锁竞争之中,处于不变之中。这就是函数式编程。
(其最终目的都是为了建立一个松耦合的结构,让系统可以灵活的扩展,以满足场景内需求的变化)
2.1.4.1 solid原则
在构建中层模块结构时候,将数据和函数进行合适的分类组合。主要目标: 使软件可容忍被改动;使软件容易被理解;构建可在多个软件系统中复用的组件
1. 单一职责原则: 每个软件模块都有且只有一个需要被改变的理由->任何一个模块都应该只对一个用户,或系统利益相关者负责->任何一个模块都应该只对某一个行为者负责。
软件模块:一组紧密相关的函数和数据结构, 更笼统的说 就是指一个类。 用户:更抽象的说是一个需求。
单一职责让一个模块或一个函数,只对一个用户或者高层负责。 避免不同用户对同一模块的依赖从而造成混乱。
2. OCP开闭原则: 设计可以通过新增加代码来修改系统行为,而不是靠修改原来的代码。对修改关闭,对新增开放。
这需要保存高层抽象的稳定性,让底层实现依赖于高层代码,只需要增加底层的不同实现即可。
3. LSP里式替换原则:如果想用可替换的组件来构建软件系统,那么这些组件必须遵守同一个约定。任何使用父类的地方,都可以用其子类替换。
4. 接口隔离原则:在设计过程中避免不必要的依赖,组件不应该依赖,其不必要的东西(所依赖的类包含无关的东西)
类似迪米特原则:在类的层级依赖关系中,当前类只与上下两层的类发生依赖,不隔层依赖,不同层依赖。这样避免了不必要的依赖,从而减少修改的扩散风险,同时为分离部署和单独编译带来可可能。
5. DIP依赖反转: 高层策略性代码不应该依赖底层细节的代码。底层实现细节代码应该依赖高层策略性代码。
ps: 单一职责,与接口隔离的统一点: 单一职责使得某一个类或组件只服务于某一功能,另一个类或组件服务于另一功能,这自然做到了接口隔离,从而可独立发展,部署。
但单一职责体现变更层面的意思:某一个类只由于一个原因而变更,不因为不同原因而变更。
而接口隔离体现依赖层面的意思:某一个类不依赖其他不相干的类。既然类都是单一职责了,自然不会产生依赖,即使有依赖,可以通过导演类,全局变量来解决。
2.1.4.2 组件聚合原则
REP原则(复用/发布): 软件复用的最小粒度,等同于其发布的最小粒度。 REP原则指组件中的类与模块彼此紧密相关,他们必须有一个共同的主题或者大方向,从而成为一个可独立发布的可复用模块
CCR原则(共同闭包):将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,与单一职责类似,只不过其服务的层次更高,更抽象。
CRP原则(共同复用): 比较常见的情况是多个类同时作为某个可复用的抽象定义,被共同复用。应该将这些类放在同一个组件中。
2.1.4.3 组件耦合原则
无环依赖原则: 如果组件间的依赖形成一个环,则这些组件必须共同修改,共同发布,共同打tag,同时无法进行分解测试,必须集成测试。相反如果单向依赖,则各组件可以单独发布版本,依赖他的模块只需控制版本即可。依赖反转可以解决循环依赖。
稳定性依赖原则: 从依赖的上游,到依赖的下游,稳定性一次递增。对于需要具有高稳定性的下游模块,其变更的工作量,远远大于新增工作量。抽象的高阶策略代码