[学习笔记] 架构整洁之道

前言:

学习笔记,随时更新。如有谬误,欢迎指正。


说明:

  1. 红色字体为较为重要部分。
  2. 绿色字体为个人理解部分。

推荐序一

  1. 所有的编程都在解决一个问题——分离控制和逻辑。控制就是业务无关的系统控制(多线程、异步等),逻辑就是业务逻辑。控制和逻辑构成整体软件的复杂度,有效的分离控制和逻辑会让系统得到最大简化。
  2. 要做到能正确的区分几组词语:
    • 简单 vs 简陋
    • 平衡 vs 妥协
    • 迭代 vs 半成品

推荐序二

1.古老的程序员知识并没有过时(面向对象的编程原则、编程范式),不少如今光鲜的架构还是在解决古老的问题,需要聆听久远的教诲,遵循古老的智慧。

序言

  1. 软件架构学关注的一个重点是组织结构,好的架构层次清晰,一目了然。
  2. 无论多好的方案都必须理解和遵守现实的条件约束。
  3. 需要付出的事件、金钱和人力成本是区分架构规模大小的衡量标准,也可以用来判断某个特定架构的好坏:一个好的架构,不仅要在某一特定时刻满足软件用户、开发者和所有者的需求,更要在一段时间内维持满足他们后续的需求。
  4. 在一个好的架构中,常规变更不应该是成本高昂的,也不应该是难以解决的大型设计调整,更不需要单独立项来推进。这些常规变更应该要能融入到每日或者每周的日常系统维护中去。
  5. 在遵循指导原则情况下不断调整,不断实践。

前言

  1. 从计算机出现开始到今天,软件的本质都是一样的,都是由if语句、赋值语句以及while循环组成的。不管任何编程语言,最终产生的代码任然只是顺序结构、分支结构、循环结构的组合,这与计算机刚出现时的程序是一模一样的。
  2. 成绩算计出现开始到今天,计算机的本质基本没有什么大变化。编程语言稍微进步了一点,工具质量大大提升,但是计算机程序基本构造没有什么变化。
  3. 软件架构的规则就是排列组合代码块的规则。由于代码块的本质没有变化,因此排列组合他们的规则也就不会变化。

第一部分 概述

  1. 好的软件架构可以大大节省软件项目构建与维护的人力成本,让每次变更都短小简单,易于实施,并且避免错误,用最小的成本最大程度的满足功能性和灵活性的要求。

第1章 设计与架构究竟是什么

  1. 设计与架构,两者的概念没有任何区别。”架构“一词往往用于”高层级“的讨论中,而这类讨论一般都把”底层“的实现细节排除在外。而”设计“一词往往指具体的系统底层组织结构和实现的细节。但是这样的区分根本不成立。底层细节设计与顶层架构信息是不可分割的。它们组合在一起,共同定义了整个软件系统,缺一不可。所谓底层和高层本身就是一系列决策组成的连续体,并没有清晰的分界线
  2. 软件架构的终极目的:用最小的人力成本来满足构建和维护该系统的需求
  3. 一个软件架构的优劣,可以用它满足用户需求所需要的成本来衡量
  4. 无论从长期还是短期来看,胡乱编写代码的工作速度其实比循规蹈矩更慢。
  5. 软件开发的核心特点:想要跑的块,先要跑的稳

第2章 两个价值维度

  1. 每个软件系统都可以通过行为架构两个维度来体现他的实际价值。
  2. 行为价值:软件表现的行为符合用户需求,不出bug。这是软件系统最直观的价值维度。
  3. 架构价值:该价值就体现在software这个英文单词上,ware是指产品,而soft就是指软件的灵活性。软件系统必须保持灵活性,软件发明的目的就是以一种灵活的方式来改变机器的行为(机器上那些难以改变的行为通常称之为硬件)。架构设计就是灵活性的保障,好的架构要能轻松的应对各种灵活的常规修改。需求变更的实施难度应该与变更的范畴成等比关系(变更范畴应该是指变更所影响的范围,变更范畴以及变更难度也都受架构的影响)。
  4. 架构价值更为重要:如果某程序能运行,但需求变更后无法通过修改让它满足需求且继续正常工作,那这个程序就没价值(这里的无法修改并不是说真的无法修改,理论上任何程序都能修改,这里说的是实施变更的成本远远超过变更带来的价值)。相反,即便某程序现在无法工作,但是如果很容易能将它修好,并且随着正常需求的变化都很容易通过修改程序而实现,那这个程序就会持续产生价值。

第二部分 从基础构件开始:编程范式

1.编程范式是指程序的编写模式,与具体的编程语言关系相对较小。这些范式会告诉你应该在什么时候采用什么样的代码结构。

