不要if else的编程

条件控制是编程中与生俱来的一种结构,但对于我来说,除了给我带来麻烦外,没有发现任何的用处。一次又一次,我不断发现,越少的if语句,越少的switch语句,越少的循环,就会是越好的代码。通常这其中的原因是程序员用编程语言实现了更好的抽象归纳。他们并不是有意识的避免使用控制结构。但他们确实做到了这些。

如果是使用一种面向对象编程语言,我们可以用多态(polymorphism)来代替switch。同样的技巧也能用在if语句上,但如果逻辑太简单,这样做就有点得不偿失。当使用一种有函数式特征的编程语言时,大部分的循环执行任务我们都可以用map,filter,fold等实现。控制结构最终从代码中消失,这是对代码大有好处的事。

条件控制结构的问题是,它很容易导致你把代码修改的乱七八糟。让我们看看下面一个简单的if语句:

 if ...
    ...
  else
    ...
  end 

代码中所有打省略号的地方都是你可以不断添加代码的地方。这些地方可以访问if外面的变量。这很容易造成高耦合。更糟糕的是,人们会习惯性的在条件控制里嵌套条件。我见过的最糟糕的代码,里面的嵌套之深的就像是噩梦里的无底洞。我想,条件控制结构的真正问题所在是,它把各种任务混合到了一起。我相信,你能从某种角度上看出,它是和任务单一编程原则相冲突的。

我们该怎么做?我们可不可以完全不要控制结构?我想不行,但我们可以做一些实验来看看如何能减少对它们的使用。通常这样做会让我们从中学到一些新技巧,让我们的代码更整洁。

不久前,我开发了一些Ruby程序,我需要写一个‘take’函数,用它从一个数组里取出一些元素。Ruby里有一些针对Enumerable的这样的函数,但我需要一些特殊的功能。如果我需要的数组的大小超出了目标数组的大小,需要把多余的数组空间都置为0。

这看起来可以用简单的if语句实现:

 def padded_take ary, n
    if n <= ary.length
      ary.take(n)
    else
      ary + [0] * (n - ary.length)
    end
  end

让我们认真的看一看这段代码。它没有向我们显示任何填充动作的信息,没有显示数组跟填充的关系。如果认真看,可以看出其中的逻辑,但我们看不出这段代码的意图。

我们引入一些函数来让这段代码更清楚些,使用guard语句来简化if语句:

 def padded_take ary, n
    return ary.take(n) unless needs_padding?(ary, n)
    ary + pad(ary, n)
  end

这个短小精悍,但不是更简单——我们可以使用一个null对象来去掉条件语句。空的数组就是很好的null对象。让我们在来一次。

我们不需要用一个条件语句来计算填充的长度。这个长度我们可以取两个数组中的最大值,如果我们想要的长度超出了数组的长度,填充的长度就是它们的差值:

 pad_length = [0, n - ary.length].max

有了这个长度,我们可以先填充数组,然后取出我们想要的元素:

 def pad ary, n
    pad_length = [0, n - ary.length].max
    ary + [0] * pad_length
  end

于是,我们可以这样定义取出动作:

 def padded_take ary, n
    pad(ary, n).take(n)
  end

我们通过先进行填充从而避免了使用if语句。当然,有时候填充的是一个空数组。

我不想去争论这样的写法是否比最初的if-then-else代码更简单,但现在的代码的意图更清晰了,而且我不认为这种策略在这种代码里使用是过度技术化。

从提取归纳的层面看,代码经过处理后的好处是明显的。当遇到更复杂问题时,它带来的益处将会更明显。

[英文原文:Unconditional Programming ]
分享这篇文章:

14 Responses to 不要if else的编程

  1. null says:

    最开始想”放P”.
    觉得不文雅改成”出虚恭”.
    又希望别人也知道自己在做什么,最后变成”除衫,出虚恭”.

  2. 无聊的可乐+碳 says:

    其实就是用”表驱动” 来部分代替if else的功能吧。
    不过例子真没用好。: )

  3. leechau says:

    对if…else…的分析很到位:可以往里面随便加东西,很容易破坏“一段代码只干一件事”的原则。

  4. stepday says:

    分析得很到位,赞一个!

  5. 码农 says:

    IEnumerable.Where(…).Take(…); 这不是我大Linq么!

  6. 独行猫儿 says:

    if else写不好真坑爹

  7. loethen says:

    博主你是闹哪样。。底部关注我们 f图标是 新浪微博 G+竟然是腾讯微博

  8. haitao says:

    if/switch平实,有什么不好?
    多态、重载 写代码时爽,但是阅读、分析时就费劲了

  9. Unicorn says:

    嗯,确实也慢慢发现条件结构的缺点。。。。“小地方”可以使用。“大地方”就多态吧

  10. gamer says:

    确实,条件嵌套太多会严重影响代码可读性

  11. marblep says:

    不是所有的地方都可以简单的替换掉if else吧,这个例子比较简单可以用max
    真的亮点不在于替换掉if else,而是把pad和take分开

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据