大约十年前,我听说了反if的活动,觉得这个概念非常荒谬。如果不用if语句,又怎么能写出有用的程序呢?这简直太荒谬了。

但之后你会开始思考:是否还记得上周你拼命想读懂的深度嵌套代码?糟透了对么?要是有办法能简化它该多好。

反if活动的网站上没给出多少实用性建议,因此在本文中,作者将会提供一系列模式,也许你会用得上。但首先我们来关注一下if语句到底造成了什么问题。

if语句的问题

if语句的第一个问题在于,通常出现if语句的代码很容易越改越糟。我们试着写个新的if语句:

public void theProblem(boolean someCondition) {        // SharedStateif(someCondition) {            // CodeBlockA} else {            // CodeBlockB}
}

这时候还不算太糟,但已经存在一些问题了。在阅读这段代码时,我必须得去查看对同一个SharedState来说,CodeBlockA和CodeBlockB有什么改动。最开始这段代码很容易阅读,但随着CodeBlock越来越多,耦合越来越复杂之后,就会很难读。

上面这种CodeBlock进一步嵌套if语句与本地return的滥用情况也很常见,很难搞懂业务逻辑是选择了哪种路径。

if语句的第二个问题在于:复制时会有问题,也就是说,if语句缺失domain的概念。很容易由于在不需要的情况下,由于将内容放在一起而增加耦合性,造成代码难读难改。

而第三个问题在于:开发者必须在头脑中模拟执行实现情况——你得让自己变成一台小型电脑,从而造成脑细胞浪费。开发者的精力应当用来思考如何解决问题,而不是浪费在如何将复杂的代码分支结构编织在一起之上。

虽然想要直截了当地写出替代方案,但首先我得强调这句话:

凡事中庸而行,尤其是中庸本身

if语句通常会让代码更加复杂,但这不代表我们要完全抛弃if语句。我曾经看到过一些非常糟糕的代码,只是为了消除所有的if语句而刻意避开if语句。我们想要绕开这个误区, 
下面我给出的每种模式,都会给出使用范围。

单独的if语句如果不复制到其他地方,也许是不错的句子。在复制if语句时,我们会希望预知危险的第六感起效。

在代码库之外,在与危险的外部世界交流时,我们会想要验证incoming response,并根据其作出相应的修改。但在自己的代码库中,由于有可靠的gatekeeper把关,我觉得这是个很好的机会,我们可以尝试使用简单、更为丰富与强大的替代方案来实现。

模式1:布尔参数(Boolean Params)

背景: 有方法在修改行为时使用了boolean。

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:使用多态(Polymorphism)

背景: 根据类型switch时。

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语句,此外随着不同bird的概念添加进来,bird类的凝聚力越来越糟。

适用范围:根据类型做单次切换是可行的,如果switch太多,在添加新类型时如果忘记更新现有隐藏类型中的所有switch,就会导致bug出现,8thlight博客关于这种情况有详细描述。

解决方案: 使用多态,添加新类型时大家都不会忘记添加相关行为。 
注意:上例为了简洁只写了一个方法,但在有多个switch时更有用。

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/Optional

背景: 当外部请求理解代码库的主要用途时,回答“查一下null的情况”。

public void example() {sumOf(null);
}private int sumOf(List<Integer> numbers) {    if(numbers == null) {        return 0;}    return numbers.stream().mapToInt(i -> i).sum();
}

问题: 在方法中,必须确认传递内容非null值。

适用范围:对外部访问代码库来说,这种做法很必要。但在代码库内部,也许这代表着你的代码写得太严格了。

解决方案: 使用NullObject或Optional类型替代。空集是很好的替代方案。

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

模式4:将内联语句(Inline statements)转为表达式

背景: 在计算布尔表达式时,包含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语句增加了处理同一个对象或者数据结构的时间,其中包含隐藏耦合——null的情况。其它对象可能会返回其他代表没有结果的magic value。

适用范围:最好将这类if语句放在一个地方,由于不会重复,我们就能将为空对象的magic value删除。

解决方案:针对被调用代码,给出应对策略。Ruby的Hash#fetch就是很好的案例,Java也用到了类似的方法。这种模式也可以用在删除例外情况时。

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语句都是魔鬼,不过现代编程语言还有很多功能值得我们探索并使用。

更多内容请关注微信公众号:it_haha

转载于:https://blog.51cto.com/doujh/1829826