第3章 编程范式总览

  1. 结构化编程:采用子程序、程序码区块、for 循环以及 while 循环等结构,来取代传统的 goto 。希望借此来改善计算机程序的明晰性、品质以及开发时间,并且避免写出面条式代码。结构化编程对程序控制权的直接转移进行了限制和规范
  2. 面向对象编程:本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。面向对象编程对程序控制权的间接转移进行了限制和规范
  3. 函数式编程:属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。函数式编程对程序中的赋值进行了限制和规范
    4.软件架构的三大关注重点:功能性组件独立性数据管理

第4章 结构化编程

  1. 数学是要将可证明的结论证明,而科学则是要将可证明的结论证伪。
  2. 测试只能展示bug的存在,而不能证明bug的不该存在。测试的作用是让给我们得出某段程序已经足够实现当前目标这一结论。
  3. 结构化编程范式中最有价值的的地方就是赋予了我们创造可证伪程序单元的能力,这也就是为什么在架构设计领域,功能性讲解拆分仍然是最佳实践之一
  4. 无论哪一个层面,从最小函数到最大组件,软件的研发过程都和科学研究非常类似,它们都是由证伪驱动的。软件结构是需要定义可以方便的地进行证伪(测试)的模块、组件以及服。为了达到这个目的,他们需要将类似的结构化编程的限制方法应用在更高的层次上

第5章 面向对象编程

  1. 面向对象编程的三大特性:封装、继承和多态其实并不是面向对象编程送独有的。C 语言相对 C++ 来说其实也已经具有这些特性(或者说 C 语言也已蕴含这三大特性的精髓)。
    • 封装:封装可以把一组相关联的数据与函数圈起来,使圈外的代码只能看见部分函数,数据则完全不可见。分离式编译的c代码其实是完美的封装,声明与实现分离,对外暴露接口,内部细节可以完美隐藏。反而是 C++ 的类要求成员变量声明在头文件中(因为 C++ 编译器必须知道类所占的内存大小)破坏了这种完美封装。
    • 继承:继承可以在某个作用域内对外部定义的一组变量与函数进行覆盖。C 语言通过含有相同内存布局部分的两个结构之间的类型强转同样可以实现。
    • 多态:C 语言通过在不同设备上相同功能模块(如不同设备上的声卡驱动)上强制规定接口名称以及参数,调用的时候使用函数指针就可以实现多态。归根结底,多态不过是函数指针的一种应用
  2. 对以一个架构师来说,面向对象编程的含义是非常明确的:面向对象编程就是以多态为手段来对源码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署

第6章 函数式编程

  1. 函数式编程中的变量是不可变的。
  2. 不可变性是软件架构设计中需要考虑的重点,因为一切的竞争、死锁、并发问题都是由可变变量导致的。
  3. 一个架构设计良好的应用程序应该将状态修改的部分和不需要修改的部分隔离成单独的组件,然后用合适的机制来保护可变量。软件架构师应该着力于将大部分处理逻辑归于不可变组件中,可变状态组件的逻辑应该越少越好

第三部分 设计原则

第7章 SRP:单一职责原则

  1. SRP:任何一个软件模块都应该只对某一类行为者负责。
  2. 单一职责原则主要讨论的是函数与类之间的关系,但它在两个讨论层面上会议不同的形式出现。在组件层面,我们可以将其称为共同闭包原则( Common Closure Principle )。在软件架构层面,它则是用于奠定架构边界的变更轴心( Axis of Change )。

第8章 OCP:开放封闭原则

  1. OCP:设计良好的计算机软件应该易于扩展,同时抗拒修改。也就是说,一个设计良好的计算机系统应该在不需要需改的前提下就可以轻易被扩展。
  2. OCP 是系统架构设计的主导原则,目标是让系统易于扩展,同时限制其每次被修改的所影响的范围。实现方式是通过将系统划分为一系列组件,并且将这些组件之间的依赖关系按层次结构进行组织,使得高阶组件不会因低阶组件被修改而受到影响。

第9章 LSP:里氏替换原则

  1. 如果想用可替换的组件来构建软件系统,那么这些组件必须遵守同一个约定,以便这些组件可以相互替换。
  2. LSP 不仅是指导如何使用继承关系的一种方法,它也是一种更为广泛的、指导接口与其实现方式的设计原则。(这里的接口可以有多种形式,可以是 Java 风格接口,具有多个实现类;也可以像 Ruby 一样,几个类共用一样的方法签名,甚至可以是几个服务响应同一个 Rest 接口,LSP 适用这些场景,因为这些场景中的用户都依赖于一种接口,并且都期待实现该接口的类之间能具有可替换性)。

