这是一篇英文博客,觉得很既意思又实用,就尝试着翻译了一下,感觉自己英语水平真菜,不要笑话。原版文章可查看底部原文链接。
大约在10年前,我遇到了反 if 运动(anti-if campaign,一个网站),觉得这是一个荒谬的概念。如果不使用 if 语句,你究竟如何才能做出一个有用的程序?荒谬。
但是它会引发你的思考。你还记得你上周不得不去理解的深度嵌套语句吗?这是不是有点糟糕?如果有一种方法可以让它变得简单就好了。
反 if 运动网站中的一些使用建议是可笑的。本文旨在通过一系列模式来弥补这一点,你可以在需要的时候采用这些模式。首先让我们观察一下if语句的构成。
if 语句的问题
if 语句的第一个问题是它常常使得代码可以很容易的以错误的方式修改。让我们从一个新的 if 语句的诞生开始:
public void theProblem(boolean someCondition) {
// SharedState
if(someCondition) {
// CodeBlockA
} else {
// CodeBlockB
}
}
上面的代码不算多差,但是已经给我们抛出了一些问题。当我阅读这段代码时,我必须要检查CodeBlockA和CodeBlockB是如何修改相同的SharedState的。现在可能很容易阅读,但是随着CodeBlocks的增长与耦合度的增加,阅读会变得越来越困难。
你经常会看到上面的CodeBlocks被滥用,里面有嵌套的 if 语句和返回语句,这样会导致通过代码很难看出业务逻辑是什么。
if 语句的第二个问题在于当它们重复的时候,意味着缺少域的概念。通过将各种东西组合在一起来增加耦合度是很容易的,但是增加了代码的阅读和重构难度。
if 语句的第三个问题是你必须在大脑中模拟执行,把自己当成一个迷你电脑,这会消耗你的精神能量,你本该把精力用在解决问题上面,现在却用来理解理清那些错综复杂的代码是如何编织在一起的。
我想告诉你我们可以使用的模式,但是首先有一个警告。
凡事要适度
if 语句通常会使你的代码更加复杂,但我们不想彻底禁止他们,我已经看到了一些非常令人发指的代码,目的是删除所有if语句的痕迹,我们希望避免陷入这样的陷阱。
对于我们下面将要学习的每个模式,我将为你提供使用它的容差。
在其他地方没有重复的单个 if 语句可能没有问题,当你使用重复的 if 语句时,你就开始犯错误了。
在代码段的外部,当你与危险的外部世界对话时,你希望验证传入的响应,因此也改变了你的行为;但是在我们自己代码的内部,在那些值得信赖的守门人背后,我认为我们有很好的机会来使用简单、丰富并且有效的替代办法。(好晦涩啊,跳过这段吧)
模式1:布尔参数
背景:您有一个方法,它接受一个改变其行为的布尔值
public void example() {
FileUtils.createFile("name.txt", "file contents", false);
FileUtils.createFile("name_temp.txt", "file contents", true);
}
public class FileUtils {
public static void createFile(String name, String contents, boolean temporary) {
if(temporary) {
// save temp file
} else {
// save permanent file
}
}
}
问题:当你看到这种代码的时候,可以发现它实际上是两个方法捆绑在一起了,布尔值代表了在代码中更新概念的一次机会。
容差:通常当你看到此上下文时,你可以在编译时确定代码将如何执行。如果是这种情况,那么常用的就是本模式。
解决方案:将原方法拆分为两个新的方法,瞧!if 语句不见了。
public void example() {
FileUtils.createFile("name.txt", "file contents");
FileUtils.createTemporaryFile("name_temp.txt", "file contents");
}
public class FileUtils {
public static void createFile(String name, String contents) {
// save permanent file
}
public static void createTemporaryFile(String name, String contents) {
// save temp file
}
}
模式2:使用多态性
背景:你正在根据类型进行切换
public class Bird {
private enum Species {
EUROPEAN, AFRICAN, NORWEGIAN_BLUE;
}
private boolean isNailed;
private Species type;
public double getSpeed() {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor();
case NORWEGIAN_BLUE:
return isNailed ? 0 : getBaseSpeed();
default:
return 0;
}
}
private double getLoadFactor() {
return 3;
}
private double getBaseSpeed() {
return 10;
}
}
问题:当我们添加新的类型时,必须记得更新switch语句。此外,由于增加了新的类型,所以鸟这个类的内聚性也受到了影响。
容差:一个switch语句就可以了。如果是多个switch语句,当有人添加新的类型时,就可能会因为忘记更新switch语句而引入bug。在 8thlight blog (一个博客网站)有关于这方面的精彩文章。
解决方案:使用多态性。任何人引入新的类型都不会忘记添加相关的行为。
public abstract class Bird {
public abstract double getSpeed();
protected double getLoadFactor() {
return 3;
}
protected double getBaseSpeed() {
return 10;
}
}
public class EuropeanBird extends Bird {
public double getSpeed() {
return getBaseSpeed();
}
}
public class AfricanBird extends Bird {
public double getSpeed() {
return getBaseSpeed() - getLoadFactor();
}
}
public class NorwegianBird extends Bird {
private boolean isNailed;
public double getSpeed() {
return isNailed ? 0 : getBaseSpeed();
}
}
模式3:空对象/空指针传递
背景:如果别人想要理解你所写代码的主要目的,那可以检查传入空指针时的情况。
public void example() {
sumOf(null);
}
private int sumOf(List<Integer> numbers) {
if(numbers == null) {
return 0;
}
return numbers.stream().mapToInt(i -> i).sum();
}
问题:你的方法必须检查是否传递了空指针。
容差:你有必要在代码段外面进行防御,但是在代码段中处于守势可能意味着你的代码很烂,不要写烂代码!
解决方案:使用空对象或者是可选择类型来代替空指针,空集合是一个不错的选择。
public void example() {
sumOf(new ArrayList<>());
}
private int sumOf(List<Integer> numbers) {
return numbers.stream().mapToInt(i -> i).sum();
}
模式4:内联声明成表达式
背景:你有一个if树,用来计算一个布尔表达式。
public boolean horrible(boolean foo, boolean bar, boolean baz) {
if (foo) {
if (bar) {
return true;
}
}
if (baz) {
return true;
} else {
return false;
}
}
问题:这段代码迫使你用大脑来模拟计算机的运行过程。
容差:很小。这样的代码在一行中或者分裂成不同的部分容易阅读。
解决方案:将 if 语句简化为一个表达式。
public boolean horrible(boolean foo, boolean bar, boolean baz) {
return foo && bar || baz;
}
模式5:给出一个应对策略
背景:你准备调用其他代码,但是你不确定这样是否能成功。
public class Repository {
public String getRecord(int id) {
return null; // cannot find the record
}
}
public class Finder {
public String displayRecord(Repository repository) {
String record = repository.getRecord(123);
if(record == null) {
return "Not found";
} else {
return record;
}
}
}
问题: 每次处理相同的对象或数据结构时,这些语句都是多重的,它们和空指针有隐藏的耦合,其他对象可能会返回没有意义的魔法值。
容差: 最好把 if 语句放在一个地方,这样我们可以减小空对象和魔法值的耦合。
解决方案: 下面给出一种应对策略的代码。Ruby 的 Hash#fetch 是一个很好的例子,这种模式甚至可以进一步删除异常。
private class Repository {
public String getRecord(int id, String defaultValue) {
String result = Db.getRecord(id);
if (result != null) {
return result;
}
return defaultValue;
}
}
public class Finder {
public String displayRecord(Repository repository) {
return repository.getRecord(123, "Not found");
}
}
总结
希望你能用到一些我们前文所介绍的模式,我发现它们在重构代码的时候很有用。
if 语句不是任何时候都不适用的,但是现代语言有很多丰富的特性,我们应该加以利用。
原文链接:如果没有 if 语句