阿里Java开发手册思考(二)

题图:by pixel2013 From pixabay

上期我们分享了关于Java中equals与hashCode的理解

本期我们将分享Java中if/else复杂逻辑的处理

在github上曾看到一些issue,国外的程序员比较忌讳写else,看到了很多这样的评论else is horrible,那么对于逻辑很复杂的代码段,如果用太多的if/else的话,那么会导致代码的阅读难度变大,同时会增加代码的圈复杂度,理论上,如果一个函数的圈复杂度超过8,那么这个函数就还有可优化的地方,那么如何优化这种多分支的复杂逻辑的函数呢?手册中给出了三种方法:卫语句策略模式状态模式,通过阅读《重构:改善既有代码的设计》发现,解决这个问题其实有很多种,下面我们就一一道来。

第一大类:重新组织函数

1、Extract Method(提炼函数)

这种方法应该是最常用的方法之一,当函数过长或者分支太多的话,就可以考虑将其中的一段代码提炼成一个独立的函数。

  • 原始代码:
public void today() {
    if (isBusy()) {
        System.out.println("change time.");
    } else if (isFree()) {
        System.out.println("go to travel.");
    } else {
        System.out.println("stay at home to learn Alibaba Java Coding Guidelines.");
    }
}
  • 改后代码:
public void today() {
    if (isBusy()) {
        changeTime();
    } else if (isFree()) {
        goToTravel();
    } else {
        stayAtHomeToLearn();
    }
}

private void changeTime() {
        System.out.println("change time.");
}

private void goToTravel() {
    System.out.println("go to travel.");
}

private void stayAtHomeToLearn() {
    System.out.println("stay at home to learn Alibaba Java Coding Guidelines.");
}

用法:

  • 提炼新函数,根据这个函数的意图来命名,以它做什么来命名,而不是以他怎么做来命名
  • 仔细检查提炼出的代码是否引用了作用域限于源函数的变量,包括局部变量和源函数参数
  • 适用场景:函数过长或者需要注释才能让人理解用途的代码

2、Substitute Algorithm(替换算法)

把某个算法替换成另一个更清晰的算法,或将函数本体替换为另一个算法。

  • 原始代码:
public String foundPerson(String[] people) {
    for (int i = 0; i < people.length; i++) {
        if (people[i].equals("Don")) {
            return "Don";
        }
        if (people[i].equals("John")) {
            return "John";
        }
        if (people[i].equals("Kent")) {
            return "Kent";
        }
    }
    return "";
}
  • 改后代码:
public String foundPerson(String[] people) {
    List<String> candidates = Arrays.asList(new String[] { "Don", "John", "Kent" });
    for (int i = 0; i < people.length; i++) {
        if (candidates.contains(people[i])) {
            return people[i];
        }
    }
    return "";
}

用法:

  • 准备好另一个替换用的算法
  • 新算法,要与原本的算法结果相同
  • 适用场景:把某个算法替换为更清晰的算法,或者把函数替换为一个算法

第二大类:简化条件表达式

1、Eecompose Conditional(分解条件表达式)

如果有复杂的条件(if-then-else)语句,从if、then、else三个段落中分别提炼出独立函数。

  • 原始代码:
public void today() {
    if (isBusy() || isNotWeekend()) {
       System.out.println("change time.");
    return;
    } else {
        System.out.println("go to travel.");
    }
}
  • 改后代码:
public void today() {
    if (notFree()) {
        changeTime();
    } else {
        goToTravel();
    }
}

private boolean notFree() {
    return isBusy() || isNotWeekend();
}

private void changeTime() {
    System.out.println("change time.");
}

private void goToTravel() {
    System.out.println("go to travel.");
}

用法:

  • 将if段落提炼出来,构成一个独立函数
  • 将then段落和else段落都提炼出来,各自构成一个独立函数
  • 适用场景:复杂的条件语句。如果发现嵌套的条件逻辑,先观察是否可以使用卫语句,如果不行,再开始分解其中的每个条件

2、Consolidate Conditioinal Expression(合并条件表达式)

如果有一系列条件测试,都得到相同结果,将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。

  • 原始代码:
public void today() {
    if (isWeekend()) {
        System.out.println("go to travel.");
    }
    if (isHoliday()) {
        System.out.println("go to travel.");
    }
    if (noWork()) {
        System.out.println("go to travel.");
    }
}
  • 改后代码:
public void today() {
    if (isFree()) {
        System.out.println("go to travel.");
    }
}

