原文地址: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.

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

  1. iOS Architecture Patterns

    By Bohdan Orlov on 21 Mar 2016 - 0 Comments iOS FYI: Slides from my presentation at NSLondon are ava ...

  2. (转) MapReduce Design Patterns(chapter 5 (part 1))(九)

    Chapter 5. Join Patterns 把数据保存成一个巨大的数据集不是很常见.例如,用户信息数据频繁更新,所以要保存到关系数据库中.于此同时,web日志以恒定的数据流量增加,直接写到HDF ...

  3. 13 Stream Processing Patterns for building Streaming and Realtime Applications

    原文:https://iwringer.wordpress.com/2015/08/03/patterns-for-streaming-realtime-analytics/ Introduction ...

  4. AMD - Learning JavaScript Design Patterns [Book] - O'Reilly

    AMD - Learning JavaScript Design Patterns [Book] - O'Reilly The overall goal for the Asynchronous Mo ...

  5. error C4430:missing type specifier 解决错误

    错误    3    error C4430: missing type specifier - int assumed. Note: C++ does not support default-int ...

  6. Missing Push Notification Entitlement 问题

    最近打包上传是遇到一个问题: 描述: Missing Push Notification Entitlement - Your app includes an API for Apple's Push ...

  7. Design Patterns Simplified - Part 3 (Simple Factory)【设计模式简述--第三部分(简单工厂)】

    原文链接:http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part3-factory/ Design ...

  8. ABP Zero示例项目登录报错“Empty or invalid anti forgery header token.”问题解决

    ABP Zero项目,登录时出现如图"Empty or invalid anti forgery header token."错误提示的解决方法: 在 WebModule.cs的P ...

  9. PHPmailer关于Extension missing: openssl报错的解决

    最近在写一个网页的时候,需要用到PHPmailer来发送邮件,按照官网上给出的demo写出一个例子,却报错Extension missing: openssl 最后发现需要修改php.ini中的配置: ...

随机推荐

  1. Lua 栈的理解

    提到C++与lua互调,不可不提栈. 栈是C++和Lua相互通讯的一个地方. 首先这个栈并不是传统意义上的栈(传统的栈需要放同一种数据类型,但在网上的某些资料说,每个栈元素是一个联合体). 栈从上向下 ...

  2. JavaScript 基础第三天

    一.前言 在前天的内容我们提到了有关于JS的一些语言结构,这些语言结构都是语法中最为基本的内容必须予以熟记并可以灵活掌握. 二.引入 在今天内容中我们将讨论两个很重要的概念,数组与函数. 三.重点内容 ...

  3. C++混合编程之idlcpp教程Python篇(6)

    上一篇在这 C++混合编程之idlcpp教程Python篇(5) 第一篇在这 C++混合编程之idlcpp教程(一) 工程PythonTutorial4中加入了四个文件:PythonTutorial4 ...

  4. 微软再次要求Google审查官方链接 称将进行调查

    之前代表微软向Google发出DMCA删除通知的反盗版公司再次要求Google审查Microsoft.com官网链接.微软对此表示将进行调查,已经要求反盗版公司停止以微软的名义发出DMCA通知. 仅仅 ...

  5. 字符串反混淆实战 Dotfuscator 4.9 字符串加密技术应对策略

    因为手头需要使用一个第三方类库,网络上又找不到它的可用的版本,于是只好自己动手.这个类库使用了Dotfuscator 加密,用.NET Reflector加载程序集, 看到的字符串是乱码,如下面的代码 ...

  6. js中的深复制和浅复制

    在实际情况中经常会遇到对对象复制的问题.比如在处理项目中的一笔多结构的数据存储或者调用,这个时候你就要对对象(json)进行操作,而不同的操作根据不同的需求来定义.其中最常见最普遍的是对对象的复制,重 ...

  7. flume 1.4的介绍及使用示例

    flume 1.4的介绍及使用示例 本文将介绍关于flume 1.4的使用示例,如果还没有安装flume的话可以参考:http://blog.csdn.net/zhu_xun/article/deta ...

  8. (转载)编写高效的jQuery代码

    原文地址:http://www.cnblogs.com/ppforever/p/4084232.html 最近写了很多的js,虽然效果都实现了,但是总感觉自己写的js在性能上还能有很大的提升.本文我计 ...

  9. 通过分析iframe和无阻塞脚本关系能让我们更懂iframe

    在我上篇文章里,我提到一种使用iframe完成无阻塞脚本加载的方式,因为我对iframe的偏见很大,所以上篇文章里我没有展开讨论这个问题. 文章发表后有位网友问了我这样一个问题,下面是他问题的原文,如 ...

  10. 在github上写个人简历——先弄个主页

    起因 不知道园友们在使用智联招聘等网站填写简历的时候对要求输入的内容有没有一种无力感,不吐槽了反正就一句话,按照它提供的格式我没法儿写简历,而且面试的时候总会被问道有没有自己作品,哥们儿天天上班,下班 ...