《重构》学习笔记(07)-- 简化条件表达式

条件逻辑有可能十分复杂,复杂的条件逻辑可能让复杂度快速上升,并有可能导致代码难以理解。因此,需要一些手段,来简化它们。

Decompose Conditional(分解条件表达式)

带有复杂逻辑的函数中,代码会告诉你发生的事情。但是十分冗长的条件表达,往往让后来者摸不着头脑。这种情况下,可以将它分解为独立的函数,并且为新函数命名(命名可以起到自注释的作用)。在原函数中调用新建函数,从而更清楚地表达自己的意图。举个例子

if(date.before(SUMMER_START) || date.after(SUMMER_END))
    charge=quantity*_winterRate+_winterServiceCharge;
else
    charge=quantity*_summerRate;

可以重构为

if(notSummer(date))
    charge=winterCharge(quantity);
else
    charge=summerCharge(quantity);

上面的例子就是将date.before(SUMMER_START) || date.after(SUMMER_END) 替代为notSummer(date),这种情况就一目了然。概括下这种重构方法的步骤:

  • 将if段落提炼出来,构成一个独立的函数。
  • 将then段落和else段落都提炼出来,各自构成一个独立的函数。
  • 如果发现嵌套的条件逻辑,应该先看看是否可以使用以卫语句取代嵌套条件式。如果不行才开始分解其中的每个条件。

Consolidate Conditional Expression(合并条件表达式)

如果有一串条件检查,如果检查条件各不相同,但是最终导致的结果却一致。那么,就使用“逻辑或”和“逻辑与”将它们合并为一个条件表达式。举例:

 double disabilityAmount() {
        if (_seniority < 2) {
            return 0;
        }
        if (_monthsDisabled > 12) {
            return 0;
        }
        if (_isPartTime) {
            return 0;
        }
        // compute the disability amount
        // ...
    }

由于都是返回0,因此可以将条件表达式合并

    double disabilityAmount() {
        if (isNotEligibleForDisability()) {
            return 0;
        }
        // compute the disability amount
        // ...
    }

    boolean isNotEligibleForDisability() {
        return ((_seniority < 2) || (_monthsDisabled > 12) || (_isPartTime));
    }

需要注意的是,要分清返回0是系统的行为是否在逻辑上一致,还是只是同时返回相同数值,在未来条件变动时还会有所改动。总结这种重构的做法为:

  • 确定这些条件语句都没有副作用。
  • 使用适当的逻辑操作符,将一系列相关条件表达式合并为一个。
  • 编译,测试。
  • 对合并后的条件表达式实施Extract Method

Consolidate Duplicate Conditional Fragments(合并重复的条件片段)

你有时候会发现,条件语句不同分支执行了相同的片段。如果是这样,你就需要将这段代码搬移到条件表达式外面。举例子:

    if (isSpecialDeal()) {
        total = price * 0.95;
       send();
    } else {
        total = price * 0.98;
        send();
   }

重构后,可以将send()提取出来

    if (isSpecialDeal()) {
        total = price * 0.95;
    } else {
        total = price * 0.98;
    }
    send();

同样的,如果try catch中都有同样的代码,就可以放在final中去执行。总结这种重构的做法为:

  • 鉴别出”执行方式不随条件变化而变化”的代码。
  • 如果这些共通代码位于条件表达式起始处,就将它移到条件表达式之前。
  • 如果这些共通代码位于条件表达式尾端,就将它移到条件表达式之后。
  • 如果这些共通代码位于条件表达式中段,就需要观察共通代码之前或之后的代码是否改变了什么东西,如果的确有所改变,应该首先将共通代码向前或向后移动,移至条件表达式的起始处或尾端,再以前面所受的办法来处理。
  • 如果共通代码不止一条语句,应该先使用Extract Method(提炼函数)将共通代码提炼到一个独立函数中,再以前面所说的办法来处理。

Remove Control Flag(移除控制标记)

