架构简洁之道笔记(CSDN这个Mac down模版还真的挺不错)

架构整洁之道笔记

软件架构(architecture)重点是组织结构(structure)。不管组件(componet)、类(class)、函数(function)、模块(module)还是层级(layer)、服务(service)以及宏观和微观的软件开发过程,软件的组织结构都是我们主要的关注点。

软件项目是具有递归(recursive)和分形(fractal)特点,最终都是由一行一行代码组成。脱离了具体的细节设计,架构设计就无从谈起。

虽然看这些有些早了点,不过也并不是毫无作用,这也是在提示自己架构的重要性,在使用别人的框架时多思考为什么要这么设计,以后再回过头来看看可能收获会更多。

第一章 设计和架构究竟是什么

乱麻系统特点越到后期生产力越低,到后期不可避免地趋近于零————工程师生产力/产品版本

要想跑的快先得跑的稳,作为工程师不能过度自信导致系统一团乱麻。不能持续低估那些好的、良好设计的、整洁的代码的重要性。认真对待自己的代码架构,对其质量负责。这样才能做出长期稳定的、持久优秀的系统。

第二章 两个价值维度

行为价值:软件系统的行为是其最为直观的价值维度。 需求文档转化为实际代码

架构价值:软件的第二个价值维度就体现在单词上:software。"soft"指的是软件的灵活性,"ware"指的是“产品”。

当需求方改变需求时,随之所需的软件变更必须可以简单而方便地实现。变更实施的难度应该和变更的范畴(scope)成等比关系,而与变更的具体形状(shape)无关。

哪个价值维度更重要?究竟是系统行为更重要,还是系统架构的灵活性更重要?
对业务部门来讲通常认为系统正常工作很重要,但是如果一个系统可以正常工作,但无法进行修改,那么它无法产生持续价值。业务变更需求时,预估工作量会远远大于业务部门的预期。

如美国前总统艾森豪威尔的紧急/重要矩阵,紧急的难题永远是不重要的,重要的难题永远是不紧急的。

软件系统的第一价值维度:系统行为,是紧急的,但是并不总是特别重要。
软件系统的第二价值维度:系统架构,是重要的,但是并不总是特别紧急。

软件架构师就是更关注系统的整体结构,而不是具体的功能和系统行为的实现。软件架构师必须创建出一个可以让功能实现起来更容易、修改起来更简单、扩展起来更轻松的软件架构。

第三章 编程范式(paradigm)总览

结构化编程(structured programming)

结构化编程由Edsger Wybe Dijkstra于1968年提出,他论证使用goto无限制跳转语句会损害程序的整体结构,主张使用if/then/else语句和do/while/until语句代替跳转语句。

面向对象编程(object-oriented programming)

面向对象编程对程序控制权的间接转移进行了限制和规范。

函数式编程(functional programming)

函数式编程对程序中的赋值进行了限制和规范。

总结:多态是跨越架构边界的手段,函数式编程是规范和限制数据存放位置与访问权限的手段,结构化编程是各模块的算法实现基础。这和软件架构的三大关注点不谋而合:功能性、组件独立以及数据管理。

第四章 结构化编程

goto语句是有害的

功能性降解拆分:可以将大型系统设计拆分成模块和组件,而这些模块和组件最终可以拆分成更小的、可证明的函数。

科学理论和科学定律的特点:它们可以被证伪,但是没有办法被证明。如果某个结论经过一定的努力无法证伪,我们则认为它在当下是足够正确的。

Dijkstra说过测试只能展示bug的存在,并不能证明不存在bug。

结构化编程最有价值的地方是它赋予了我们创造可证伪程序单元的能力。这就是为什么现代编程语言一般不支持无限制的goto语句。更重要的是,这也是为什么在架构设计领域,功能性降解拆分仍是最佳实践之一。

第五章 面向对象编程

什么是面向对象:常见的回答是数据与函数的组合。另一种是,面向对象编程是一种对真实世界进行建模的方式。还有人说面向对象编程是封装、继承和多态的有机组合。

封装:把一组关联的数据和函数圈起来,使圈外的代码只能看见部分函数,数据则完全看不见。例如,类中的公共函数和私有成员变量。

继承:它的作用是让我们可以在某个作用域内对外部定义的某一组变量与函数进行覆盖。

多态:在面向对象语言被发明之前,我们所使用的编程语言就支持多态。自从冯·诺伊曼架构诞生起,程序猿们就一直在使用函数指针模拟多态。但是用函数指针显式实现多态的问题就在于函数指针的危险性。函数指针的调用依赖于一系列人为遵守的约定。一旦有一个程序员没有遵守这些约定,整个程序就会产生极其难以跟踪和消除的bug。

依赖反转:模块和接口在源码上的依赖关系(或者叫继承关系),该关系的方向和控制流正好相反的,我们称之为依赖反转。

总结:面向对象编程就是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。

第六章 函数式编程

不可变性与软件架构:所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。如果变量永远不会被更改,那就不可能产生竞争或者并发更新问题。如果锁状态是不可变的,那就永远不会产生死锁问题。如果我们能忽略存储器与处理器在速度上的限制,那么不可变性是实际可行的。否则,它只在一定情况下可行的。

可变性的隔离:

