软件设计之美

课前必读

开篇词

  • 设计是为了让软件在长期更容易适应变化。 — Kent Beck

    • Design is there to enable you to keep changing the software easily in the long term.

  • 算法对抗的是数据的规模,而软件设计对抗的是需求的规模。

  • 软件设计学习的难度,不在于一招一式,而在于融会贯通。

    • 软件设计,是一门关注长期变化的学问,因为只有长期的积累,需求才会累积,规模问题才会凸显出来。软件设计,实际上就是应对需求的 “算法”。

  • “软件设计”的两个维度

      1. “了解现有软件的设计”

      1. “自己设计一个软件”

  • 快速了解现有软件设计的方法,那就是抓住这个软件最核心的三个部分

      1. 模型

      1. 接口

      1. 实现

01 软件设计到底是什么

  • 核心的模型

    • 模型,是一个软件的骨架,是一个软件之所以是这个软件的核心 区别于解决简单的问题,软件的开发往往是一项长期的工作,会有许多人参与其中。在这种情况下,就需要建立起一个统一的结构,以便于所有人都能有一个共同的理解。这就如同建筑中的图纸,懂建筑的人看了之后,就会产生一个统一的认识。

      • 模型的粒度可大可小

    • 好的模型

      • 有效地隐藏细节,让开发者易于理解

      • “高内聚、低耦合” 指的就是对模型的要求

    • 模型是分层的

      • 可以不断地叠加,基于一个基础的模型去构建上一层的模型,计算机世界就是这样一点点构建出来的

  • 约束的规范

    • 规范,就是限定了什么样的需求应该以怎样的方式去完成。

    • 常见的问题

        1. 缺乏显式的、统一的规范

        1. 规范不符合软件设计原则

  • 模型与规范的关系

    • 相辅相成

      • 模型要符合一定的规范

      • 规范的制定依赖于模型

  • 做设计的关键是,找到不变的东西

    • 流程是容易调整的,而模型如果变了,这个软件整个就变了

    • 分层不是你的设计,而构建出你的模型才是设计

02 | 分离关注点

  • 分离关注点

    • 定义:将一个模块的不同维度分开

    • 把业务处理和技术实现分离 在真实项目中,程序员最常犯的错误就是认为所有问题都是技术问题,总是试图用技术解决所有问题。任何试图用技术去解决其他关注点的问题,只能是陷入焦油坑之中,越挣扎,陷得越深。

      • 业务与技术的区分

        • 业务人员能理解的就是业务的,比如,订单

        • 业务人员理解不了的就是技术的,比如,多线程。

    • 不同的数据变动方向

      • 高频和低频分离

      • 动静分离,就是把变和不变的内容分开

      • 读写分离,就是把读和写分开

  • 重要性

    • 一方面,不同的关注点混在一起会带来一系列的问题

    • 另一方面,当分解得足够细小,你就会发现不同模块的共性,才有机会把同样的信息聚合在一起

  • 说明

    • 分离关注点是一种意识

      • 这种意识是需要训练的 一旦自己在做设计时,出现纠结或者是觉得设计有些复杂,首先需要想想,是不是因为把不同的关注点混在了一起。

      • 实践结合

        • 在设计评审的 DoD 中,增加一条 “是否考虑了分离关注点”

    • 需要从小事开始练习

  • 实践

    • CQRS(Command Query Responsibility Segregation)

      • 静态上

        • 拆分了这两块的代码。使各自可以采用不同的技术栈,做针对性的调优

      • 动态上

        • 切分了流量,能够更灵活的做资源分配

    • 循环依赖

      • 产生的一个重要原因就是没有分清接口和实现。

03 | 可测试性

  • 原因

    • 软件开发要解决的问题是从需求而来。需求包括两大类

      • 第一类是功能性需求

      • 第二类是非功能性需求

        • 执行质量(Execution qualities)

          • 包括吞吐、延迟、安全

          • 可以在运行时通过运维手段被观察到

        • 演化质量(Evolution qualities)

          • 包括可测试性、可维护性、可扩展性

          • 内含于一个软件的结构之中

  • 可测试性的视角

    • 作为一个衡量标准来考察已有的设计

    • 帮助我们理解软件的发展趋势

  • 实践

    • private 方法怎么测?其实是一个伪命题

      • 要测 private 方法,更多的是因为这个类承担了过多的职责,才会出现层层嵌套的方法,才会不好测。

    • 实例 曾经开发过堆场应用,其中一个步骤是从远端服务器同步到本地服务器,然后再执行本地逻辑。如果每次测试本地逻辑都要从服务端拉取数据的话,就没法自动测了。当时采用的测试方法就是先抓取接口数据生成接口文件,测试就从文件中加载,再运行,最后销毁整个数据库。如果有接口相关的 bug,也同样抓取数据保存,构建一个 bug 号命名的测试方法测试 bug。后来做过系统高可用软件,采用的方法是将代码自动部署到多个 Docker 里,测试代码里依据场景(为了方便,场景还用 DSL 写)比如杀某个 Docker 来测试高可用逻辑是否正常。

了解一个软件的设计

04 | 三步走:如何了解一个软件的设计

  • 模型

    • 作用

      • 模型是这个系统与其他系统有所区别的关键

      • 设计最关键的就是构建出模型

      • 理解一个设计中的模型,可以帮助我们建立起对这个软件整体的认知

    • 示例说明

      • 分布式计算: 需要考虑怎样在不同的节点上调度计算

      • MapReduce: 只要考虑如何把计算分开(Map)最后再汇总(Reduce)

      • Spark: 注意力就集中在要做怎样的计算上

  • 接口

    • 作用

      • 决定了软件通过怎样的方式,将模型提供的能力暴露出去

      • 是与这个软件交互的入口

    • 示例

      • 程序库的接口就是它的 API

      • 工具软件一般会提供命令行接口

      • 业务系统的接口,就是对外暴露的各种接口

  • 实现

    • 作用

      • 软件提供的模型和接口在内部是如何实现的

      • 是软件能力得以发挥的根基

    • 说明

      • 模型和接口的稳定度都要比实现高,实现则是要随着软件发展而不断调整

  • 说明

    • 设计应该遵循一个顺序,先模型,再接口,最后是实现 如果模型都还没有弄清楚,就贸然进入细节的讨论,你很难分清哪些东西是核心,是必须保留的,哪些东西是可以替换的。如果你清楚了解了模型,也就知道哪些内容在系统中是广泛适用的,哪些内容必须要隔离。简单地说,分清模型会帮助你限制实现的使用范围

  • 实践

    • 另一个角度

      • 模型,通常包含两类要素

        • 一是基本元素

        • 二是这些元素之间的关系

