1.设计与架构究竟是什么:
软件架构的终极目标,用最小的人力成本来满足构建和维护该系统的需求。
2.架构的两个价值维度:行为和架构
架构是行为的基础,不打好基础,系统就乱套了,最终难以维护
3.三种编程范式(目的是限制):
(1)结构化编程(structured programming),限制了goto语句。
(2)面向对象编程(object-oriented programming),限制了函数指针。
(3)函数式编程(functional programming),限制了赋值语句。
4.关于测试的2点认知
(1)科学方法论不需要证明某条结论是正确的,只需要想办法证明它是错误的。如果某个结论经过一定的努力无法证伪,我们则认为它在当下是足够正确的。
(2)Dijkstra曾经说过“测试只能展示Bug的存在,并不能证明不存在Bug”,换句话说,一段程序可以由一个测试来证明其错误性,但是却不能被证明是100%正确的。测试的作用是让我们得出某段程序已经足够实现当前目标这一结论。
5.锁与变量的关系
(1)所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。如果变量永远不会被更改,那就不可能产生竞争或者并发更新问题。如果锁状态是不可变的,那就永远不会产生死锁问题。
(2)软件架构师应该着力于将大部分处理逻辑都归于不可变组件中,可变状态组件的逻辑应该越少越好。
6.关于软件设计的5个原则
(1)单一职责:函数和类必须在某一维度职责单一,只对某一类行为者负责。避免边界不清晰,后期维护困难
(2)开闭原则:对扩展开放,对修改关闭;对客户端修改关闭,对服务端修改开放
(3)里氏替换选择,父类出现的地方子类可以进行替换,提升代码复用性、扩展性;同时又增加了父子类的耦合性
(4)接口隔离原则:接口、类的职责要单一,低耦合
(5)依赖反转原则:要依赖抽象/接口,不依赖具体实现(代码注释要更贴近业务语言,避免出现具体实现相关的描述,简称通用语言)。
7.关于组件
组件是软件在部署过程中的最小单元。设计良好的组件都应该永远保持可被独立部署的特性,也意味着这些组件应该可以被单独开发,对应在Java里就是jar文件。
8.关于组件聚合
(1)软件开发者必须要能够知道这些组件的发布时间,以及每次发布带来了哪些变更
(2)对大部分应用程序来说,可维护性的重要性要远远高于可复用性。
(3)这些变更最好都体现在同一个组件中,而不是分布于很多个组件中
(4)将由于相同原因而修改,并且需要同时修改的东西放在一起。将由于不同原因而修改,并且不同时修改的东西分开。
(5)这种平衡本身也在不断变化。也就是说,当下适用的分割方式可能明年就不再适用了。所以,组件的构成安排应随着项目重心的不同,以及研发性与复用性的不同而不断演化。
9.关于组件耦合
(1)第一种是“每周构建”,第二种是“无依赖环原则(ADP)”。
(2)我们可以打破这些组件中的循环依赖,并将其依赖图转化为DAG。目前有以下两种主要机制可以做到这件事情
a.应用依赖反转原则(DIP)
b.创建一个新的组件
(3)我们不希望那些频繁变更的组件影响到其他本来应该很稳定的组件
(4)组件依赖关系是必须要随着项目的逻辑设计一起扩张和演进的。
(5)任何一个我们预期会经常变更的组件都不应该被一个难于修改的组件所依赖,否则这个多变的组件也将会变得非常难以被修改。
(6)让软件组件难于修改的一个最直接的办法就是让很多其他组件依赖于它。带有许多入向依赖关系的组件是非常稳定的,因为它的任何变更都需要应用到所有依赖它的组件上。
10.关于软件架构
(1)软件架构等同于设计架构,即要设计出易于理解,易于修改,易于维护的的软件。最大限度的释放开发人员的维护人力,减少软件运营成本
(2)软件架构设计的主要目标是支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护,并且能轻松部署。软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本。
(3)对于一个因架构设计糟糕而效率低下的系统,我们通常只需要增加更多的存储器与服务器,就能够让它圆满地完成任务。另外,硬件也远比人力要便宜,这也是软件架构对系统运行的影响远没有它对开发、部署、维护的影响那么深远的一个原因。
(4)设计良好的系统架构应该可以使开发人员对系统的运行过程一目了然
(5)在软件系统的所有方面中,维护所需的成本是最高的。满足永不停歇的新功能需求,以及修改层出不穷的系统缺陷这些工作将会占去绝大部分的人力资源。
(6)系统维护的主要成本集中在“探秘”和“风险”这两件事上。其中,“探秘(spelunking)”的成本主要来自我们对于现有软件系统的挖掘,目的是确定新增功能或被修复问题的最佳位置和最佳方式。而“风险(risk)”,则是指当我们进行上述修改时,总是有可能衍生出新的问题,这种可能性就是风险成本。
(7)优秀的架构师会小心地将软件的高层策略与其底层实现隔离开
(8)优秀的架构师所设计的策略应该允许系统尽可能地推迟与实现细节相关的决策,越晚做决策越好。
11.关于独立性
我们一定要小心避免陷入对任何重复都要立即消除的应激反应模式中。一定要确保这些消除动作只针对那些真正意义上的重复。
12.划分边界
(1)软件架构设计本身就是一门划分边界的艺术。边界的作用是将软件分割成各种元素,以便约束边界两侧之间的依赖关系。
(2)简单来说,通过划清边界,我们可以推迟和延后一些细节性的决策,这最终会为我们节省大量的时间、避免大量的问题。这就是一个设计良好的架构所应该带来的助益。
(3)边界线应该画在那些不相关的事情中间。GUI与业务逻辑无关,所以两者之间应该有一条边界线。数据库与GUI无关,这两者之间也应该有一条边界线。数据库又与业务逻辑无关,所以两者之间也应该有一条边界线。
(4)这其实就是单一职责原则(SRP)的具体实现,SRP的作用就是告诉我们应该在哪里画边界线。
13.边界剖析
所谓划分边界,就是指在这些模块之间建立这种针对变更的防火墙。
14.策略与层次
低层组件被设计为依赖于高层组件。一条策略距离系统的输入/输出越远,它所属的层次就越高。而直接管理输入/输出的策略在系统中的层次是最低的。
15.业务逻辑
业务逻辑就是程序中那些真正用于赚钱或省钱的业务逻辑与过程。
选择直接在数据结构中使用对业务实体对象的引用。毕竟,业务实体与请求/响应模型之间有很多相同的数据。但请一定不要这样做!这两个对象存在的意义是非常、非常不一样的。随着时间的推移,这两个对象会以不同的原因、不同的速率发生变更。
16.关于框架
使用框架要衡量成本和产出,框架是工具而不是生活信条
17.整洁架构
所有跨边界的依赖线都是指向内的,这很好地遵守了架构的依赖关系规则
18.层次与边界
过度的工程设计往往比工程设计不足还要糟糕。但另一方面,如果我们发现自己在某个位置确实需要设置一个架构边界,却又没有事先准备的时候,再添加边界所需要的成本和风险往往是很高的。
19.软件构建发展的3个过程:
(1)“先让代码工作起来”——如果代码不能工作,就不能产生价值。
(2)“然后再试图将它变好”——通过对代码进行重构,让我们自己和其他人更好地理解代码,并能按照需求不断地修改代码。
(3)“最后再试着让它运行得更快”——按照性能提升的“需求”来重构代码。
分层架构的理念是基于接口编程的理念来设计的。当模块之间能以接口形式交互时,我们就可以将一个服务替换成另外一个服务
20.数据库是实现细节
数据存储只是一个实现细节,所以架构方案不应局限于数据存储
系统架构应该对磁盘本身的存在完全不关心。
21.Web是实现细节
业务规则应该与UI解耦,做到可插拔式,便于扩展
22.应用程序框架是实现细节
程序框架只是实现细节,架构尽量避免依赖框架
如果选择了框架,就要做好准备在整个生命周期里适应它
23.依赖关系方向
所有跨越边界的依赖关系都应该是同一个方向
只有外部代码能依赖内部代码,反之则不能