减少该死的 if else 嵌套

  • 写在前面
  • 正文
  • 写在最后

写在前面

不知大家有没遇到过像“横放着的金字塔”一样的if else嵌套:

if (true) {    if (true) {        if (true) {            if (true) {                if (true) {                    if (true) {

                    }                }            }        }    }}

我并没夸大其词,我是真的遇到过了!嵌套6、7层,一个函数几百行,简!直!看!死!人!

if else作为每种编程语言都不可或缺的条件语句,我们在编程时会大量的用到。但if else一般不建议嵌套超过三层,如果一段代码存在过多的if else嵌套,代码的可读性就会急速下降,后期维护难度也大大提高。所以,我们程序员都应该尽量避免过多的if else嵌套。下面将会谈谈我在工作中如何减少if else嵌套的。

听说有图会更多人看

正文

在谈我的方法之前,不妨先用个例子来说明if else嵌套过多的弊端。

想象下一个简单分享的业务需求:支持分享链接、图片、文本和图文,分享结果回调给用户(为了不跑题,这里简略了业务,实际复杂得多)。当接手到这么一个业务时,是不是觉得很简单,稍动下脑就可以动手了:

先定义分享的类型、分享Bean和分享回调类:

private static final int TYPE_LINK = 0;private static final int TYPE_IMAGE = 1;private static final int TYPE_TEXT = 2;private static final int TYPE_IMAGE_TEXT = 3;

public class ShareItem {    int type;    String title;    String content;    String imagePath;    String link;}

public interface ShareListener {

    int STATE_SUCC = 0;    int STATE_FAIL = 1;

    void onCallback(int state, String msg);}

好了,然后在定义个分享接口,对每种类型分别进行分享就ok了:

