为什么应该继续使用 if else

引言

大学的时候学编程对于if else,不管哪本书上,都是这样教的:(我们假想一种实际不同用户的业务逻辑)

// 产品提出会员与非会员的计算逻辑
if (isVip) {
    System.out.println("8折出售");
} else {
    System.out.println("不打折出售");
}
// 产品又提出以前的会员逻辑不变,新增VVip(最近某些视频APP推出这种制度,坊间调侃为VVip)
if (type == UserType.VIP) {
    System.out.println("8折出售");
} else if (type == UserType.VVIP) {
    System.out.println("7折出售");
} else {
    System.out.println("不打折出售");
} 
// 产品又提出以前的会员逻辑不变,新增VVVip
if (type == UserType.VIP) {
    System.out.println("8折出售");
} else if (type == UserType.VVIP) {
    System.out.println("7折出售");
} else if (type == UserType.VVVIP) {
    System.out.println("6折出售");
} else {
    System.out.println("不打折出售");
}

这些业务逻辑随着时间的增长会变得越来越多,而写之前的代码的人已经离职了,现在出现新的业务逻辑超级会员,五折出售,你接手之后是继续添加还是用各“技术”重构?我在知乎上看到过,为了逃避Sonar设置的if else的三个分支上限的报告检查,在最后一个else里面调用新函数继续if else的极品赶着下班的方案。这种方式类似于我为了应对产品急着上线,草草地写了单元测试糊弄Jenkins质量检查。(我相信很多开发者都这么干过O(∩_∩)O)用死板的程序防止工程师写出恶心的代码,这是在侮辱工程师的智商。所以分享一下我在工作中的经验,如果有不合适的地方,欢迎探讨交流。

现象

因为现在大多数公司都是业务驱动,毕竟赚钱是王道,不赚钱你都不用在这里了。所以就产生,第一个开发者,if else多简单,一看就懂,随着业务开发,再加个 if else 也能接受。第二个开发者,看到之前代码,以前都这样写,我可不能动以前的代码,多个if else 也没毛病。过两天,再加两个 if ese ,第三个人接手之后,前面的人好恶心啊,业务这么复杂,算了,不优化了,我也加个 if else。

如果在稍大点的公司工作,Sonar或者Jenkins等工具检查代码质量的时候会显示 Cyclomatic Complexity,Cyclomatic Complexity,这是一种代码复杂度的衡量标准,中文叫圈复杂度。在软件测试的概念里,圈复杂度用来衡量一个模块判定结构的复杂程度,数量上表现为线性无关的路径条数,即合理的预防错误所需测试的最少路径条数。圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。什么意思呢?就是你写的代码,每个公司都有自己不同的单元测试分支覆盖率的标准,这里分支越来越多,单元测试会变得很复杂,或者压根就没法测了,因为这里并没有TDD(测试驱动开发)。

解决方案与分析

方案一 switch

switch和if else是半斤八两的写法,可读性差不多,switch 显得更整齐点。但没有 if esle 好维护,if else可以更灵活。不管怎么样,这两种方法都不利于写单测。而且代码规范都会要求case后边必须跟break,switch里边必须有default。但是对 if esle没有过多要求。而且 switch 的条件只能是同一类型的不同值,比如颜色,但是现实世界中有些时候遇到的是年龄段,而不是年龄值。

if (age == 10) {
    statement;
} else if (age == 20) {
    statement;
} else if (age >30) {
    statement;
} else {
    statement;
}
···

swich是通过跳转表设置索引值效率理论上更高,但在现在编译器中 if else switch 基本没差距,这种优化对效率基本没什么影响。

方案二 策略模式

// Java实际开发中和钱有关的不能用double,要用BigDecimal,这里只作为展示作用。
public interface Strategy {
    double compute(double money);
}

public class VipStrategy implements Strategy {

    @Override
    public double compute(double money) {
        return money * 0.8;
    }
}

public class VvipStrategy implements Strategy {

    @Override
    public double compute(double money) {
        return money * 0.7;
    }
}

public class VvvipStrategy implements Strategy {

    @Override
    public double compute(double money) {
        return money * 0.6;
    }
}

public class SvipStrategy implements Strategy {

    @Override
    public double compute(double money) {
        return money * 0.1;
    }
}

public static void main(String[] args) {
    Strategy svip = new SvipStrategy();
    svip.compute(money);
    Strategy vvip = new VvipStrategy();
    vvip.compute(money);
}

这种使用涉及模式的方式写底层没问题,写框架更没问题。但是涉及到业务代码,可读性降低,入门门槛高。其实是不利于维护和交接的。

方案三 奇技淫巧