05 | 如何分析一个软件的模型

  • 理解一个模型的关键

    • 要知道项目提供了哪些模型,这些模型都提供了怎样的能力。

    • 要了解这个模型设计的来龙去脉,知道它是如何解决相应的问题 理解一个模型,需要了解在没有这个模型之前,问题是如何被解决的,这样,你才能知道新的模型究竟提供了怎样的提升

  • 以Spring DI 容器为例

    • “依赖注入”(Dependency Injection,简称 DI)

      • 总结:Martin Fowler《反转控制容器和依赖注入模式》

      • 有效解决对象创建与组装的问题

    • 很多人习惯性把对象的创建和组装写到了一个类里面

      • 造成的结果就是,代码出现了大量的耦合

        • 错误的做法 class ArticlService {
          private ArticleRepository repository;

          public ArticlService(final Connection connection) {
          this.repository = new DBArticleRepository(connection);
          }
          }

        • 正确的做法 // 创建
          class ArticleService {
          private ArticleRepository repository;

          public ArticleService(final ArticleRepository repository) {
          this.repository = repository;
          }
          }
          // 组装
          ArticleRepository repository = new DBArticleRepository(connection);
          AriticleService service = new ArticleService(repository);

      • 可测试性是衡量设计优劣的一个重要标准

06 | 如何分析一个软件的接口

  • 两个步骤 找主线看文章,看风格看接口。从上到下,从整体到局部。正是读源码的正确步骤。

    • 找主线

      • 方法

        • 找到功能主线,对项目建立起结构性认知

        • 沿着主线把相关接口梳理出来

      • 工具

        • 起步文档

    • 看风格

      • 含义

        • 接口希望你怎么使用它,或怎么在上面继续开发

      • 意义

        • 看出设计者的品味

        • 维护项目的一致性

  • 以Ruby on Rails为例

    • 模型

      • 基于 MVC模型开发的 web 框架

      • 设计理念

        • 约定大于配置

    • 三种接口

      • REST API

        • Web 应用对外暴露的接口

        • 实用、更容易落地

      • API

        • 程序员写程序时用到的接口

        • 简单、表达性好

      • 命令行

        • 开发过程中用到的接口

        • 融合软件工程的最佳实践

07 | 如何分析一个软件的实现

  • 软件的结构

    • 含义

      • 软件的分层

    • 工具

      • 结构图

    • 软件的结构其实也是软件的模型,只不过,它不是整体上的模型,而是展开实现细节之后的模型 对于每个软件来说,当你从整体的角度去了解它的时候,它是完整的一块。但当你打开它的时候,它就变成了多个模块的组合,这也是所谓 “分层” 的意义所在

  • 关键的技术

    • 定义

      • 关键技术是能够让这个软件的 “实现” 与众不同的地方

  • 以 Kafka 为例

    • 模型和接口

      • 以消息队列为核心

      • 以生产和消费为接口

    • 实现

      • 软件结构

        • 生产者、消费者、集群3部分组成

      • 关键技术

        • 软硬接合的思路

        • 磁盘顺序读写的特性

  • 一句话

    • 理解实现,带着自己的问题,了解软件的结构和关键的技术。

设计一个软件 — 程序设计语言

08 _ 语言的模型

  • 前言

    • 每年至少学习一门新语言

      • Andrew Hunt 和 David Thomas 在《程序员修炼之道》(The Pragmatic Programmer)中给程序员们提了一项重要的建议

    • 学习程序设计语言主要是为了学习程序设计语言提供的编程模型

      • 程序设计语言本身也是一个软件,它也包含模型、接口和实现

  • 程序设计语言发展简史

    • 主流路径

      • 汇编语言

        • 引入了助记符

      • 高级程序设计语言

        • Fortran

          • 数据类型

          • 控制结构

        • C

          • 屏蔽了硬件诸多细节

        • C++

          • 兼容 C

          • 增加了面向对象和范型

        • Java

          • 垃圾回收机制

    • 次主流路径

      • 函数式编程

        • LISP

        • 探索

          • 声明式编程

          • DSL

          • 元编程

    • 其他

      • 动态语言

        • 代表

          • PHP、Python、Ruby、Perl

        • 优势

          • 简单、轻巧、门槛低

  • 一切都是语法糖

    • 定义

      • 语法糖(Syntactic sugar)

      • 英国计算机科学家彼得・兰丁发明的一个术语

      • 定义

        • 是那些为了方便程序员使用的语法,它对语言的功能没有影响。

    • 启示

      • 学习语言就是学习其编程模型

      • 不提供新编程模型的语言不值的学习

09 _ 语言的接口

  • 软件设计的发力点

    • 语法

    • 程序库

      • 目的

        • 消除重复

      • 类型

        • 标准库

        • 三方库

          • 包管理器

  • 编程思想

    • 语言设计就是程序库设计

      • 一个经过程序库验证的模式最终成为语言的一部分

        • new

        • synchronized

    • 程序库设计就是语言设计

  • 总结

    • 语法和程序库是在解决同一个问题,二者之间是相互促进的关系

    • 提升软件设计能力,可以从编写程序库入手

      • 1、可以锻炼自己从日常工作中寻找重复

      • 2、可以更好地理解程序设计语言提供的能力

  • 实践

    • 设计模式是缺失的语言特性

      • 如在函数是一等公民的语言中,至少有半打的设计模式是不需要的

    • 所有非平凡的抽象 (abstraction) 在某种程度上都是有漏洞的 (leaky)

      • 封装的程序库只能工作在某个抽象层次上,总会遇到无法在该抽象层次上解决的问题,此时只能绕过这层抽象从更低的抽象层次上解决。

10 _ 语言的实现

  • 运行时

    • 程序设计语言的实现就是支撑程序运行的部分

    • 目的:实现语言的执行模型

    • 软件设计的地基

  • 程序如何运行

    • 对于 Java 来说

      • 了解执行文件结构

        • class 文件结构

      • 了解程序如何加载

        • 加载器的运作和内存的布局

      • 了解指令如何执行

        • 运行机制和字节码的执行

      • 了解更多信息

        • GC、语法、程序树

    • 突破口就在于了解指令是如何执行的

  • 运行时的编程接口

    • 提供方式

      • 以程序库的方式提供

        • getClass

        • Annotation

      • 以标准的方式提供

        • ASM

        • ByteBuddy

    • 作用

      • 实现超过语言本身的能力

