5.3.3 矩阵分析

《程序员的底层思维》学习笔记目录

原书:《程序员的底层思维》 | 作者:张建飞

  • 把复杂的问题变成“填空题”
  • 直观性:通过矩阵把多维度的信息放在一起进行对比分析,使我们能清晰的看到问题全貌,从而做出合理的决策

业务场景和业务流程

选一个自己喜欢的 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 会员可以复用同一套流程编写代码
  • 游客的业务相对简单,更适合独立编码,便于理解

相关知识

  • Excel 天然就是一个矩阵分析工具
  • 波士顿矩阵

扩展知识

决策表

参考:《领域特定语言》Martin Fowler 著 | 7.1 决策表 | 第 48 章 决策表(Decision Table)

  • 决策表特别容易被非程序员理解,因此很适合用来与领域专家沟通
  • 由于决策表就是一张表格,因此也很适合在电子数据表中编辑
  • 如果用 DSL 来展现的话,你的程序就有了“让领域专家直接编辑”的可能性

生意火爆的馒头店

设想自己开了一家馒头店,生意很好,不但得到当地居民的好评,还得到很多外国友人的赞誉。

每天有很多发往全国各地的订单,还有很多发往国外的订单。

为了锁住馒头刚出锅时的美味,每一份快递都使用了超越当前科技水平的保鲜盒,虽然小贵,但物超所值。

所有发往国外的订单,在国内派件的这段路程按国内快递计费,跨出国门后按国际订单计费。

馒头店的快递计费规则

  • X:无所谓,不管这个条件的值是什么,该列都是合法的
    • 三值布尔逻辑的第三个值
    • 可以去除表中的重复,使其更加紧凑
  • Y:符合条件
  • N:不符合条件
优质客户XXYYNN
国内加急订单YNYNYN
国际订单YYNNNN
费用15010070508060
短信通知快递公司YYYNNN

举例说明

  • 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 满天飞,可谓眼花缭乱;而且分支的规则不统一,有的按名字去判断,有的按数量去判断。

对于这种分支条件较多的代码,我们可以梳理需求文档(如果有的话)和代码,找出所有的路径,根据每个路径下各个字段的数据和最终的值,制定一张决策表

决策表画出来后复杂的吓人,一般人不会想看……

comments powered by Disqus