第10章 ISP:接口隔离原则

  1. LSP:在软件设计中应避免不必要的依赖。任何层次的软件设计如果依赖了它所不需要的东西,就会带来意料之外的麻烦。

第11章 DIP 依赖反转原则

  1. DIP:如果想要设计一个灵活的的系统,在源代码层次的层次的依赖关系中应该多引用抽象类,而非具体实现。
    • 应该在代码中多使用抽象类,尽量避免使用那些多变的具体实现类(通常使用抽象工厂模式)。
      *不要再具体实现类上创建衍生类(继承关系是所有一切源代码依赖关系中最强、最难以修改的,所以对继承的使用要格外小心)。
    • 不要覆盖包含具体实现的函数(尽量调用抽象函数)。
    • 避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。

第四部分 组件构建原则

第12章 组件

  1. 组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。在编译运行语言中,组件是一组二进制文件组合。在解释型语言中,组件是一组源代码文件的组合。
  2. 在程序开发早期,程序员可以完全掌控自己编写的程序所处的内存地址和存放格式。那时,程序的第一条语句被称为起源语句,它的作用的是声明该程序有应该被加载到的内存位置。这种程序基本不能被重定位。在调用函数库的时候程序员需要将所有要调到的函数库源代码包含到自己测程序中,然后再整体编译。函数库文件都是以源码而非二进制的形式保存的。但这种方式会使编译十分耗时。
  3. 为了缩短编译时间,程序员们将函数库的源代码单独编译。函数库的源代码在编译后会被加载到一个指定位置(固定地址)。然后编译器会针对该库文件创建一个符号表,将其和应用程序编译在一起。当程序运行时,先会加载二进制形式的库文件,再加载编译后的应用程序。但是当应用程序较大时,一块空闲内存放不下,那就得将程序代码分割存放在多处。但是程序和函数库的碎片化程度就会随着计算机内存的增加而不断增加。
  4. 为了解决内存碎片化问题,出现了重定位计数。其原理就是程序员修改修改编译输出文件的二进制格式,使其可以有由一个智能加载器加载到任意内存位置。在加载器启动时需要为这些文件指定要加载到的内存地址,而且可重定位的代码中还包含了一些符号,加载器将其加载到指定位置时会修改这些记号对应的地址。一般来说,这个过程不过就是将二进制文件中包含的内存地址都按照其加载到的内存基础位置进行递增。除此之外,程序员还对编译器进行了另一个修改,就是在可重定位的二进制文件中将函数名输出为元数据并存储起来。这样一来,如果一段程序调用了某个库函数,编译器就会将这个函数名称输出为外部引用( external referance ),而将库函数的定义输出为外部定义( external definition )。加载器在加载完成后,会将外部引用和外部定义链接( link )起来。这就是链接加载器( linking loader )的由来。
  5. 连接加载器让程序员们可以将程序分割成多个可被分别编译、加载的程序段。在程序规模小、链接少的情况下,这个方案一直很好用,但随着程序规模的大幅增长,链接加载器的处理速度跟不上了。后来程序员们只能将加载和链接的过程也进行分离。将耗时较长的部分——链接部分放在了一个单独的程序中去进行,这个程序就是所谓的链接器。链接器的输出是一个已经完成了外部链接的、可重定位的二进制文件,这种文件可以由一个支持重定位的加载器迅速加载到内存中。这使得程序员可以用缓慢的连接产生出可以很快进行多次加载的可执行文件。

第13章 组件聚合

  1. 与构建组件相关的基本原则: REP (复用/发布等同原则) CCP (共同闭包原则) CRP (共同复用原则)
  2. 复用/发布等同原则:软件复用的最小粒度应等同于其发布的最小粒度。从软件设计和架构设计角度来看,ERP 原则就是指组件中的类与模块必须是彼此紧密相关的。也就是说一个组件不能由一组毫无关联的类和模块组成,它们之间应该有一个共同的主题或者大方向。但 REP 原则的薄弱性在于没有清晰的定义出到底如何将类与模块组合成组件( CCP 和 CRP 会从相反的角度对这个原则进行有力的补偿)。
  3. 共同闭包原则:我们应该将那些会同时修改,并且为相同目的而修改的类放到的同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同组件中。CCP 的主要作用就是提示我们要将所有可能会(战略性选择闭包范围)被一起修改的类集中在一处。也就是说,如果两个类紧密相关,不管是在源代码层面还是抽象理念层面永远都会一起被修改,那么它们就应该被归属于同一个组件。当某一类变更出现时,我们需要尽可能做到改变更只影响到有限的相关组件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值