11 _ DSL:设计一门自己的语言

  • 《领域特定语言》

    • Martin Fowler

    • 语义模型(Semantic Model)

    • 定义:一种用于某种特定领域的程序设计语言

    • 作用:缩短问题和解决方案之间的距离,降低理解的门槛

  • 领域特定语言

    • 外部 DSL

      • 宿主语言(Host Language)与 DSL 相同

    • 内部 DSL

      • 宿主语言(Host Language)与 DSL 不同

    • 语言工作台(Language Workbench)

  • 常见的 DSL

    • 正则表达式

      • 用于文本处理这个特定领域的 DSL

    • 配置文件

      • 根据需求对软件行为进行定制

      • 以 Nginx 为原型

        • 单独的Web 服务器

        • 反向代理

        • 负载均衡

    • SQL

    • markdown

    • Cron类

    • ansibe

    • pipefile

      • .drone.yml

      • .github/hook.yml

    • 高级编程语言是低级编程语言的 DSL

  • 代码的表达性

    • DSL 的 4 个关键元素

      • 计算机程序设计语言(Computer programming language);

      • 语言性(Language nature);

      • 受限的表达性(Limited expressiveness);

      • 针对领域(Domain focus)。

    • 语言性强调的就是 DSL 要有连贯的表达能力

      • 设计自己的 DSL 时,重点是要体现出意图

      • 写代码时应该关注代码的表达能力

    • 优秀程序员与普通程序员

      • 普通程序员的关注点只在于功能如何实现

      • 优秀的程序员会懂得将不同层次的代码分离开来,将意图和实现分离开来,而实现可以替换

    • 程序设计语言的一个重要发展趋势

      • 声明式编程

  • 重要的设计原则

    • 分离意图和实现

    • 想写好代码,一定要懂得设计

加餐:几门语言

  • C#

    • 微软

      • Anders Hejlsberg

        • 最顶级的程序员

      • J++

        • 庭外和解

    • C#

      • Lambda、类型推演

      • 从语言特性上来看,说 C# 领先 Java 十年并不夸张

      • .NET

        • 一个平台,多个语言

    • Java

      • 一个语言,多个平台

      • JVM

        • 多个语言,多个平台

        • Groovy、Scala、Clojure

  • JavaScript

    • JavaScript

      • Brendan Eich

        • 为了应付工作,因为他当时供职的 Netscape 需要让网页上的元素动起来

      • 仅仅用 10 天就设计出来

      • 真正想做的是一门函数式编程的语言

        • 向现实妥协的结果就是,借助了 C 风格的语法,函数式编程的底子却留在了 JavaScript 里

    • Node.js

      • 归功于 V8 这个 JavaScript 引擎

        • V8 一开始就是一个独立的 JavaScript 引擎

      • 引入了异步 IO 的模型

        • 与 JavaScript 事件驱动的特点相吻合

      • NPM 这个包管理器

        • 降低了众多开发者参与的门槛

      • React、Angular、Vue 等框架

    • 历史包袱

      • 包袱

        • JavaScript 作为一门语言,其问题之多也是由来已久的

        • 沉重的历史包袱让很多人都想开发出新的语言去替代它

      • 很多人把它看成了一种 Web 上的汇编语言

        • CoffeeScript

        • TypeScript

        • 新一代的 JavaScript 标准

      • 取 JavaScript 而代之

        • 仅仅在语言层面屏蔽 JavaScript 是不够的

        • WebAssembly 就是想成为 Web 上真正的汇编

  • Go

    • C

      • C 诞生那个年代,程序的规模还不算太大

      • 现在规模越来越大,大到超出了 C 语言的能力范畴

    • C++

      • 语言特性的试验田,泛型编程,尤其是模板元编程

      • 高手极度喜爱,普通人一脸懵

      • 背负了 C 语言所有的历史负担

        • 如,内存管理

        • 必须对 C++ 极其了解,才能写好 C++

        • 对于一个工程化的语言来说,要求实在是太高了

    • Go

      • 作者

        • Ken Thompson

          • C 语言的作者

        • Rob Pike

          • Unix 先驱

      • 特性

        • 接口设计和并发上的处理方式优雅

      • 成绩

        • 系统编程领域并没有太多的机会留给它

          • 实时性和性能要求极高的领域

          • GC差

        • 云计算领域还有一些基础设施

          • Docker

          • Kubernetes

  • Rust

    • Mozilla

      • Graydon Hoare 的个人项目

    • 特性

      • 对初学者并不友好

        • “变” 量缺省是不变的

        • 先要了解所有权的概念

    • LLVM

      • 传统的工具链 GCC 太过沉重

      • LLVM 把编译器的前端和后端分离开来

        • 语言开发者只要关注前端,设计好各种语言特性

        • 可以利用 LLVM 的后端进步的优势

          • 如,不断优化带来的性能提升

    • 成绩

      • C 语言替代者的竞争中,Rust 值得期待

设计一个软件 — 编程范式

12 | 编程范式

  • 定义

    • 编程范式(Programming paradigm)

      • 程序的编写模式

      • 意味着主要使用的是什么样的代码结构

    • 主流的编程范式

      • 结构化编程(structured programming)

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

      • 函数式编程(functional programming)

      • 逻辑式编程

        • Prolog

  • 意义

    • 编程范式不仅仅是提供了一个个的概念

    • 更重要的是,它对程序员的能力施加了约束

      • 结构化编程,限制使用 goto 语句,它是对程序控制权的直接转移施加了约束

      • 面向对象编程,限制使用函数指针,它是对程序控制权的间接转移施加了约束

      • 函数式编程,限制使用赋值语句,它是对程序中的赋值施加了约束

    • 当 Bob 大叔说出那句,“编程范式本质是从某方面对程序员编程能力的限制和规范” 时,真有些振聋发聩

  • 吸取不同编程风格中的优秀元素

    • 采用面向对象来组织程序中的各个模块

    • 采用函数式编程指导类的接口设计

    • 在具体的实现中使用结构化编程提供的控制结构

13 | 结构化编程

  • 历史

    • 结构化是相对非结构化编程而言的

    • goto 是有害的

      • goto 是一种对程序控制权的直接转移

      • Edgar Dijkstra

        • Go To Statement Considered Harmful

  • 功能分解

    • 将模块按功能进行拆分

    • 促进各种结构化分析和结构化设计方法的兴起

  • 不足之处

    • 抽象级别不够高

    • 模块间依赖关系过强

    • 可测试性不够

  • 扩展

    • 基于统计思想设计的算法

      • hyperloglog

      • 布隆过滤器