提前return,枚举,Optional,工厂等方案,这一些方案其实说白了只是在某种程度上降低了复杂度,只是转移注意力,该判断还是要判断的。

感想

if else 是面向业务逻辑编程的,switch 是面向框架编程的。开发框架为什么可以使用大量的设计模式,因为框架是被清晰定义的,有严谨逻辑的。但是大多数工程师面对的是不断增加的业务逻辑,面对复杂的现实世界,抽象出来的业务逻辑使你无法使用固定的设计模式。当第一个工程师写下 if else, 悲剧已经产生了,世界就变得复杂了,随着需求逐渐增加,又想省事,借用之前的代码,先加一个,再加一个,于是逻辑分支越来越多,据说几百个if else 或者上千的分支代码不是没有。这个时候已经没有工程师愿意重构switch或者策略模式,毕竟业务复杂度摆在那里,你重构了,所有的东西都需要重测,测试不剁了你才怪。如果系统崩了,涉及大量资金损失,可以考虑下一家了。最终就变成了 if else if else if else ···,这种局面了。你不能说第一个工程师有错,因为产品并没有告诉他以后的情况,编程嘛,最重要的是不要为难自己,继续增加 if else ,测试,提交,上线没问题,打卡下班。因为有时候大多数的情况是两头都定下来了,你夹在中间,上游给你传了枚举类型,是这种枚举类型,就走一种逻辑,如果是另一种枚举类型,就走另一种逻辑,这时候,直接加if else是最快的解决方案了。

我也想成为优秀的工程师,有时候不是我不思考,有时候不是我不想写出优秀的代码,是不能写出优秀的代码,根本没有写高质量代码的客观环境。产品催,开发经理催,需求不停的变动,什么设计模式都不顶用,最终就是怎样快,怎样方便怎样来。现实工作绝不是软件工程课程讲的那么理想,会有无数的突发情况在等着你,等你考虑好了设计模式,抽象类,接口产品通知你有个需求要加塞,或者那个需求砍掉了。重构不仅增大自己工作量,也会增大测试团队的工作量,在现有交付压力的情况下,重构对交付产品毫无帮助。出问题不仅要自己背锅,而且连累别人。然而,这锅,本来不存在的,也不需要背的,是你给自己造了个锅。

正确是相对的,一个优秀的软件开发工程师要考虑到成本和分险,重构是体现优秀程序员的时候,但是考虑过可能会带来的额外风险,这种风险是否可以接受?有的时候不需要把代码当艺术品,能够适度忍受不完美,bug率可控,异常正常,有啥问题解决啥问题。给我几年时间,我也能把一个业务项目变成高雅艺术品,有啥用?!优秀的程序员应该按点把自己的事情做了。

祖传的代码尽量不要动他,if else 没啥大问题就先放着,先把手头的任务做了。if else 比动不动滥用策略模式好太多,策略模式作者感觉爽了,但是增加了维护成本。每个项目都有代码难看的地方,这是工程的必然,工程向的代码,第一要义要快速实现,第二要义是方便维护。设计模式在技术选型阶段定下来比较好,重构还是算了吧。拿着底层码农的薪水,却操着CTO都不操的心。业务驱动技术,不要维护只有自己关心但用户不关心的特性。大量的不规范的代码的源头,都是不专业的产品或策划,它们提出的不合理的需求,或者无脑的需求变更。

说了这么多,但这不是我们不学重构,代码整洁之道,设计模式的借口。设计模式和框架绝对一个项目中非常重要的东西,但不是绝对重要的,而本来就是几个 if else 就能直观解决的问题。什么是优秀的代码?能让人一眼看懂的代码才是好代码。业务代码个人感觉还是直观能看清楚实现逻辑,而不是在代码架构里跳来跳去。最简明易懂的代码结构有着最好的可维护性和可扩展性。

结论

宋代的青原行思大禅师说过这样一段话:"老僧三十年前来参禅时,见山是山,见水是水;及至后来亲见知识,有个入处,见山不是山,见水不是水;而今得个体歇处,依然见山还是山,见水还是水。"由此得来人生三个境界:"看山是山,看水是水;看山不是山,看水不是水;看山还是山,看水还是水。"

对于 if else if else ···情况,我们在学习编程的时候是第一重境界;等我学了设计模式,学了重构,学了代码整洁之道,感觉 if else if else太low了,设计模式多高大上,进入第二重境界;等我真正面对大量变动的业务逻辑的时候,我觉得还是第三重境界才是真。

所以,具体环境具体做法,通常情况下,可读性,逻辑清晰度永远是第一。能让读者一眼看懂的代码才是好代码。if else if else ··· 该加的时候毫不手软!而且还是最快的方式!

你可能感兴趣的:(为什么应该继续使用 if else)