程序中减少使用if语句的方法集锦相关推荐

  1. thinkphp mysql 预处理_thinkPHP框架中执行原生SQL语句的方法

    本文实例讲述了thinkPHP框架中执行原生SQL语句的方法.分享给大家供大家参考,具体如下: 怎样在thinkphp里面执行原生的sql语句? $Model = new Model();//或者 $ ...

  2. java用redis缓存的步骤_详解在Java程序中运用Redis缓存对象的方法|chu

    这段时间一直有人问如何在Redis中缓存Java中的List 集合数据,其实很简单,常用的方式有两种: 1. 利用序列化,把对象序列化成二进制格式,Redis 提供了 相关API方法存储二进制,取数据 ...

  3. 开发微信小程序 中遇到的坑 及解决方法

    开发微信小程序 中遇到的坑 及解决方法 参考文章: (1)开发微信小程序 中遇到的坑 及解决方法 (2)https://www.cnblogs.com/feiye512/p/6070052.html ...

  4. c语言程序设计删除,C程序设计语言练习1-23 编写一个删除C语言程序中所有的注释语句...

    C程序设计语言练习1-23 编写一个删除C语言程序中所有的注释语句,要正确处理带引号的字符串与字符常量,以C语言中,注释不允许嵌套. 本程序运行后,将要求用户输入一个当前目录下的C源码程序文件名,读取 ...

  5. Python中os和shutil模块实用方法集锦

    Python中os和shutil模块实用方法集锦 类型:转载 时间:2014-05-13 这篇文章主要介绍了Python中os和shutil模块实用方法集锦,需要的朋友可以参考下 复制代码代码如下: ...

  6. 7.PL_SQL——在PL_SQL程序中内嵌查询语句、DML语句、事物处理语句和游标属性

    在PL/SQL中可以使用的SQL语句主要有以下几类: SELECT 查询语句,DML语句,Transaction 事物处理语句以及游标的属性,本文将对这几类语句在PL/SQL中的用法逐一介绍. 一.查 ...

  7. 在python程序中,#后面的语句是表示啥意思

    这句话以#开头,表示本来不在程序中运行. 这句话的用途是告诉机器寻找到该设备上的python解释器,操作系统使用它找到的解释器来运行文件中的程序代码. 通常情况下: linux: #! /usr/bi ...

  8. 【编程工具】程序中出现中文乱码的解决方法

    很多同学在用别人源码都会碰到这个问题,程序中充斥着大量的中文乱码. 怎么解决这个问题呢,很简单: 在工程的属性下找文本编码方式这一项,然后选择正确的编码方式,一般都是UTF-8,如果不可以的话,可以一 ...

  9. java do while 循环语句_java中do...while循环语句使用方法详解

    do..while循环语句也称为后测试循环语句,它的循环重复执行方式,也是利用一个条件来控制是否要继续重复执行这个语句.与while循环所不同的是,它先执行一次循环语句,然后再去判断 - do...w ...

最新文章

  1. -bash: /usr/java/jdk1.8.0_101/bin/java: Permission denied
  2. Android Handler 流程解析
  3. 【iOS-Cocos2d游戏开发之十五】详解CCProgressTimer 进度条并修改cocos2d源码实现“理想”游戏进度条!...
  4. Qt学习(九):QT中使用线程
  5. 1 计算机主机里面都有些什么东西,计算机主机和外设分别包括那些东西?
  6. Fiddler之如何通过浏览器输入链接地址修改页面返回数据的内容
  7. 前端学习(1620):前端系列实战课程之提取行间样式
  8. js Ajax跨域访问
  9. 蚂蚁庄园 php源码,蚂蚁庄园五体投地
  10. 温故而知新 js 的错误处理机制
  11. AcWing 1270. 数列区间最大值(RMQ问题)
  12. Ubuntu各个版本国内源
  13. 【译】R包介绍:Online Random Forest
  14. 如何经营好自己的朋友圈
  15. 《咏怀古迹五首·其三》.唐.杜甫
  16. 易周金融 | 邮惠万家银行开业;微信公众号叫停四类金融营销宣传
  17. Zynq-7000 - Which IBIS models should be used for Zynq-7000 devices?
  18. Fine-Grained Visual Classification via Progressive Multi-Granularity Training of Jigsaw Patches
  19. 软件漏洞及缓冲区溢出
  20. lc[栈与队列]---232.用栈实现队列

热门文章

  1. r语言 悲观剪枝_《R语言编程—基于tidyverse》新书信息汇总
  2. linux能运行安卓模拟器吗,Ubuntu 14.04中使用模拟器运行Android系统
  3. 若依如何实现翻页保留选择?
  4. python文件名带日期变量,获取日期并将其另存为文件名python
  5. UCMA(OCS) 开发系列之一
  6. java http 表单提交_java模仿http表单提交数据(含文件上传)实例源码
  7. python读写磁盘扇区数据_[Win32] 直接读写磁盘扇区(磁盘绝对读写)
  8. 【电脑帮助】解决Wind10系统照片中自带的保存的图片和本机照片的问题
  9. gitlab批量新增用户
  10. ftp ---- vsftpd安装卸载