14 | 面向对象之封装

  • 历史

    • “面向对象” 这个词是由 Alan Kay 创造的

      • 2003 年图灵奖的获得者

      • “I made up the term “object-oriented,” and I can tell you I did not have C++ in mind.” Alan Kay

      • “Java and C++ make you think that the new ideas are like the old ones. Java is the most distressing thing to hit computing since MS-DOS.” Alan Kay

    • 最初的构想

      • 对象就是一个细胞

      • 当细胞一点一点组织起来,就可以组成身体的各个器官

      • 再一点一点组织起来,就构成了人体

      • 当你去观察人的时候,就不用再去考虑每个细胞是怎样的

    • 面向对象给了我们一个更宏观的思考方式

  • 封装

    • 封装是面向对象的根基

    • 定义

      • 把紧密相关的信息放在一起,形成一个单元

      • 一层一层地逐步向上不断构建更大的单元

    • 重点在于对象提供了哪些行为,而不是有哪些数据

      • 函数是接口,而数据是内部的实现

      • 接口是稳定的,实现是易变的

    • 设计一个类步骤

      • 先要考虑其对象应该提供哪些行为

      • 然后根据行为提供对应的方法

      • 最后才是考虑实现这些方法要有哪些字段

  • 减少暴露接口

    • 之所以我们需要封装,就是要构建一个内聚的单元

    • 减少这个单元对外的暴露

      • 减少内部实现细节的暴露

      • 减少对外暴露的接口

    • 统一的原则:最小化接口暴露

  • 不局限于面向对象的封装

    • 可运用于非面向对象的程序设计语言中

    • 提高代码的模块性

  • 一句话

    • 基于行为进行封装,不要暴露实现细节,最小化接口暴露

15 | 面向对象之继承

  • 类型

    • 实现继承

      • 站在子类的角度往上看,面对的是子类

    • 接口继承

      • 站在父类的角度往下看,面对的是父类

        • 更多是与多态相关

  • 误区

    • 把实现继承当作一种代码利用的方式

    • 很大程度上是受了语言的局限

  • 合理方式

    • 通用原则

      • 组合优于继承

    • 编程思想

      • 面向组合编程

  • 实践

    • DCI (Data,Context 和 Interaction)

      • 是一种特别关注行为的模式 (可以对应 GoF 行为模式)

        • MVC 模式是一种结构性模式

      • https://www.jdon.com/dci.html

    • 属性和组合的区别

      • 属性就是一个类固有的性质,就像一个人的身高体重

      • 组合讲的是与其它部分的关系,比如,车有两个轮胎

    • 小类大对象

      • 是用 C++ 的方式描述的

      • 每个类是小的,简言之代码都不多

      • 最终形成的对象是个大的,因为它把所有的小类组合了起来

      • 说明

        • 在运行时,用到的是对象

        • 设计中,你用到的是接口,是类层面的东西。

    • 设计的角度

        1. 作抽象 (找共性,文档中的系统模型,代码详细设计的接口)

        1. 作分解 (找特性,实现)

        • 分解的目的就是将处理逻辑和数据的不同点突出出来

          • 根据不同的差异将各种实现进行相应的组合,支持接口功能

          • 分解做好了,代码重复性就降低了

        • 两个方面着手

            1. 数据角度

            1. 数据处理角度

  • 要求

    • 是否能够快速学习一个新东西,就是程序员之间体现出差异的地方

16 | 面向对象之多态

  • 理解多态

    • 概念

      • 一个接口多种形态

      • 基于对象和面向对象的分水岭

    • 前提

      • 理解接口的价值

      • 谨慎的选择接口中的方法

    • 做法

      • 找出不同事物的共同点,建立起抽象

        • 关键在分离关注点上

      • 面向接口编程

        • 在构建抽象上,接口扮演着重要的角色

            1. 接口将变的部分和不变的部分隔离开来

            1. 接口是一个边界

            • 清晰界定不同模块的职责是很关键的

            • 模块之间彼此通信最重要的就是通信协议

            • 这种通信协议对应到代码层面上,就是接口。

  • 实现多态

    • 做法

      • 将一种常见的编程结构升华为语法

    • 作用

      • 函数指针的使用就得到了限制

      • 降低程序员犯错的几率

  • 没有继承的多态

    • 只要遵循相同的接口,就可以表现出多态

      • 多态不一定依赖于继承

    • 面向对象的编程,更重要的是封装和多态

  • 面向对象编程的三个特点的定位

    • 封装

      • 面向对象的根基,软件就是靠各种封装好的对象逐步组合出来的

    • 继承

      • 给了继承体系内的所有对象一个约束,让它们有了统一的行为

    • 多态

      • 让整个体系能够更好地应对未来的变化

  • 要求

    • 对程序员来说,识别出变与不变,是一种很重要的能力

  • 一句话

    • 建立起恰当的抽象,面向接口编程

17 | 函数式编程

  • 函数式编程是一种编程范式

    • 提供给我们的编程元素就是函数

      • 这个函数是来源于数学的函数

    • Lambda函数

      • 起源是数学家 Alonzo Church 发明的 Lambda 演算(Lambda calculus,也写作 λ-calculus)

      • 可以简单地把它理解成匿名函数

      • Lambda 演算和图灵机是等价的

        • 都是那个年代对 “计算” 这件事探索的结果

        • 大多数程序设计语言都是从图灵机的模型出发的

        • 也有少部分从 Lambda 演算出发。比如LISP

    • 函数式编程概念

      • 虽然说函数式编程语言早早地就出现了

      • 这个概念却是 John Backus 在其 1977 年图灵奖获奖的演讲上提出来

  • 一等公民(first-class citizen)

    • 它可以按需创建;

    • 它可以存储在数据结构中;

    • 它可以当作实参传给另一个函数;

    • 它可以当作另一个函数的返回值。

  • 一句话

    • 函数式编程的要素是一等公民的函数,如果语言不支持,可以自己模拟。

18 | 函数式编程之组合性

  • 来源于函数式编程的概念

    • GC

    • Lambda

    • 流(Stream)

    • Optional

    • 惰性求值

  • 函数式编程在设计上对我们帮助最大的两个特性

    • 组合性

    • 不变性

  • 组合行为的高阶函数

    • 高阶函数(High-order function)

    • 定义

      • 可以接收函数作为输入,或者返回一个函数作为输出

    • 一个重要作用

      • 可以用它去做行为的组合

  • 组合性

    • 函数式编程的组合性,就是一种好的设计方式

    • 能把模型拆解成多个可以组合的构造块,这个过程非常考验人的洞察力,也是 “分离关注点” 的能力

      • 这个过程可以让人得到一种智力上的愉悦

  • 列表转换思维

    • map

      • 把一组数据通过一个函数映射成另一组数据

    • filter

      • 把一组数据通过一个条件过滤,只留下满足条件的数据

    • reduce

      • 把一组数据按某个规则,归约为一个数据

  • 组合性的区别

    • 面向对象编程

      • 元素是类和对象

      • 对系统结构进行搭建

    • 面向函数编程

      • 元素是函数

      • 对函数接口进行设计

    • 总结

      • 可以用面向对象编程的方式对系统的结构进行搭建,然后,用函数式编程的理念对函数接口进行设计

  • 一句话

    • 设计可以组合的函数接口