由于状态的修改会导致一系列并发问题的产生,所以我们通常会采用某种事务型内存来保护可变变量,避免同步更新和竞争状态的发生。事务型内存基本上与数据库保护磁盘数据的方式类似,通常采用的是事务或者重试机制。

在这里插入图片描述

总结:计算机程序无一例外是由顺序结构、分支结构、循环结构和间接转移这几种行为组合而成的,无可增加,也缺一不可。

设计原则——SOLID

SOLD原则的主要作用是告诉我们如何将数据和函数组织成为类,以及如何将这些类链接起来成为程序。一般情况下,我们为软件构件中层结构的主要目标如下:
1、使软件可容忍被改动。
2、使软件更容易被理解。
3、构建可在多个软件系统中复用的组件。

第七章 Single Responsibility Principle 单一职责原则

任何一个软件模块都应该只对某一类行为者负责。

简单直接的方法是:将数据与函数分离,设计三个类(PayCalculator、HourReporter、EmployeeSaver)共同使用一个不包含函数的、十分简单的EmployeeData类,每个类只包含与之相关的函数代码,互相不可见。

另一种办法是:使用Facade设计模式。

单一职责原则主要讨论的是函数和类之间的关系——但是它在两个讨论层面上会以不同的形式出现。在组件层面,我们可以将其称为共同闭包原则(Common Closure Principle),在软件架构层面,它则是奠定架构边界的变更轴心(Axis of Change)。

第八章 Open Closed Principle 开闭原则

设计良好的计算机软件应该易于扩展,同时抗拒修改。

一个好的软件架构师会努力将旧代码的修改量降至最小,我们可以先将满足不同需求的代码分组(即SRP),然后再来调整这些分组之间的依赖关系(即DIP)。

在具体实现上,我们会将整个程序划分成一系列的类,然后再将这些类分割成不同的组件。
如果A组件不想被B组件上发生的修改所影响,那么就应该让B组件依赖于A组件。Interactor组件是整个系统中最符合OCP的。发生在DataBase、Controller、Presenter甚至View上的修改都不会影响到Interactor。

组件之间的关系都是单向的

OCP是我们进行系统架构设计的主导原则,其主要目标是让系统易于扩展,同时限制其每次被修改所影响的范围。实现方式是通过系统划分为一系列组件,并且将这些组件间的依赖关系按层次结构进行组织,使得高阶组件不会因低阶组件被修改而受到影响。

第九章 Liskov Substitution Principle 里氏替换原则

1988年Barbara Liskov在描述如何定义子类型时,这里需要一种可替换性:如果对于每个类型是S的对象o1都存在一个类型为T的对象o2,能使操作T类型的程序P在用o2替换o1时行为保持不变,我们就可以将S称为T的子类型。

比如两个子类继承父类,使用父类的方法进行实现。这样的设计就符合LSP原则。

随着时间的偏移,LSP逐渐演变成一种更广泛的、指导接口与其实现方式的设计原则。LSP可以且应该被应用于软件架构层面,因为一旦违背了可替换性,该系统架构就不得不为此增添大量复杂的应对机制。

第十章 Interface Segregation Principle 接口隔离原则

任何层次的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。

第十一章 Dependence Inversion Principle 依赖反转原则

如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。

但是把这条设计原则当成金科玉律来加以严格执行是不现实的,因为软件系统在实际构造中不可避免地需要依赖到一些具体实现。例如,Java中的String类就是这样的一个具体实现,我们将其强迫转换为抽象类是不现实的,而在源代码层次上也无法避免对Java.lang.String的依赖,并且也不应该尝试去避免。

String类本身是非常稳定的,因为这个类被修改的情况是非常罕见的,而且可修改的内容也受到严格的控制,所以程序猿和软件架构师完全不必担心String类上会发生经常性的或意料之外的修改。

我们主要关注的是软件系统内部那些经常变动的(volatile)具体实现模块。

如果想要在软件架构上追求稳定,就必须多使用稳定的抽象接口,少依赖多变的具体实现。将该设计原则归纳为以下具体的编码原则:
1、应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。
2、不要在具体实现类上创建衍生类。
3、不要覆盖(Override)包含具体实现的函数。
4、应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。

在大部分面向对象编程语言中,人们都会选用抽象工厂模式来解决源代码依赖的问题。

组件构建原则

大型软件系统的构建过程与建筑物修建很类似,都是由一个个小组件组成的。所以,如果说SOLID原则是用于指导我们如何将砖块砌成墙和房间的,那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。

第十二章 组件

组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。

在早期的软件开发中,程序员可以完全掌握自己编写程序所处的内存地址和存放格式。因为初期的编程方式不能被重定位(relocate),所以调用函数时需要将所有要调用的库函数源代码包含到自己的程序代码中,然后再整体编译。编译器只能多次从缓慢的存储设备中读取源代码,这样做十分耗时。

重定位技术
编译生成可重定位的二进制文件,修改编译器输出文件的二进制格式,使其可以由一个智能加载器加载到任意内存位置。当然,这需要我们在加载器启动时为这些文件指定要加载到的内存地址,而且可重定位的代码中还包含了一些记号,加载器将其加载到指定位置时会修改这些记号对应的地址值。————这个过程是将二进制文件中包含的内存地址都按照其加载的内存基础位置进行递增。

后面未完。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值