Anti-If: The missing patterns--转

原文地址:http://code.joejag.com/2016/anti-if-the-missing-patterns.html

Around 10 years ago I encountered the anti-if campaign and found it to be an absurd concept. How on earth would you make a useful program without using an if statement? Preposterous.

But then it gets you thinking. Do you remember that heavily nested code you had to understand last week? That kinda sucked right? If only there was a way to make it simpler.

The anti-if campaign site is sadly low on practical advice. This post intends to remedy that with a collection of patterns you can adopt when the need arises. But first let’s look at the problem that if statements pose.

The problems of if statements

The first problem with if statements is that they often make it easy to modify code in bad ways. Let’s start with the birth of a new if statement:

public void theProblem(boolean someCondition) {
        // SharedState

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

This isn’t too bad at this point, but we’ve already given us some problems. When I read this code I have to check how CodeBlockA and CodeBlockB are modifying the same SharedState. This can be easy to read at first but can become difficult as the CodeBlocks grow and the coupling becomes more complicated.

You’ll often see the above CodeBlocks abused with further nested if statements and local returns. Making it hard to see what the business logic is through the routing logic.

The second problem with if statements is when they are duplicated. This means means a domain concept is missing. It’s all too easy to increase coupling by bringing things together than don’t need to be. Making code harder to read and change.

The third problem with if statements is that you have to simulate execution in your own head. You must beome a mini-computer. That’s taking away from your mental energy, energy that would be better spent thinking about solving the problem, rather than how the intracate code branches weave together.

I want to get to the point of telling you patterns we can do instead, but first a word of warning.

Moderation in all things, especially moderation

If statements usually make your code more complicated. But we don’t want to outright ban them. I’ve seen some pretty heinous code created with the goal of removing all traces of if statements. We want to avoid falling into that trap.

For each pattern we’ll read about I’m going to give you a tolerance value for when to use it.

A single if statement which isn’t duplicated anywhere else is probably fine. It’s when you have duplicated if statements that you want your spider sense to be tingling.

At the outside of your code base, where you talk to the dangerous outside world, you are going to want to validate incoming responses and change your beahaviour accordingly. But inside our own codebases, where we behind those trusted gatekeepers, I think we have a great opportunity to use simple, richer and more powerful alternatives.

Pattern 1: Boolean Params

Context: You have a method that takes a boolean which alters its behaviour

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
        }
    }
}

Problem: Any time you see this you actually have two methods bundled into one. That boolean represents an opportunity to name a concept in your code.

Tolerance: Usually when you see this context you can work out at compile time which path the code will take. If that is the case then always use this pattern.

Solution: Split the method into two new methods. Voilà, the if is gone.

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
    }
}

Pattern 2: Switch to Polymorphism

Context: You are switching based on type.

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

Problem: When we add a new type we have to remember to update the switch statement. Additionally the cohesion is suffering in this Bird class as multiple concepts of different birds are being added.

Tolerance: A single switch on type is fine. It’s when their are multiple switches then bugs can be introduced as a person adding a new type can forget to update all the switches that exist on this hidden type. There is an excellent write up on the8thlight blog on this context.

Solution: Use Polymorphism. Anyone introducing a new type cannot forget to add the associated behaviour,

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

note: This example only has one method being switched on for brevity, it’s more compelling when there are multiple switches

Patten 3: NullObject/Optional over null passing

Context: A outsider asked to understand the primary purpose of your code base answers with “to check if things equal null”.

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

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

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

Problem: Your methods have to check if they are being passed non null values.

Tolerance: It’s necessary to be defensive at the outer parts of your codebase, but being defensive inside your codebase probably means the code that you are writing is offensive. Don’t write offensive code.

Solution: Use a NullObject or Optional type instead of ever passing a null. An empty collection is a great alternative.

public void example() {
    sumOf(new ArrayList<>());
}

private int sumOf(List<Integer> numbers) {
    return numbers.stream().mapToInt(i -> i).sum();
}

Patten 4: Inline statements into expressions

Context: You have an if statement tree that calculates a boolean expression.

public boolean horrible(boolean foo, boolean bar, boolean baz) {
    if (foo) {
        if (bar) {
            return true;
        }
    }

    if (baz) {
        return true;
    } else {
        return false;
    }
}

Problem: This code forces you to use your brain to simulate how a computer will step through your method.

Tolerance: Very little. Code like this is easier to read on one line. Or broken into different parts.

Solution: Simplify the if statements into a single expression.