19 | 函数式编程之不变性

  • 不变性的意义

    • 函数之间的组合很好地体现出了函数式编程的巧妙之处

    • 学习编程范式不仅要看它提供了什么,还要看它约束了什么

    • 概念

      • 一个初使化后就不再改变的量

      • 当你使用一个值的时候,值是不变的

    • 应用

      • 编写不变类

        • 所有字段只在构造函数中初使化

        • 所有方法都是纯函数

        • 有改变时返回一个新的对象,不修改原有字段

    • 意义

      • 保证不会显示改变一个量

  • 纯函数

    • 概念

      • 对相同的输入有相同的输出

      • 没有副作用

    • 应用

      • 不修改任何字段

      • 不调用修改字段的方法

    • 意义

      • 不会隐式改变一个量

  • 实践

    • 变化与不变

      • 变化是需求层面的不得已

      • 不变是代码层面的努力控制

    • 函数式编程的 “不变性” 也是 OCP 原则的一种体现

  • 编程原则

    • 尽可能编写不变类和纯函数

      • Java 就尽可能多使用 final

      • C/C++ 就多写 const

加餐 | 函数式编程拾遗

  • 惰性求值

    • Lazy Evaluation

    • 定义

      • 一种求值策略,它将求值的过程延迟到真正需要这个值的时候

    • 好处就在于可以规避一些不必要的计算,尤其是规模比较大,或是运行时间比较长的计算

      • Proxy 模式,它就是采用了惰性求值的策略

  • 无限流

    • Infinite Stream

    • 无限长集合真正预置进去的是,元素的产生规则

      • 如:产生一个自然数的集合

      • Stream.iterate(1, number -> number + 1)

      • 定义了这个集合的第一个元素,然后给出了后续元素的推导规则

  • 记忆

    • Memoization

    • 定义

      • 在计算中,记忆是一种优化技术,主要用于加速计算机程序,其做法就是将昂贵函数的结果存储起来,当使用同样的输入再次调用时,返回其缓存的结果。

  • Optional

    • Optional 将对象封装起来的做法来自于函数式编程中一个叫 Monad 的概念

    • Option 的价值在于类型而非对象

  • 参考

    • 《计算机程序的构造与解释》

设计一个软件 — 设计原则与模式

20 | 单一职责原则

  • SOLID 原则

    • 由 Robert Martin 提出并逐步整理和完善的

      • 《敏捷软件开发:原则、实践与模式》

      • 《架构整洁之道》

  • 定义

    • 误区

      • 一个类只干一件事

    • 初使

      • 一个模块应该有且仅有一个变化的原因

        • 将变化纳入了考量

          • 这是与一个类只干一件事之间最大差别

    • 进阶

      • 一个模块应该对一类且仅对一类行为者(actor)负责

        • 将变化的来源纳入了考量

  • 本质

    • 理解单一职责原则本质上就是要理解分离关注点

  • 想理解好单一职责原则

    • 需要理解封装

      • 知道要把什么样的内容放到一起

    • 需要理解分离关注点

      • 知道要把不同的内容拆分开来

    • 需要理解变化的来源

      • 知道把不同行为者负责的代码放到不同的地方

  • 一句话

    • 应用单一职责原则衡量模块,粒度越小越好

21 | 开放封闭原则

  • 定义

    • 软件实体(类、模块、函数)应该对扩展开放,对修改封闭

      • Bertrand Meyer

      • 《面向对象软件构造》(Object-Oriented Software Construction)

    • 如何理解

      • 对扩展开放

        • 新的需求用新代码实现

      • 对修改封装

        • 不修改已有代码

    • 给软件设计提出了一个极高的要求:不修改代码

  • 构建扩展点

    • 每个扩展点都是一个需要设计的模型

      • 阻碍程序员们构造出稳定模块的障碍,其实是构建模型的能力

    • 构建模型的难点

      • 首先在于分离关注点

      • 其次在于找到共性

  • 更广泛的用途

    • 提供机制,而不是策略

      • 《Unix 编程艺术》

    • 向提供足够扩展能力的软件接口学习

      • 插件机制

    • 帮助我们改进自己的系统

      • 查看自己的源码控制系统,找出那些最经常变动的文件,它们通常都是没有满足开放封闭原则的,而这可以成为我们改进系统的起点

  • 一句话

    • 设计扩展点,迈向开放封闭原则

22 | Liskov 替换原则

  • 起源

    • 2008 年,图灵奖授予 Barbara Liskov

    • 1988 年,定义了 LSP 这里需要如下替换性质:若每个类型 S 的对象 o1,都存在一个类型 T 的对象 o2,使得在所有针对 T 编程的程序 P 中,用 o1 替换 o2 后,程序 P 行为保持不变,则 S 是 T 的子类型。

  • 如何理解

    • 表述

      • 子类型必须能够替代其父类型

    • 是一个把继承体系设计好的设计原则

    • 理解角度

      • 站在父类角度

        • 正确

      • 站在子类角度

        • 错误

          • 关心子类是一种实现继承的表现

        • 现象

          • 出现 RTTI 代码

    • RTTI

      • 示例

        • 如:需要用instanceof先判断子类型是什么,再做相应的业务处理 void handle(final Handler handler) {
          if (handler instanceof ReportHandler) {
          // 生成报告
          ((ReportHandler)handler).report();
          return;
          }

          if (handler instanceof NotificationHandler) {
          // 发送通知
          ((NotificationHandler)handler).sendNotification();
          }

          }

        • 虽然都是同一父类的子类,但它们没有统一的处理接口

      • 以多态应用为荣,以分支判断为耻

  • 如何满足

    • 对象体系要有统一的接口

    • 子类要满足 IS-A 的关系

      • IS-A

        • 表述

          • 如果A 是 B 的子类,需要满足A 是一个 B

        • 关系

          • 继承要符合 IS-A

          • 判定是基于行为的

  • 更广泛的用途

    • 接口设计

      • 公开接口是最宝贵的资源,不能随意添加

  • 总结

    • LSP 的根基在于继承,但显然接口继承才是重点