在一系列的布尔表达式中,某个变量带有“控制标记”的作用,那么用break或者return语句取代控制标记。这样的控制标记带来的麻烦通常超过带来的便利。结构化编程告诉我们一个原则:每个子程序都只能有一个入口和一个出口,但是“单一出口”原则通常让我们在代码中加入许多的控制标记,这样就大大降低了表达式的可读性。使用break或者return可以让程序条理更加清晰。举例:

function checkSecurity(peoples) {
  String found = '';
  for(let i = 0; i < peoples.length; i++) {
    if(!found) {
      if(peoples[i] === 'Don' || peoples[i] === 'John') {
        sendAlert();
        found = peoples[i];
      }
    }
  }
  someLaterCode(found);
}

重构为

function checkSecurity(peoples) {
  String found = foundMiscreant(peoples);
  someLaterCode(found);
}

function foundMiscreant(peoples) {
  for(let i = 0; i < peoples.length; i++) {
    if(peoples[i] === 'Don' || peoples[i] === 'John') {
      sendAlert();
      return peoples[i];
    }
  }
  return '';
}

这种重构的一般做法为:
使用extract method,整段逻辑提炼到一个独立函数中

  • 找出跳出这段逻辑的控制标记值
  • 找出对标记变量赋值的语句,代以恰当的return语句.
  • 替换完成后,编译并测试。

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

如果if else分支都属于正常行为,那么建议正常使用if else去表达。但是如果else分支中的情况极其罕见,那么建议采用卫语句进行表现。

function getPayAmount () {
  let result
  if (isDead) result = deadAmount()
  else {
    if (isSeparated) result = separatedAmount()
    else {
      if (isRetired) result = retiredAmount()
      else result = normalPayAmount()
    }
  }
  return result
}

可以重构为:

function getPayAmount () {
  if (isDead) return deadAmount()
  if (isSeparated) return separatedAmount()
  if (isRetired) return retiredAmount()
  return normalPayAmount()
}

这样代码条理更加清晰,可读性更高。这种重构方法的做法为:

  • 对于每个检查,放进一个卫语句(卫语句要不就从函数返回,要不就抛出一个异常)。
  • 编译测试。

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

“多态”是面向对象的精髓所在,多态可以避免为不同类型代入不同行为。举个例子:

var makeSound = function(animal) {
    if(animal instanceof Duck) {
        console.log('嘎嘎嘎');
    } else if (animal instanceof Chicken) {
        console.log('咯咯咯');
    }
}
var Duck = function(){}
var Chiken = function() {};
makeSound(new Chicken());
makeSound(new Duck());

重构后

var makeSound = function(animal) {
    animal.sound();
}

var Duck = function(){}
Duck.prototype.sound = function() {
    console.log('嘎嘎嘎')
}
var Chiken = function() {};
Chiken.prototype.sound = function() {
    console.log('咯咯咯')
}

makeSound(new Chicken());
makeSound(new Duck());

多态背后的思想是将”做什么“和”谁去做以及怎样去做分开“。通常的做法是:

  • 如果要处理的条件表达式是一个更大函数中的一部分。首先,对条件表达式进行分析,然后使用extract method将其提炼到一个独立函数去。
  • 如果由必要,使用move method、将条件表达式放置到继承结构的顶端。
  • 任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新建函数中,并对其进行适当调整。
    可能需要将超类中某些private字段声明为protected。
  • 超类中删掉条件表达式内被复制了的分支。
  • 将超类中容纳条件表达式的函数声明为抽象函数。

Introduce Null Object(引入Null对象)

这种重构方法在前端中不知如何使用,暂忽略。

Introduce Assertion(引入断言)

这种情况在程序中一般使用是非空判断,为了减少圈复杂度。我建议进行直接的空判断,而不是非空判断。
举例

if(response){
   //do something
}

可以重构为

if(!response){
    return;
}
//do something

本章重构方法比较简单,总的原则就是让代码清晰可懂,减少复杂度。这样的代码维护和交接都是一件非常愉悦的事情。

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

推荐阅读更多精彩内容