<读书笔记>(代码单元层面)BMS-2: 写更简单的代码单元

"每一个问题都可以再分解为多个更小的问题."

2.1 指导原则

将每一个代码单元的 branch point 的个数限制为最多 4 个. 如果做到这个呢? 这个就需要将复杂的代码单元进行分解, 分解为更为简单的代码单元. 限制分支点的个数也意味着更容易进行单元测试.

实际上这样的做法就是在限制代码的复杂程度.

因为如果一个代码单元中的代码可能�路径更多, 则意味着它更加复杂.

上面说的 branch point(分支结点)指的是: 根据某个条件的不同可能会有不同的执行方向的代码结点. 比如 if 或 switch 等.

计算分支结点个数的方法是: 利用 branch coverage 来计算. 即最少的, 且覆盖到代码单元中所有路径的执行方式的个数.

这里又有所有的执行路径和所有的分支路径的区分. 即 cyclomatic complexity, 或者叫做 McCabe complexity. 它们的值为分支路径的个数加 1 来计算.

比如想要把代码单元的分支结点个数限制为 4 个, 则 swiftlint 中的 cyclomatic_complexity(环路复杂度) 规则的数目可以设置为 5, 如下所示:

# 代码单元结点分支个数
cyclomatic_complexity:
  warning: 5
  error: 6

加一的目的是在实际使用的时候更简单. 比如一个没有分支的代码单元的环路复杂性是 1, 因为它只有一条执行路径.

2.2 动机

遵守这个原则的动机就是要减少代码的复杂度:

  • 简单的代码单元显然更加易读, 更易于�修改.
  • 简单的代码单元更加容易测试.

2.3 如何在实践中运用

在 Java 中有如下的语句�被认为是分支结点:

  • if
  • case
  • ?
  • && ||
  • while
  • for
  • catch

一般来说, 一个复杂的代码单元都是由�若干个代码块结合在一起的, 这样的代码的环路复杂度就是所有的小块的和. 而另外一些是多重的 if-then-else 等, 或者是多分支的 switch. 针对这些复杂代码的解决方法各不相同, 下面就来看看.

2.3.1 处理�多路条件判断

针对复杂条件或条件嵌套的一个解决方式是在外部建立和条件对应的条件对象集合(比如一个字典), 根据条件直接就在对象集合中取得对应的对象, 从而完成对应功能.

更高级的办法是运用重构规则: Replace conditional with Polymorphism, 这个规则的意思是利用多态行为来代替条件判断.�(实际是�前面那个方法的面向对象的�实现)

比如之前的代码是这样的:

    enum Flags {
        case one
        case two
        case three
        case four
        case five
        case six
        case seven
    }

    public func getFlagWith(num: Int) -> Flags {�// 环路复杂度是8
        switch num {
        case 1:
            .one
        case 2:
            .two
        case 3:
            .three
        case 4:
            .four
        case 5:
            .five
        case 6:
            .six
        case 7:
            .seven
        default:
            .one
        }
    }

多态代替条件判断的重构方法需要首先建立一个� Flag 接口:

public protocol Flag {
    func getFlag() -> Flags
}

然后建立�和条件对应的多个类, 实现这个接口, 从而可以返回对应的对象, 从而完成不同功能:

public class One: Flag {
    public func getFlag() -> Flags {
        return .one
    }
}

public class Two: Flag {
    public func getFlag() -> Flags {
        return .two
    }
}

//...

则在使用的时候就可以这样了:

public func getFlagWith(�flagObj: Flag) -> Flags {�
    return fagObj.getFlag()
}

2.3.2 处理嵌套条件判断

对于嵌套条件判断, 可以先明确里面的一些终止值, 然后利用 guard return 来去掉一些条件判断.

实际上这个方法应用了一个重构模式: Replace Nested Conditional with Guard Clauses 模式.

但如果只应用这个模式, 实际分支结点个数是不会变化的. 它只是减少嵌套层次的一个办法.

另外一个办法就是将条件再次分解到另外的方法中.

2.4 反对声音一览

针对这个原则, 仍然有不少人持反对态度, 下面就来看看.

2.4.1 高环路复杂度无法避免

反对者说: 因为我们的业务就是这么复杂, 没办法减小环路复杂度.

2.4.2 分解的方式没有减少环路复杂度的总量

的确是这样, 但是更加可读, 易于理解.

附录

附上再次修改的 swiftlint 配置文件:

disabled_rules: # rule identifiers to exclude from running
#  - colon
#  - comma
#  - control_statement
#   - for_where
#   - force_unwrapping
opt_in_rules: # some rules are only opt-in
  - empty_count
  - missing_docs
  - closure_end_indentation
  - closure_spacing
  - force_unwrapping
  - implicitly_unwrapped_optional
  - operator_usage_whitespace
  - redundant_nil_coalescing

included: # 包含目录`--path` is ignored if present.
  - ./

excluded: # 排除目录, 这个是在包含目录之前进行排除的
  - Carthage
  - Pods

force_cast: warning # implicitly
force_try:
  severity: warning # explicitly

# 行宽
line_length: 120

# 类型体的长度
type_body_length: 
  - 300 # warning
  - 400 # error

# 方法或函数体的长度
function_body_length: 30

# 方法或函数的参数个数限制
function_parameter_count: 6

# 文件的长度限制
file_length:
  warning: 500
  error: 1200

# 代码单元结点分支个数
cyclomatic_complexity:
  warning: 5
  error: 10

# 类型名称的长度规定
type_name:
  min_length: 2 # only warning
  max_length: # warning and error
    warning: 40
    error: 50
  excluded: iPhone # excluded via string

# 标识符长度规定 
identifier_name:
  min_length: # only min_length
    error: 2 # only error
  excluded: # excluded via string array
    - id
    - vm
    - URL
    - GlobalAPIKey

reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容

  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,230评论 0 25
  • 一. 算法之变,结构为宗 计算机在很多情况下被应用于检索数据,比如航空和铁路运输业的航班信息和列车时刻表的查询,都...
    Leesper阅读 6,912评论 13 42
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,183评论 25 707
  • 因为之前就复习完数据结构了,所以为了保持记忆,整理了一份复习纲要,复习的时候可以看着纲要想具体内容。 树 树的基本...
    牛富贵儿阅读 6,886评论 3 10
  • 本文参加#我的军训我来说#活动,本人承诺,文章内容为原创,且未在其他平台发表过。 任汗湿衣衫,任日晒脸庞,我依然站...
    山建艺术学院阅读 333评论 0 0