23 | 接口隔离原则

  • 如何理解

    • 表述

      • 不应强迫使用者依赖于它们不用的方法。

      • No client should be forced to depend on methods it does not use.

    • 常见错误

      • 分不清使用者和设计者

  • 应用场景

    • 接口过胖

      • 接口内包含太多内容

      • 应把大接口分解成一个个小接口

    • 接口设计的 SRP

      • 每个使用者面向的接口都是一种角色接口

        • 角色接口(Role Interface)

          • Martin Fowler

          • 每个人在实际生活中扮演着不同的角色一样

            • 父亲、儿子、员工、公民

            • 多个角色由一个人承担

      • 在做接口设计时,应该关注不同的使用者

  • 更广泛的用途

    • 不要依赖于我们不需要的东西

    • 指导我们在更高层次设计

24 | 依赖倒置原则

  • 高层&低层

    • 表述

      • 高层模块不应该依赖于低层模块,二者依赖于抽象

      • High-level modules should not depend on low-level modules. Both should depend on abstractions.

    • 关键点

      • 依赖倒置

        • 让高层模块不再依赖于低层模块

    • 做法

      • 引入一个中间层,也就是模型

  • 细节&抽象

    • 表述

      • 抽象不应该依赖于细节,细节依赖于抽象

      • Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

    • 编码规则

      • 任何变量都不应该指向一个具体类

      • 任何类都不应继承自具体类

      • 任何方法都不应该改写父类中已经实现的方法

    • 组装代码与模型代码分开

      • 分离关注点,将变化的与不变有效的区分开

  • 实践

    • 名言

      • 计算机科学中的所有问题都可以通过引入一个间接层得到解决。

      • David Wheeler

      • All problems in computer science can be solved by another level of indirection

    • 依赖倒置的关键点

      • 高层模块和底层模块之间抽象出一个通用的稳定不变的公共接口

      • 这个接口作为了一个隔板,将稳定部分和易变部分隔离开

        • 用到开闭原则 - 分离关注点,找共性,对修改封闭,对扩展开放

      • 当可变部分扩展业务功能时,只要实现接口方可

        • 接口的粒度需要接口隔离原则和单一职责原则来指导

    • 锻炼构建模型的能力

      • 不理解 DIP 的程序员,就只能写功能,不能构建出模型

      • 好莱坞规则

        • Don’t call us, we’ll call you

        • 别调用我,我会调你的

        • 这是一个框架才会有的说法

          • 有了一个稳定的抽象,各种具体的实现都应该是由框架去调用

  • 更广泛的用途

    • DI 容器负责将具体类组合起来

    • 对于任何一个项目而言,了解不同模块的依赖关系是一件很重要的事

      • 找一些工具去生成项目中的依赖关系图

      • 用 DIP 作为评判标准,衡量项目中在依赖关系中的表现

      • 找到项目的改造着力点

25 | 设计模式

  • 定义

    • 一种特定的解决方案

      • 所谓模式,其实就是针对的就是一些普遍存在的问题给出的解决方案

    • 历史

      • 起源于建筑领域,建筑师克里斯托佛・亚历山大曾把建筑中的一些模式汇集成册

      • 最早是 Kent Beck 和 Ward Cunningham 探索将模式这个想法应用于软件开发领域

      • 之后,Erich Gamma 把这一思想写入了其博士论文

      • 《设计模式》这本书出版

        • Erich Gamma

        • Richard Helm

        • Ralph Johnson

        • John Vlissides

  • 误区

    • 学习设计模式不要贪多求全,那注定会是一件费力不讨好的事

    • 有一些模式,如果你不是编写特定的代码,你很可能根本就用不上

    • 有效地学习设计模式

      • 每一个模式都是一个特定的解决方案

      • 学习设计模式不仅仅要学习代码怎么写,更重要的是要了解模式的应用场景

    • 设计模式的意义

      • 设计模式在某种意义上就是为了解决语言自身缺陷的一种权宜之计

        • Peter Norvig,Google 公司的研究总监,早在 1996 年就曾做过一个分享《动态语言的设计模式》

        • 语言本身的局限造成了一些设计模式的出现

  • 如何学习

    • 了解模式的应用场景

      • 设计模式只是设计原则在特定场景下的应用

      • 每个模式都只是一个特定场景下的解决方案

    • 认识到:设计模式背后是设计原则

  • 开眼看模式

    • 要看到语言的局限

    • 设计模式本身也在随着语言演变

  • 一句话

    • 学习设计模式,从设计原则开始,不局限于模式。

26 | 简单设计

  • 说明

    • 下面这些原则并不是指导你具体如何编码的原则

    • 更像是一种思考方法、一种行为准则

  • KISS

    • 表述

      • 这条原则其实是出自美国海军

      • 保持简单、愚蠢

        • Keep it simple, stupid

      • 保持简单能够让系统运行得更好

    • 举例

      • 如何有现成库,就不要自己写

      • 能用文本做协议就别用二进制

    • 意义

      • KISS 是一个很好的指引

      • 这种级别的原则听上去很有吸引力,但问题是,你并不能用它指导具体的工作

  • YAGNI

    • 表述

      • 这个说法来自于极限编程社区(Extreme Programming,简称 XP)

      • 如非必要,勿增功能

    • 举例

      • Word 和 Markdown

    • 意义

      • YAGNI 是一种上游思维,就是尽可能不去做不该做的事,从源头上堵住

      • 从某种意义上说,它比其他各种设计原则都重要

  • DRY

    • 表述

      • 源自 Andy Hunt 和 Dave Thomas 的《程序员修炼之道》(The Pragmatic Programmer)

      • 定义

        • 在一个系统中,每一处知识都必须有单一、明确、权威地表述。

        • Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

    • 举例

      • 消除重复的代码

    • 意义

      • 减少重复,减少后期维护的成本

      • DRY 针对的是你对知识和意图的复制

        • 最浅层的理解就是 “不要复制粘贴代码”

        • 两段代码不同,但表达相同也不符合 DRY

        • DRY 原则并不局限于写代码

  • 简单设计

    • 定义

      • 简单设计(Simple Design)原则

      • 原则来自极限编程社区

      • 提出者是 Kent Beck

    • 4 条规则

      • 通过所有测试

        • 保证系统能够按照预期工作

        • 有配置的自动化测试,保证测试覆盖的大多数场景

        • Test Driven Development,简称 TDD

      • 消除重复

        • 需要你对分离关注点有着深刻的认识

      • 表达出程序员的意图

        • 指出重构的方向

        • 需要编写有表达性的代码

      • 让类和方法的数量最小化

        • 不要过度设计

          • 除非你已经看到这个地方必须要做一个设计

          • 如,留下适当的扩展点

设计一个软件 — 设计方法

