原文链接

大约10年前,我遇到了反if运动,并发现它是一个荒谬的概念。如何在不使用if语句的情况下制作出有用的程序?荒谬。

但是这会让你思考。你还记得上周你必须理解的嵌套代码吗?这有点吸了吗?如果只有一种方法可以使它更简单。

反实施活动网站在实用建议方面令人沮丧。这篇文章打算用一系列你需要的时候可以采用的模式来补救。但首先让我们看看如果陈述构成的问题。

if语句的问题

if语句的第一个问题是它们通常很容易以不好的方式修改代码。让我们从一个新的if语句的诞生开始:

public void theProblem(boolean someCondition) {
        // SharedState

        if(someCondition) {
            // CodeBlockA
        } else {
            // CodeBlockB
        }
}
 

目前这还不算太坏,但我们已经给了我们一些问题。当我读取这段代码时,我必须检查CodeBlockA和CodeBlockB如何修改相同的SharedState。这可以很容易阅读,但随着代码块的增长和耦合变得更加复杂,可能会变得困难。

你会经常看到上面的代码块被进一步嵌套的if语句和本地返回所滥用。通过路由逻辑很难看出业务逻辑是什么。

if语句的第二个问题是当它们被复制时。这意味着域概念缺失。通过将事物聚集在一起而不是不需要的事情来增加耦合是非常容易的。让代码更难阅读和更改。

if语句的第三个问题是您必须在自己的头上模拟执行。你必须成为一台迷你电脑。这正在消除你的精神能量,这种能量在思考解决问题时会更好,而不是像intracate代码如何编织在一起。

我想要告诉你我们可以做的模式,但首先要警告一下。

适可而止,特别是适度

如果语句通常会让你的代码更复杂。但我们不想彻底禁止他们。我已经看到了一些非常令人发指的代码,其目标是去除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
        }
    }
}

问题:任何时候你看到这个,你实际上有两种方法捆绑成一个。该布尔代表了一个机会,可以在代码中命名一个概念。

容差:通常当你看到这个上下文时,你可以在编译时计算代码将采用的路径。如果是这种情况,那么总是使用这种模式。

解决方案:将该方法拆分为两种新方法。Voilà,如果没有了。

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语句。此外,由于不同鸟类的多个概念正在被添加,因此该鸟类中的内聚力正受到影响。

公差:单一类型的开关很好。当它们是多个交换机时,可以引入错误,因为添加新类型的人可能会忘记更新此隐藏类型中存在的所有交换机。在这方面有一篇很好的关于8thlight博客的文章

解决方案:使用多态性。任何引入新类型的人都不能忘记添加相关的行为,

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:NullObject /可选over null传递

上下文:一个局外人要求了解你的代码基本答案的主要目的是“检查是否等于null”。

public void example() {
    sumOf(null);
}

private int sumOf(List<Integer> numbers) {
    if(numbers == null) {
        return 0;
    }

    return numbers.stream().mapToInt(i -> i).sum();
}

问题:您的方法必须检查它们是否传递了非空值。

容忍:有必要在代码库的外部防守,但在代码库中防守可能意味着你正在编写的代码是冒犯性的。不要写无礼的代码。

解决方案:使用NullObjectOptional类型,而不是传递null。一个空集合是一个很好的选择。

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语句都会相乘。他们有一个隐藏的耦合,'空'意味着有些东西。其他对象可能会返回其他表示无结果的魔术值。

公差:最好把这个if语句放到一个地方,所以它不会被重复,并且我们可以移除空对象魔术值的耦合。

解决方案:让代码被称为应对策略。Ruby的Hash#fetchJava复制的一个很好的例子。这种模式可以进一步消除异常

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");
    }
}

快乐狩猎

希望您可以在刚才所用的代码上使用这些模式中的一部分。在重构代码以更好地理解代码时,我发现它们很有用。

请记住,如果陈述并非全是邪恶的。但是我们有一套丰富的现代语言功能来代替我们应该利用的功能。

相关文章


扫描二维码,在手机上阅读!