《程序员的底层思维》学习笔记目录
原书:《程序员的底层思维》 | 作者:张建飞
- 把复杂的问题变成“填空题”
- 直观性:通过矩阵把多维度的信息放在一起进行对比分析,使我们能清晰的看到问题全貌,从而做出合理的决策
业务场景和业务流程
选一个自己喜欢的 App 在线观看影片
- 业务场景:不同用户身份(游客、普通会员、VIP 会员、SVIP 会员)观看影片
- 业务流程:预览精彩片段、播放正片、提升观影体验
用户身份 | 预览精彩片段 | 播放正片 | 提升观影体验 |
---|
游客 | 点击播放 | 无此功能 | 无此功能 |
普通会员 | 点击播放 | 播放 | 1. 倍速播放2. 清晰度(720P、1080P)3. 音质(标准、高清) |
VIP 会员 | 点击播放 | 播放 | 1. 倍速播放2. 清晰度(720P、1080P、2K)3. 音质(标准、高清、超高) |
SVIP 会员 | 点击播放 | 播放 | 1. 倍速播放2. 清晰度(720P、1080P、2K、4k)3. 音质(标准、高清、超高、原声) |
通过对比
- 普通会员、VIP 会员、SVIP 会员可以复用同一套流程编写代码
- 游客的业务相对简单,更适合独立编码,便于理解
相关知识
扩展知识
决策表
参考:《领域特定语言》Martin Fowler 著 | 7.1 决策表 | 第 48 章 决策表(Decision Table)
- 决策表特别容易被非程序员理解,因此很适合用来与领域专家沟通
- 由于决策表就是一张表格,因此也很适合在电子数据表中编辑
- 如果用 DSL 来展现的话,你的程序就有了“让领域专家直接编辑”的可能性
生意火爆的馒头店
设想自己开了一家馒头店,生意很好,不但得到当地居民的好评,还得到很多外国友人的赞誉。
每天有很多发往全国各地的订单,还有很多发往国外的订单。
为了锁住馒头刚出锅时的美味,每一份快递都使用了超越当前科技水平的保鲜盒,虽然小贵,但物超所值。
所有发往国外的订单,在国内派件的这段路程按国内快递计费,跨出国门后按国际订单计费。
馒头店的快递计费规则
- X:无所谓,不管这个条件的值是什么,该列都是合法的
- 三值布尔逻辑的第三个值
- 可以去除表中的重复,使其更加紧凑
- Y:符合条件
- N:不符合条件
优质客户 | X | X | Y | Y | N | N |
国内加急订单 | Y | N | Y | N | Y | N |
国际订单 | Y | Y | N | N | N | N |
费用 | 150 | 100 | 70 | 50 | 80 | 60 |
短信通知快递公司 | Y | Y | Y | N | N | N |
举例说明
- YYN 一位优质客户,下了一个国内加急订单,费用是 70 元
- NNN 一位普通客户,自提打包,费用是 60 元
- NYN 一位普通客户,下了一个国内加急订单,费用是 80 元
- XYY 无论什么客户,下了一个国内加急 + 国际订单,费用是 150 元
伪代码
1
2
3
4
5
6
7
8
9
10
11
12
| int 计算运费(String 条件组合字符串) {
return 费用规则().get(条件组合字符串);
}
Map 费用规则() {
Map conditions = new Map();
conditions.put("XYY", 150);
conditions.put("XNY", 100);
conditions.put("YYN", 70);
return conditions;
}
|
引入更多任意的枚举、数字范围或者字符串匹配,表格可能会变得更复杂
- 我们可以把每种这样的条件当作布尔值来捕获
- 这样操作时,注意不要犯类似的错误:100 > x > 50,50 >= x 不可能同时为真
决策表实战
以下内容摘自:
极客时间 | 遗留系统现代化实战 | 08 | 代码现代化:你的代码可测吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public void updateQuality() {
for (int i = 0; i < items.length; i++) {
if (!items[i].name.equals("Aged Brie")
&& !items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
if (items[i].quality > 0) {
if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
items[i].quality = items[i].quality - 1;
}
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
if (items[i].sellIn < 11) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
if (items[i].sellIn < 6) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
}
}
}
if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
items[i].sellIn = items[i].sellIn - 1;
}
if (items[i].sellIn < 0) {
if (!items[i].name.equals("Aged Brie")) {
if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
if (items[i].quality > 0) {
if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
items[i].quality = items[i].quality - 1;
}
}
} else {
items[i].quality = items[i].quality - items[i].quality;
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
}
}
}
|
这是非常典型的遗留代码,if/else 满天飞,可谓眼花缭乱;而且分支的规则不统一,有的按名字去判断,有的按数量去判断。
对于这种分支条件较多的代码,我们可以梳理需求文档(如果有的话)和代码,找出所有的路径,根据每个路径下各个字段的数据和最终的值,制定一张决策表
决策表画出来后复杂的吓人,一般人不会想看……