27 | 领域驱动设计

  • 结构化编程的思路误区

    • 一上来会先设计数据库

    • 认为程序就是数据加函数

      • 数据,存到数据库里

      • 函数,对数据库表进行增删改查

  • 领域驱动设计

    • DDD 的根基

      • 通用语言(Ubiquitous Language)

      • 模型驱动的设计(Model-Driven Design)

      • 领域驱动设计的过程,就是建立起通用语言和识别模型的过程

    • 作用

      • 接近业务与开发之间的距离

      • 提供一套标准的建模方法

    • 应用场景

      • 微服务

        • 微服务的难度并不在于将一个系统拆分成若干的服务

        • 而在于如何有效地划分微服务

        • DDD 才是最恰当的指引

  • 通用语言

    • 定义

      • 在业务人员和开发人员之间建立起的一套共有的语言

      • 软件设计是要在问题和解决方案架设一座桥梁,好的设计要更接近问题

        • 开发人员熟悉解决方案,但是对业务一端理解则通常不够充分

        • 通用语言所做的事情,就是把开发人员的思考起点拉到了业务上,也就是从问题出发

    • 事件风暴(Event Storming)

      • 让业务人员和开发人员在一起

        • 目标:最后让两方都能听懂

      • 它的关注点在于领域事件

        • 领域事件是用来记录业务过程中发生过的重要事情

          • 如,作为电商工作人员,想知道产品是不是已经上架了,领域事件就是产品已上架

          • 如,作为消费者,会关心我的订单是不是下成功了,领域事件就是订单已下

      • 主要分成三步

        • 第一步就是把领域事件识别出来

        • 第二步就是找出这些动作,也就是引发领域事件的命令

          • 如:产品已上架是由产品上架这个动作引发的

          • 如:订单已下单就是由下单这个命令引发的

        • 第三步就是找出与事件和命令相关的实体或聚合

          • 如,产品上架就需要有个产品(Product)

          • 如,下单就需要有订单(Order)

    • 模型驱动设计

      • 将设计分成了两个阶段

        • 战略设计(Strategic Design)

          • 高层设计

            • 指将系统拆分成不同的领域

          • 给了我们一个拆分系统的新视角:按业务领域拆分

            • 如,把一个电商系统拆分成产品域、订单域、支付域、物流域等

            • 拆分成领域之后,我们识别出来的各种业务对象就会归结到各个领域之中

            • 要在不同的领域之间设计一些交互的方式

              • 不同领域的业务对象会进行交互,如,要知道订单的物流情况。

        • 战术设计(Tactical Design)

          • 低层设计

            • 指如何具体地组织不同的业务模型

          • 还要考虑模型之间的关系

            • 如,哪些模型要一起使用,可以成为一个聚合

            • 还需要考虑这些模型从哪来、怎样演变

            • DDD 提供了一些标准的设计概念,比如仓库、服务等

    • 一句话

      • 建立一套业务人员和开发人员共享的通用语言

28 | 战略设计

  • DDD概念分类标准

    • 做业务的划分

      • 一部分是为了将不同的业务区分开来,也就是要将识别出来的业务概念做一个划分

    • 落地成解决方案

      • 另一部分则是将划分出来的业务落实到真实的解决方案中

  • 业务概念的划分

    • 领域(Domain)

      • 对应软件开发要解决的问题

      • 分而治之

    • 子域(Subdomain)

      • 核心域(Core Domain)

        • 是整个系统最重要的部分,是整个业务得以成功的关键

        • Eric Evans 曾提出过几个问题,帮我们识别核心域

          • 为什么这个系统值得写?

          • 为什么不直接买一个?

          • 为什么不外包?

      • 支撑域(Supporting Subdomain)

        • 概念

          • 不是你的核心竞争力,但却是系统不得不做的东西

          • 市场上也找不到一个现成的方案

        • 举例

          • 排行榜功能

      • 通用域(Generic Subdomain)

        • 概念

          • 行业里通常都是这么做

          • 即便不自己做,也并不影响你的业务运行

        • 举例

          • 如, App 通知

    • 投资策略

      • 核心域要全力投入

      • 支撑域次之

      • 通用域甚至可以花钱买服务

  • 业务概念的落地

    • 首先要解决的就是这些子域如何组织的问题

      • 是写一个程序把所有子域都放在里面呢

      • 还是每个子域做一个独立的应用

      • 抑或是有一些在一起,有一些分开

    • 限界上下文(Bounded Context)

      • 概念

        • 它形成了一个边界,一个限定了通用语言自由使用的边界

        • 一旦出界,含义便无法保证

      • 与子域的关系

        • 子域和限界上下文不一定是一一对应的

        • 可能在一个限界上下文中包含了多个子域

        • 也可能在一个子域横跨了多个限界上下文

      • 示例

      • 与微服务关系

        • 最纠结的问题就是如何划分服务边界

        • 限界上下文的出现刚好与微服务的理念契合,每个限界上下文都可以成为一个独立的服务

      • 限界上下文的重点

        • 完全独立的

        • 不会为了完成一个业务需求要跑到其他服务中去做很多事

    • 上下文映射图(Context Map)

      • 定义

        • 一种描述方式,将不同限界上下文之间交互的方式描述出来

      • DDD 给我们提供了一些描述这种交互的方式

        • 合作关系(Partnership)

        • 共享内核(Shared Kernel)

        • 客户 - 供应商(Customer-Supplier)

        • 跟随者(Conformist)

        • 防腐层(Anticorruption Layer)

          • 必须要记住

          • 定义

            • 指我们要在外部模型和内部模型之间建立起一个翻译层,将外部模型转化为内部模型

          • 最具防御性的一种关系

        • 开放主机服务(Open Host Service)

        • 发布语言(Published Language)

        • 各行其道(Separate Ways)

        • 大泥球(Big Ball of Mud)

          • 你要规避的

      • 示例

      • 意义

        • 可以帮助我们理解系统的各个部分之间,是怎样进行交互的

        • 帮我们建立一个全局性的认知,而这往往是很多团队欠缺的

