当我看到一堆if else时,我的内心是奔溃的

栏目: Java · 发布时间: 5年前

内容简介:不知大家有没遇到过像“横放着的金字塔”一样的if else嵌套:我并没夸大其词,我是真的遇到过了!嵌套6、7层,一个函数几百行,简!直!看!死!人!if else作为每种编程语言都不可或缺的条件语句,我们在编程时会大量的用到。但if else一般不建议嵌套超过三层,如果一段代码存在过多的if else嵌套,代码的可读性就会急速下降,后期维护难度也大大提高。所以,我们程序员都应该尽量避免过多的if else嵌套。下面将会谈谈我在工作中如何减少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个分支,还要改代码,一点都不“开放-闭合”。再再如果后面项目交接给他人跟进,他人又要把自己大脑变成处理器来想每个分支的作用,我敢肯定有百分之八十的人都会吐槽代码。

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

空值判断

业务判断

状态判断

几乎所有的业务都离不开这几个判断,从而导致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和内部接口shareImpl,ShareItem和ShareListener的判断都放在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获取具体值,消除分支。

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

当我看到一堆if else时,我的内心是奔溃的


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTML5

HTML5

Matthew David / Focal Press / 2010-07-29 / USD 39.95

Implement the powerful new multimedia and interactive capabilities offered by HTML5, including style control tools, illustration tools, video, audio, and rich media solutions. Understand how HTML5 is ......一起来看看 《HTML5》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具