public boolean horrible(boolean foo, boolean bar, boolean baz) {
    return foo && bar || baz;
}

Pattern 5: Give a coping strategy

Context: You are calling some other code, but you aren’t sure if the happy path will succeed.

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

Problem: These sort of if statements multiply each time you deal with the same object or data structure. They have a hidden coupling where ‘null’ means someting. Other objects may return other magic values that mean no result.

Tolerance: It’s better to push this if statement into one place, so it isn’t duplicated and we can remove the coupling on the empty object magic value.

Solution: Give the code being called a coping strategy. Ruby’s Hash#fetch is a good example which Java has copied. This pattern can be taken even further to remove exceptions.

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

Happy hunting

Hopefully you can use some of these patterns on the code you are working on just now. I find them useful when refactoring code to better understand it.

Remember if statements aren’t all evil. But we have a rich set of features in modern languages to use instead which we should take advantage of.

时间: 2024-10-14 03:40:16

Anti-If: The missing patterns--转的相关文章

Learning JavaScript Design Patterns -- A book by Addy Osmani

Learning JavaScript Design Patterns A book by Addy Osmani Volume 1.6.2 Tweet Copyright © Addy Osmani 2015. Learning JavaScript Design Patterns is released under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license. It

Missing Opportunities for Polymorphism

Missing Opportunities for Polymorphism Kirk Pepperdine POLYMORPHiSM iS ONE OF THE GRAND iDEAS that is fundamental to OO. The word, taken from Greek, means many (poly) forms (morph). In the con- text of programming, polymorphism refers to many forms o

13 Stream Processing Patterns for building Streaming and Realtime Applications

原文:https://iwringer.wordpress.com/2015/08/03/patterns-for-streaming-realtime-analytics/ Introduction More and more use cases, we want to react to data faster, rather than storing them in a disk and periodically processing and acting on the data. This

The constructor ClassPathXmlApplicationContext(String) refers to the missing type BeansException

"The constructor ClassPathXmlApplicationContext(String) refers to the missing type BeansException" "构造函数ClassPathXmlApplicationContext(字符串)是指缺失类型BeansException" 出现错误的原因:jar没有正确引入,即使表面上你能import包. import org.junit.Test; import org.spring

[BZOJ1717][Usaco2006 Dec]Milk Patterns 产奶的模式

1717: [Usaco2006 Dec]Milk Patterns 产奶的模式 Time Limit: 5 Sec  Memory Limit: 64 MB Submit: 1297  Solved: 705 [Submit][Status][Discuss] Description 农夫John发现他的奶牛产奶的质量一直在变动.经过细致的调查,他发现:虽然他不能预见明天产奶的质量,但连续的若干天的质量有很多重叠.我们称之为一个"模式". John的牛奶按质量可以被赋予一个0到100

error C2143: syntax error : missing &#39;;&#39; before &#39;{&#39;

这是我在实现哈夫曼树的时候,遇到的错误,具体为什么我也不清楚!!!因为这是我用学校实验室的电脑编译出现的错误(用的软件是VC6.0,贼老的版本!!!),我自己的是Code Blocks(没有出错)??? 代码如下: for ( i = 1; i <= n; i++ ) { huffNode HT[i](w[i],0,0,0);//初始化前n个节点(构造哈夫曼树的原始节点) } 然后,就有错了(-_-!) error C2057: expected constant expression erro

LeetCode题解-----First Missing Positive

Given an unsorted integer array, find the first missing positive integer. For example,Given [1,2,0] return 3,and [3,4,-1,1] return 2. Your algorithm should run in O(n) time and uses constant space. 分析: 因为数组的大小为n,因此那个缺失的整数只可能的范围[1,n+1] 方法一:需要O(n)的空间,设

the tomcat installation directory is not valid. It is missing excepted file or folder

问题描述 : the tomcat installation directory is not valid 原因 : 我在上一页没有选择apache tomcat 7.0,因为eclipse版本太低,只有到6.0的server供选择 所以这边就算把Name改成7.0,还是配置不了 解决方案: 安装更高版本的eclipse the tomcat installation directory is not valid. It is missing excepted file or folder,布布

41. First Missing Positive【leetcode】寻找第一个丢失的整数,java,算法

41. First Missing Positive Given an unsorted integer array, find the first missing positive integer. For example,Given [1,2,0] return 3,and [3,4,-1,1] return 2. Your algorithm should run in O(n) time and uses constant space. 题目:寻找第一个丢失的整数意思为[1,2,4,5,