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

转载于:https://www.cnblogs.com/davidwang456/p/5593736.html

Anti-If: The missing patterns--转相关推荐

  1. Linear Growth curve model的R实现

    目录 Overview 第0步:读入数据并调用需要的库. 第1步:绘制纵向数据. 第2步:拟合一个无增长模型 使用 "lme "函数的无增长模型 使用 "nlme &qu ...

  2. 具有缺失值的多元时间序列的递归神经网络

    https://www.toutiao.com/a6673418191592489479/ 具有缺失值的多元时间序列的递归神经网络 题目: 具有缺失值的多元时间序列的递归神经网络 作者: Ben D. ...

  3. pyspider all 只启动了_我是如何让微博绿洲的启动速度提升30%的(二)

    作者 | 收纳箱,绿洲iOS研发工程师,绿洲ID:收纳箱KeepFit 0.序言 之前的文章<我是如何让微博绿洲的启动速度提升30%的>收到了很多朋友的反馈. 其中,动态库转静态库的收益相 ...

  4. [论]【DSTGCN】Dynamic SpatiotemporalGraph Convolutional Neural Networks for Traffic Data Imputation

    Dynamic Spatiotemporal Graph Convolutional Neural Networks for Traffic Data Imputation with Complex ...

  5. 老司机 iOS 周报 #27 | 2018-07-16

    老司机 iOS 周报,只为你呈现有价值的信息. 你也可以为这个项目出一份力,如果发现有价值的信息.文章.工具等可以到 Issues 里提给我们,我们会尽快处理.记得写上推荐的理由哦.有建议和意见也欢迎 ...

  6. [技术交流]借鉴的技术——溶解游戏提取精华进行再创作

    [技术交流]"借鉴"的技术--溶解游戏提取精华进行再创作 最近把小游戏融入到手机游戏的思路开始流行,的确随着手机游戏用户的质量提高,这是一个趋势,现在手机游戏用户的品质已经接近20 ...

  7. Structural patterns (proxy、bridge、decorator)学习笔记(一)

    Structural patterns (proxy.bridge.decorator)学习笔记(一) 一.proxy pattern是实际应用中,一般都用于比较复杂的一些对象当中,尤其是创建对象需要 ...

  8. latex审阅模式出现Missing \begin{document}错误

    项目场景: windows 10中使用latexdiff生成审阅版本tex文件 问题描述: latex生成审阅版本的.tex文件编译过程中出现Missing \begin{document},出现该错 ...

  9. go build 编译报错 missing go.sum entry for module providing package

    go build 编译报错 missing go.sum entry for module providing package 解决方法 // 移除未使用的依赖 go mod tidy 再次编译,就可 ...

最新文章

  1. 车联网空间巨大 解决网络安全问题乃当务之急
  2. 干货!隐马尔科夫模型
  3. 全称是什么_JDK,JRE和JVM是什么?三者有什么关系?二分钟搞定!!!
  4. 在Oracle中不通过存储过程一次执行多条SQL语句Oracle PL/SQL
  5. 【POJ - 2485 】Highways (最小生成树,Prim算法,瓶颈生成树)
  6. Docker实战6 之构建自己的docker镜像
  7. JavaEE学习05--cookiesession
  8. 《Java就业培训教程》_张孝祥_书内源码_08
  9. 可以用php做出一个圆锥吗,PS使用渐变工具画一个立体圆锥
  10. 局域网怎么修改其它计算机ip,电脑局域网怎么更改ip
  11. 【高级持续性威胁追踪】SolarWinds供应链攻击持续跟踪进展
  12. Spring课程 Spring入门篇 5-6 introductions应用
  13. 新冠死亡率居高不下,为什么偏偏是意大利?
  14. 为了理想,谁人都可以
  15. 测试高薪必备:3步教测试人员破解子查询
  16. esclip直接快捷键构造函数_IntelliJ Idea 常用快捷键
  17. 使用node连接MongoDB的工具安装及配置
  18. linux快捷键锁定屏幕,天天Linux-Ctrl+S快捷键锁定屏幕的问题
  19. 从零开始搭建公司后台技术栈
  20. Python学习教程:针对任意多的分隔符拆分字符串

热门文章

  1. 计算机没有autoCAD_挑战在一年内用晚上业余时间学会灵活运用CAD(1)|cad|autocad|图学|计算机|电子电路...
  2. java中 2017 1等于_java 2017秋招1
  3. 一行代码实现strlen
  4. delphi 调用php接口_新浪图床 API 接口调用与请求方法详细教程
  5. win10 管理linux文件,Linux子系统文件可在未来的Win10发行版中通过资源管理器访问...
  6. php 升级php5,thinkphp5升级步骤
  7. mysql 二维表 查询_二维报表数据表设计
  8. 苹果手机at系统_iOS14系统苹果手机如何设置充满电提示音?
  9. php实现多商家开发,Thinkphp5.0实战-仿百度糯米开发多商家电商平台学习注意事项...
  10. deep$wide keras