private boolean isFree() {
    return isWeekend() || isHoliday() || noWork();
}

用法:

  • 确定这些条件语句都没有副作用
  • 使用适当的逻辑操作符,将一系列相关条件表达式合并为一个,并对合并后的表达式提炼函数
  • 适用场景:一系列条件测试,都得到相同的结果

3、Consolidate Duplicate Conditional Clauses(合并重复的条件判断)

在条件表达式的每个分支上有相同的一段代码,将这段代码搬移到条件表达式之外。

  • 原始代码:
public void today() {
    if (isBusy()) {
        System.out.println("change time.");
        sleep();
    } else if (isFree()) {
        System.out.println("go to travel.");
        sleep();
    } else {
        System.out.println("stay at home to learn Alibaba Java Coding Guidelines.");
        sleep();
    }
}
  • 改后代码:
public void today() {
    if (isBusy()) {
        System.out.println("change time.");
    } else if (isFree()) {
        System.out.println("go to travel.");
    } else {
        System.out.println("stay at home to learn Alibaba Java Coding Guidelines.");
    }
    sleep();
}

用法:

  • 鉴别出执行方式不随条件变化而变化的的代码
  • 如果这些共同代码位于条件表达式起始处,就将它移到条件表达式之前,如果在尾端,移到条件表达式之后
  • 适用场景:在条件表达式的每个分支上有相同的一段代码

4、Remove Control Flag(移除控制标记)

在一系列布尔表达式中,某个变量带有“控制标记(Flag)”的作用,以break语句或者return语句取代控制标记。

5、Replace Nested Confitional with Guard Clauses(以卫语句取代嵌套条件表达式)

如果多个分支都属于正常行为,就应该使用if...else...的条件表达式,如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查常常被称为卫语句。

  • 原始代码:
public void today() {
    if (isBusy()) {
        System.out.println("change time.");
    } else if (isFree()) {
        System.out.println("go to travel.");
    } else {
        System.out.println("stay at home to learn Alibaba Java Coding Guidelines.");
    }
}
  • 改后代码:
public void today() {
    if (isBusy()) {
        System.out.println("change time.");
        return;
    }
    if (isFree()) {
        System.out.println("go to travel.");
        return;
    }
    System.out.println("stay at home to learn Alibaba Java Coding Guidelines.");
    return;
}

用法:

  • 对于每个检查,放进一个卫语句,卫语句要么就从函数中返回,要么就抛出一个异常
  • 每次将条件检查替换成卫语句后,编译并测试:如果所有的卫语句都导致同样的结果,请使用合并条件表达式
  • 适用场景:使用卫语句返回所有特殊情况

6、Replace Conditional with Polymorphism(以多态取代条件表达式)

条件表达式,根据对象的类型选择不同的行为,将这个条件表达式的每个分支放进一个子内的覆写函数中,然后将原始函数声明为抽象函数,这一项就是手册中说的策略模式以及状态模式。
正因为有了多态,所以“类型码的switch语句”以及“基于类型名称的if-then-else”语句在面向对象程序中很少出现。

第三大类:简化函数调用

1、Separate Query from Modifier(将查询函数和修改函数分离)

某个函数既返回对象状态值,又修改了状态,建立两个不同的函数,其中一个负责查询,另一个负责修改。

  • 并发的情况:需要保留第三个函数来同时做这两件事

2、Parameterize Method(令函数携带参数)

若干函数做了类似的工作,但在函数本体中却包含了不同的值,建立单一函数,以参数表达那些不同的值。

  • 原始代码:
public void tenPercentRaise() {
    salary *= 1.1;
}

public void fivePercentRaise() {
    salary *= 1.05;
}
  • 改后代码:
public void raise(double factor) {
    salary *= (1 + factor);
}
  • 要点在于:以可将少量数值视为参数为依据,找出带有重复性的代码

3、Replace Parameter with Explicit Methods(以明确函数取代参数)

有一个函数,其中完全取决于参数值而采取不同行为,针对该参数的每一个可能值,建立一个独立的函数。

  • 原始代码:
public void setValue(String name, int value) {
    if (name.equals("height")) {
        height = value;
    }

    if (name.equals("width")) {
        width = value;
    }
}
  • 改后代码:
public void setHeight(int arg) {
    height = arg;
}

public void setWidth(int arg) {
    width = arg;
}

用法:

  • 针对参数的每一种可能值,新建一个明确的函数
  • 修改条件表达式的每个分支,使其调用合适的新函数
  • 适用场景:函数完全取决于参数值而采取不同行为
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容