public void share (ShareItem item, ShareListener listener) {    if (item != null) {        if (item.type == TYPE_LINK) {            // 分享链接            if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) {                doShareLink(item.link, item.title, item.content, listener);            } else {                if (listener != null) {                    listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");                }            }        } else if (item.type == TYPE_IMAGE) {            // 分享图片            if (!TextUtils.isEmpty(item.imagePath)) {                doShareImage(item.imagePath, listener);            } else {                if (listener != null) {                    listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");                }            }        } else if (item.type == TYPE_TEXT) {            // 分享文本            if (!TextUtils.isEmpty(item.content)) {                doShareText(item.content, listener);            } else {                if (listener != null) {                    listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");                }            }        } else if (item.type == TYPE_IMAGE_TEXT) {            // 分享图文            if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) {                doShareImageAndText(item.imagePath, item.content, listener);            } else {                if (listener != null) {                    listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");                }            }        } else {            if (listener != null) {                listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享类型");            }        }    } else {        if (listener != null) {            listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能为 null");        }    }}

到此,简单的分享模型就做出来了。有没问题?老实说,如果没什么追求的话,还真没什么问题,至少思路是清晰的。但一周后呢?一个月后呢?或者一年后呢?share方法的分支有15条,这意味着你每次回看代码得让自己的大脑变成微型的处理器,考虑15种情况。如果出现bug,你又得考虑15种情况,并15种情况都要测试下。再如果现在需要加多分享小视频功能,你又得添加多3个分支,还要改代码,一点都不“开放-闭合”。再再如果后面项目交接给他人跟进,他人又要把自己大脑变成处理器来想每个分支的作用,我敢肯定有百分之八十的人都会吐槽代码。

我们程序员的脑力不应该花费在无止境的分支语句里的,应该专注于业务本身。所以我们很有必要避免写出多分支嵌套的语句。好的,我们来分析下上面的代码多分支的原因:

  1. 空值判断
  2. 业务判断
  3. 状态判断

几乎所有的业务都离不开这几个判断,从而导致if else嵌套过多。那是不是没办法解决了?答案肯定不是的。

上面的代码我是用java写的,对于java程序员来说,空值判断简直使人很沮丧,让人身心疲惫。上面的代码每次回调都要判断一次listener是否为空,又要判断用户传入的ShareItem是否为空,还要判断ShareItem里面的字段是否为空……

对于这种情况,我采用的方法很简单:接口分层。

减少 if else 方法一:接口分层

所谓接口分层指的是:把接口分为外部和内部接口,所有空值判断放在外部接口完成,只处理一次;而内部接口传入的变量由外部接口保证不为空,从而减少空值判断。

来,看代码更加直观:

public void share(ShareItem item, ShareListener listener) {    if (item == null) {        if (listener != null) {            listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能为 null");        }        return;    }

    if (listener == null) {        listener = new ShareListener() {            @Override            public void onCallback(int state, String msg) {                Log.i("DEBUG", "ShareListener is null");            }        };    }

    shareImpl(item, listener);}

private void shareImpl (ShareItem item, ShareListener listener) {    if (item.type == TYPE_LINK) {        // 分享链接        if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) {            doShareLink(item.link, item.title, item.content, listener);        } else {            listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");        }    } else if (item.type == TYPE_IMAGE) {        // 分享图片        if (!TextUtils.isEmpty(item.imagePath)) {            doShareImage(item.imagePath, listener);        } else {            listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");        }    } else if (item.type == TYPE_TEXT) {        // 分享文本        if (!TextUtils.isEmpty(item.content)) {            doShareText(item.content, listener);        } else {            listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");        }    } else if (item.type == TYPE_IMAGE_TEXT) {        // 分享图文        if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) {            doShareImageAndText(item.imagePath, item.content, listener);        } else {            listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");        }    } else {        listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享类型");    }}

可以看到,上面的代码分为外部接口share和内部接口shareImplShareItemShareListener的判断都放在share里完成,那么shareImpl就减少了if else的嵌套了,相当于把if else分摊了。这样一来,代码的可读性好很多,嵌套也不超过3层了。

但可以看到,shareImpl里还是包含分享类型的判断,也即业务判断,我们都清楚产品经理的脑洞有多大了,分享的类型随时会改变或添加。嗯说到这里相信大家都想到用多态了。多态不但能应付业务改变的情况,也可以用来减少if else的嵌套。

减少 if else 方法二:多态

利用多态,每种业务单独处理,在接口不再做任何业务判断。把ShareItem抽象出来,作为基础类,然后针对每种业务各自实现其子类:

public abstract class ShareItem {    int type;

    public ShareItem(int type) {        this.type = type;    }

    public abstract void doShare(ShareListener listener);}

public class Link extends ShareItem {    String title;    String content;    String link;

    public Link(String link, String title, String content) {        super(TYPE_LINK);        this.link = !TextUtils.isEmpty(link) ? link : "default";        this.title = !TextUtils.isEmpty(title) ? title : "default";        this.content = !TextUtils.isEmpty(content) ? content : "default";    }

    @Override    public void doShare(ShareListener listener) {        // do share    }}

public class Image extends ShareItem {    String imagePath;

    public Image(String imagePath) {        super(TYPE_IMAGE);        this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default";    }

    @Override    public void doShare(ShareListener listener) {        // do share    }}

public class Text extends ShareItem {    String content;

    public Text(String content) {        super(TYPE_TEXT);        this.content = !TextUtils.isEmpty(content) ? content : "default";    }

    @Override    public void doShare(ShareListener listener) {        // do share    }}

public class ImageText extends ShareItem {    String content;    String imagePath;

    public ImageText(String imagePath, String content) {        super(TYPE_IMAGE_TEXT);        this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default";        this.content = !TextUtils.isEmpty(content) ? content : "default";    }

    @Override    public void doShare(ShareListener listener) {        // do share    }}

(注意:上面每个子类的构造方法还对每个字段做了空值处理,为空的话,赋值default,这样如果用户传了空值,在调试就会发现问题。)

实现了多态后,分享接口的就简洁多了:

public void share(ShareItem item, ShareListener listener) {    if (item == null) {        if (listener != null) {            listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能为 null");        }        return;    }

    if (listener == null) {        listener = new ShareListener() {            @Override            public void onCallback(int state, String msg) {                Log.i("DEBUG", "ShareListener is null");            }        };    }

    shareImpl(item, listener);}

private void shareImpl (ShareItem item, ShareListener listener) {    item.doShare(listener);}

嘻嘻,怎样,内部接口一个if else都没了,是不是很酷~ 如果这个分享功能是自己App里面的功能,不是第三方SDK,到这里已经没问题了。但如果是第三方分享SDK的功能的话,这样暴露给用户的类增加了很多(各ShareItem的子类,相当于把if else抛给用户了),用户的接入成本提高,违背了“迪米特原则”了。

处理这种情况也很简单,再次封装一层即可。把ShareItem的子类的访问权限降低,在暴露给用户的主类里定义几个方法,在内部帮助用户创建具体的分享类型,这样用户就无需知道具体的类了:

public ShareItem createLinkShareItem(String link, String title, String content) {    return new Link(link, title, content);}

public ShareItem createImageShareItem(String ImagePath) {    return new Image(ImagePath);}

public ShareItem createTextShareItem(String content) {    return new Text(content);}

public ShareItem createImageTextShareItem(String ImagePath, String content) {    return new ImageText(ImagePath, content);}

或者有人会说,这样用户也需额外了解多几个方法。我个人觉得让用户了解多几个方法好过了解多几个类,而已方法名一看就能知道意图,成本还是挺小,是可以接受的。

其实这种情况,更多人想到的是使用工厂模式。嗯,工厂模式能解决这个问题(其实也需要用户额外了解多几个type类型),但工厂模式难免又引入分支,我们可以用Map消除分支。

减少 if else 方法三:使用Map替代分支语句

把所有分享类型预先缓存在Map里,那么就可以直接get获取具体类型,消除分支:

private Map<Integer, Class<? extends ShareItem>> map = new HashMap<>();

private void init() {    map.put(TYPE_LINK, Link.class);    map.put(TYPE_IMAGE, Image.class);    map.put(TYPE_TEXT, Text.class);    map.put(TYPE_IMAGE_TEXT, ImageText.class);}

public ShareItem createShareItem(int type) {    try {        Class<? extends ShareItem> shareItemClass = map.get(type);        return shareItemClass.newInstance();    } catch (Exception e) {        return new DefaultShareItem(); // 返回默认实现,不要返回null    } }

这种方式跟上面分为几个方法的方式各有利弊,看大家取舍了~

写在最后

讲到这里大家有没收获呢?总结下减少if else的方法:

  • 把接口分为外部和内部接口,所有空值判断放在外部接口完成;而内部接口传入的变量由外部接口保证不为空,从而减少空值判断。
  • 利用多态,把业务判断消除,各子类分别关注自己的实现,并实现子类的创建方法,避免用户了解过多的类。
  • 把分支状态信息预先缓存在Map里,直接get获取具体值,消除分支。

好了,到此就介绍完了,希望大家以后写代码能注意,有则避之,无则加勉。希望大家写的代码越来越简洁~

来源:http://t.cn/Ez2s6ba

原文地址:https://www.cnblogs.com/yudaoyuanma/p/10575557.html

时间: 2024-10-14 12:37:52

减少该死的 if else 嵌套的相关文章

浅谈代码重构与优化

浅谈代码重构与优化 前几天看到有一篇不错的文章减少该死的if-else嵌套,觉得写得很不错,整理了一下后准备在团队内部简单分享一下. 写在前面 大家在接手项目的时候,应该有遇到过下面这种结构的代码 if (true) { ..... if (true) { if (true) { .... if (true) { if (true) { ...... } } // do something } // do something if (true) { ......... } } // do som

CSS规范 - 代码格式

选择器.属性和值都使用小写 在xhtml标准中规定了所有标签.属性和值都小写,CSS也是如此.单行写完一个选择器定义 便于选择器的寻找和阅读,也便于插入新选择器和编辑,便于模块等的识别.去除多余空格,使代码紧凑减少换行. 如果有嵌套定义,可以采取内部单行的形式. /* 单行定义一个选择器 */ .m-list li,.m-list h3{width:100px;padding:10px;border:1px solid #ddd;} /* 这是一个有嵌套定义的选择器 */ @media all

好的代码标准

规范性 (一)排版规范,统一风格 (1)程序采用缩进风格编码. (2)较长的语句要分成多行书写,不允许把多个语句写成一行. (3)if.for.do.while.case.switch.default等语句的执行语句,无论多少都要用{}括号,并占一行. (二)注释准确,描述清晰,优先做到代码的自注释 (1)文件头部应有注释,函数头部应有输入参数,输出参数,函数功能说明. (2)注释的内容要清楚.明了.含义准确.无二义性. (3)对代码的注释应不可放在代码下面. (4)变量.常量.数据结构声明,如

实习系列1

实习,抱着一颗学习的心态因为底子薄,发现很多东西不会,学得完全不够用!这个捉急啊.这一个月来接触了很多新东西,zepto,自适应布局,-webkit-私有属性,less,git,ajax,表单验证,正则表达式...每一个知识点都需要花时间才能学得好,可是我啊,每一项花的时间都不是很多,有点浮在上面,总感觉很多都得学,都想学,最后发现,到现在还没有那一项是拿的出手的.这可不行呀,我的内心告诉我,沉下去,一步一步走,专注在一件事上,制定学习计划(重点是落实计划)...说了一堆还没有进入正题,惭愧.

Android之——ListView优化

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/47209253 作为客户端,其最主要的任务就是最直观的和用户交互.从服务器拿数据,解析过后显示数据,根据用户操作按照一定的协议传回数据,达到用户想要的结果.这是我自己的理解,所以我们的程序,必须给用户一个良好的体验. listView可以说是安卓开发中很重要的一个控件.我所做的项目中,几乎每个页面都会有listView.Adapter是listView和数据源间的中间人.当每条数据

Android消息传递源码理解。Handler,Looper,MessageQueue,Message

Android中消息传递模块差不多看了好几次,虽然每次看的方式都差不多但是还是发觉的到每次看了之后,理解的更清晰一点. 关于这个模块的文章数不胜数,但是最重要的还是自己动手理解一遍更好. 会牵扯到的几个类: Handler.java  , Looper.java , MessageQueue.java , Message.java 源代码路径: xxx/frameworks/base/core/java/android/os  看的过程中你会发现高版本和低版本系统代码有些地方比较大的差距.从中我

Android常见问题总结(三)

上一篇博客传送门:Android常见问题总结(二) 11. Android的数据存储形式 在Android中的数据存储形式主要有以下几种: SharedPreferrences SharedPreferrences主要用于存储一些少量的简单的应用程序配置信息.SharedPreferrences以明文键值对的形式把数据存储在一个xml文件上,该文件位于/data/data/<package name>/shared_prefs目录下.因此,SharedPreferrences只适合用于存储一些

HTML5技术学习总结

背景: HTML5(下面简称H5)技术早在N多年前就"如火如荼"了,当然现在的热度依旧不减.尤其是移动市场大火的今天,很多网站都采用了H5的技术.其实很多时候我们实际开发中多或少的都会用到H5技术.尤其是像我这种"半道出家"的非"职业前端"开发人员,很多页面效果都是度娘和google的,网上dang下来也简单测试下兼容性也就用了.我想这也是H5工作小组想要达到的目的,"文化渗透"."逐步蚕食",避免大规模&

提高网站加载速度的3项黄金守则

如何优化网页加载速度是每个前端开发工程师需要了解的,也是前端开发工程师需要具备的基本条件. 优化网站加载速度的原理主要是减少网站文件的大小,减少HTTP请求数.网站文件越小,浏览器加载页面会比较轻松,打开页面的速度也会提升:一个HTTP请求,对页面打开速度造成的延时大概是0.01秒,HTTP的请求数越多,网站打开的速度就会越慢. 那么如何减少网站文件的大小,减少HTTP请求数呢?笔者做前端(重构方向)有2年多了,这里分享3项优化网站加载速度的方法,希望能给有需要的同学以及刚接触前端的初学者带来帮