29 | 战术设计

  • 角色:实体、值对象

    • 识别出一个一个的模型,其实,就是识别名词

      • 要识别的名词包括了实体和值对象

    • 实体

      • 概念

        • 实体(Entity)指的是能够通过唯一标识符标识出来的对象

      • 举例

        • 订单的订单号

    • 值对象

      • 概念

        • 表示一个值,会有很多的属性

      • 举例

        • 如,订单地址

        • 由省、市、区和具体住址组成

      • 同实体的差别

        • 没有标识符

        • 之所以它叫值对象,是因为它表现得像一个值

      • 误区

        • 一个字符串表示电话号码

          • 有格式规则

        • 用一个 double 类型表示价格

          • 要有精度的限制

        • 值对象的意义

          • 不用一个类将它封装起来,这种行为就将散落在代码的各处,变得难以维护

    • 区分实体和值对象

      • 目的

        • 把变的对象和不变的对象区分开

      • 分析

        • 实体:在时间上有连续性,并且有唯一标识可以来区分的对象,具有生命周期和行为。

        • 值对象:用来描述事物的,不区分谁是谁的,不可变的对象,不具有生命周期和行为。

      • 启示

        • 在 DDD 的对象设计中,对象是有行为的

        • 只有数据的对象是封装没做好的结果,一个好的封装应该是基于行为的

  • 关系:聚合和聚合根

    • 聚合(Aggregate)

      • 概念

        • 就是多个实体或值对象的组合

        • 这些对象之间是一个整体

          • 同生共死的关系

      • 实例

        • 如,一个订单里有很多个订单项,如果这个订单作废了,这些订单项也就没用了

          • 可以把订单和订单项看成一个单元,订单和订单项就是一个聚合

      • 注意

        • 聚合要保证事务(Transaction)一致性

          • 简言之,就是要更新就一起更新,要删除就一起删除

    • 聚合根(Aggregate Root)

      • 概念

        • 从外部访问这个聚合的起点

        • 一个聚合的唯一根就是聚合根

      • 注意

        • 当你纠结于技术时,先想想自己是不是解错了问题

    • 识别聚合

      • 是聚合

        • 可以一次都拿出来

      • 不是聚合

        • 靠标识符按需提取

  • 互动:工厂、仓库、领域服务、应用服务

    • 事件

      • 领域事件

        • 概念

          • 相当于记录了业务过程中最重要的事情

        • 作用

          • 作为一条主线,帮我们梳理业务上的变化

          • 还可帮助我们让系统达成最终一致的状态

    • 运作

      • 领域服务(Domain Service)

        • 操作的目标

          • 领域对象,更准确地说,它操作的是聚合根

      • 工厂(Factory)

        • 用途

          • 创建对象的动作

      • 仓库(Repository)

        • 用途

          • 保存变更的结果

        • 可以简单地把它理解成持久化操作

      • 应用服务

        • 用途

          • 可以扮演协调者的角色,协调不同的领域对象、领域服务等

          • 完成客户端所要求的各种业务动作

          • 所以,也有人把它称之为 “工作流服务”

            • 核心业务逻辑之外杂七杂八的东西

              • 如,用户要修改一个订单,但首先要保证这个订单是他的

            • 一些与业务逻辑无关的内容都会放到应用服务中完成

              • 如,监控、身份认证等

        • 和领域服务之间最大的区别

          • 领域服务包含业务逻辑,而应用服务不包含

  • 扩展

    • Vaughn Vernon

      • 先阅读《领域驱动设计精粹》

      • 再看《实现领域驱动设计》

  • 一句话

    • 战术设计,就是按照模板寻找相应的模型

巩固篇

30 | 程序库的设计

  • 问题

    • 解释

      • 实现一个什么样的程序库

      • 从一个要解决的问题出发

    • 以 Moco 为例

      • 解决集成的问题

  • 需求

    • 解释

      • 把问题变成一个可以下手解决的需求

    • 以 Moco 为例

      • 正常需求

        • 支持配置

        • 独立部署

        • 通用方案

      • 额外需求

        • 有一个有表达性的 DSL

  • 解决方案

    • 解释

      • 抽丝剥茧,拿掉无关的信息,找最核心的部分

      • 根基

        • 分离关注点

    • 以 Moco 为例

      • 核心问题

        • 模拟服务到底是做什么的

      • 单元测试

        • 帮助锁定目标

  • 设计

    • 基础设计

      • 考虑提供怎样的功能

      • 组合相关元素,构建核心模型

      • 以 Moco 为例

        • RequestMatcher

        • ResponseHandler

    • 扩展设计

      • 不断应对新的变化

      • 以 Moco 为例

        • 大部分需求不用改动基础模型

        • template 扩展基础模型

  • 实践

    • https://github.com/dreamhead/moco

  • 一句话

    • 注意发现身边的小问题,用一个程序库或工具解决它

31 | 应用的设计

  • 职责划分

    • 划分出不同的职责部分

      • 分离关注点

    • 以指数系统为例

      • 指标获取

      • 公式计算

  • 更进一步

    • 区分问题和解决方案

      • 业务人员关注问题

      • 开发人员关注解决方案

        • 千万别混淆问题和解决方案

    • 重新审视解决方案

      • 没有自动化

      • 开发人员修改代码

      • 开发人员修改配置

      • 业务人员修改配置

    • 以指数系统为例

      • 给业务人员配置接口

      • 解析文本执行

  • 更广泛的用途

    • 适用于物联网系统

    • 适用于 APM 类应用

  • 一句话

    • 一个更好的设计从拒绝低水平重复开始,把工作做成有技术含量的事情

32 | 应用的改进

  • 从目标开始

    • 一个目标

      • 要做设计上的改进

      • 不能只改进实现

    • 一个问题

      • 如果有机会从头设计系统,它应该是什么样的

    • 一个共识

      • 设计系统和实施系统改进是两个不同的问题

      • 可以分阶段地进行

  • 入手的起点

    • 为什么入手起点是接口

      • 模型能力的体现

      • 系统内部状态发生改变的原因

    • 什么样的接口

      • 传统意义的接口

      • 各种后台服务

  • 重新设计

    • 难点

      • 不要回到老路上

    • 合理做法

      • 积小胜为大胜

        • 永远不要指望一个真实的项目停下来,一步到位地进行改进

        • 既有项目的设计有一个很大的问题就是各种信息混在一起,而能够把不同的信息拆分开来,对于设计而言,就是一个巨大的进步

    • 关键点

      • 相关利益人达成共识

    • 启示

      • 可以运用在更小的模块

  • 一句话

    • 改进既有设计,从做一个正常的设计开始,小步向前

结束语

  • 《10x 程序员工作法》

    • 藏经阁的目录

    • 已经结构化了的软件开发的各种最佳实践

  • 《软件设计之美》

    • 构建软件设计知识大厦的一种尝试

  • 设计需要沟通

    • 沟通确实是程序员成长过程中一个重要的阻碍

  • 经验积累

    • 普通程序员和高手之间的差别就在于此,普通程序员凭直觉做事,高手却是把专业的做法训练成直觉

  • 结束

    • Now this is not the end. It is not even the beginning of the end. But it is perhaps the end of the beginning.

      • 丘吉尔在阿拉曼战役庆功宴上发表的演讲