说来也是巧最近在看 Dubbo 源码,然后发现了一处很奇怪的代码,刚好和这个 switch 和 if else 有关!
让我们来看一下这段代码,它属于 ChannelEventRunnable
,这个 runnable 是 Dubbo IO 线程创建,将此任务扔到业务线程池中处理。
看到没,把 state == ChannelState.RECEIVED
拎出来独立一个 if,而其他的 state 还是放在 switch 里面判断。
我当时脑子里就来回扫描,想想这个到底有什么花头,奈何知识浅薄一脸懵逼。
于是就开始了一波探险之旅!
遇到问题当然是问搜索引擎了,一般而言我会同时搜索各大引擎,咱这也不说谁比谁好,反正有些时候度娘还是不错的,比如这次搜索度娘给的结果比较靠前,google 较靠后。
一般搜索东西我都喜欢先在官网上搜,找不到了再放开搜,所以先这么搜 site:xxx.com key
。
你看这就有了,完美啊!
我们先来看看官网的这篇博客怎么说的,然后再详细地分析一波。
现代 CPU 都支持分支预测 (branch prediction) 和指令流水线 (instruction pipeline),这两个结合可以极大提高 CPU 效率。对于像简单的 if 跳转,CPU 是可以比较好地做分支预测的。但是对于 switch 跳转,CPU 则没有太多的办法。 switch 本质上是根据索引,从地址数组里取地址再跳转。
也就是说 if 是跳转指令,如果是简单的跳转指令的话 CPU 可以利用分支预测来预执行指令,而 switch 是要先根据值去一个类似数组结构找到对应的地址,然后再进行跳转,这样的话 CPU 预测就帮不上忙了。
然后又因为一个 channel 建立了之后,超过99.9%情况它的 state 都是 ChannelState.RECEIVED,因此就把这个状态给挑出来,这样就能利用 CPU 分支预测机制来提高代码的执行效率。
并且还给出了 Benchmark 的代码,就是通过随机生成 100W 个 state,并且 99.99% 是 ChannelState.RECEIVED,然后按照以下两种方式来比一比(这 benchSwitch 官网的例子名字打错了,我一开始没发现后来校对文章才发现)。
虽然博客也给出了它的对比结果,但是我还是本地来跑一下看看结果如何,其实 JMH 不推荐在 ide 里面跑,但是我懒,直接 idea 里面跑了。
从结果来看确实通过 if 独立出来代码的执行效率更高(注意这里测的是吞吐),博客还提出了这种技巧可以放在性能要求严格的地方,也就是一般情况下没必要这样特殊做。
至此我们已经知道了这个结论是对的,不过我们还需要深入分析一波,首先得看看 if 和 switch 的执行方式到底差别在哪里,然后再看看 CPU 分支预测和指令流水线的到底是干啥的,为什么会有这两个东西?
我们先简单来个小 demo 看看 if 和 switch 的执行效率,其实就是添加一个全部是 if else 控制的代码, switch 和 if + switch 的不动,看看它们之间对比效率如何(此时还是 RECEIVED 超过99.9%)。
来看一下执行的结果如何:
好家伙,我跑了好几次,这全 if 的比 if + switch 强不少啊,所以是不是源码应该全改成 if else 的方式,你看这吞吐量又高,还不会像现在一下 if 一下又 switch 有点不伦不类的样子。
我又把 state 生成的值改成随机的,再来跑一下看看结果如何:
我跑了多次还是 if 的吞吐量都是最高的,怎么整这个全 if 的都是最棒滴。
在我的印象里这个 switch 应该是优于 if 的,不考虑 CPU 分支预测的话,当从字节码角度来说是这样的,我们来看看各自生成的字节码。
先看一下 switch 的反编译,就截取了关键部分。
也就是说 switch 生成了一个 tableswitch,上面的 getstatic 拿到值之后可以根据索引直接查这个 table,然后跳转到对应的行执行即可,也就是时间复杂度是 O(1)。
比如值是 1 那么直接跳到执行 64 行,如果是 4 就直接跳到 100 行。
关于 switch 还有一些小细节,当 swtich 内的值不连续且差距很大的时候,生成的是 lookupswitch,按网上的说法是二分法进行查询(我没去验证过),时间复杂度是 O(logn),不是根据索引直接能找到了,我看生成的 lookup 的样子应该就是二分了,因为按值大小排序了。
还有当 switch 里面的值不连续但是差距比较小的时候,还是会生成 tableswtich 不过填充了一些值,比如这个例子我 switch 里面的值就 1、3、5、7、9,它自动填充了2、4、6、8 都指到 default 所跳的行。
让我们再来看看 if 的反编译结果:
可以看到 if 是每次都会取出变量和条件进行比较,而 switch 则是取一次变量之后查表直接跳到正确的行,从这方面来看 switch 的效率应该是优于 if 的。当然如果 if 在第一次判断就过了的话也就直接 goto 了,不会再执行下面的哪些判断了。
所以从生成的字节码角度来看 switch 效率应该是大于 if 的,但是从测试结果的角度来看 if 的效率又是高于 switch 的,不论是随机生成 state,还是 99.99% 都是同一个 state 的情况下。
首先 CPU 分支预测的优化是肯定的,那关于随机情况下 if 还是优于 switch 的话这我就有点不太确定为什么了,可能是 JIT 做了什么优化操作,或者是随机情况下分支预测成功带来的效益大于预测失败的情形?
难道是我枚举值太少了体现不出 switch 的效果?不过在随机情况下 switch 也不应该弱于 if 啊,我又加了 7 个枚举值,一共 12 个值又测试了一遍,结果如下:
好像距离被拉近了,我看有戏,于是我背了波 26 个字母,实不相瞒还是唱着打的字母。
扩充了分支的数量后又进行了一波测试,这次 swtich 争气了,终于比 if 强了。
题外话: 我看网上也有对比 if 和 switch 的,它们对比出来的结果是 switch 优于 if,首先 jmh 就没写对,定义一个常量来测试 if 和 switch,并且测试方法的 result 写了没有消费,这代码也不知道会被 JIT 优化成啥样了,写了几十行,可能直接优化成 return 某个值了。
对比了这么多我们来小结一下。
首先对于热点分支将其从 switch 提取出来用 if 独立判断,充分利用 CPU 分支预测带来的便利确实优于纯 swtich,从我们的代码测试结果来看,大致吞吐量高了两倍。
而在热点分支的情形下改成纯 if 判断而不是 if + swtich的情形下,吞吐量提高的更多。是纯 switch 的 3.3 倍,是 if + switch 的 1.6 倍。
在随机分支的情形下,三者差别不是很大,但是还是纯 if 的情况最优秀。
但是从字节码角度来看其实 switch 的机制效率应该更高的,不论是 O(1) 还是 O(logn),但是从测试结果的角度来说不是的。
在选择条件少的情况下 if 是优于 switch 的,这个我不太清楚为什么,可能是在值较少的情况下查表的消耗相比带来的收益更大一些?有知道的小伙伴可以在文末留言。
在选择条件很多的情况下 switch 是优于 if 的,再多的选择值我就没测了,大伙有兴趣可以自己测测,不过趋势就是这样的。
接下来咱们再来看看这个分支预测到底是怎么弄的,为什么会有分支预测这玩意,不过在谈到分支预测之前需要先介绍下指令流水线(Instruction pipelining),也就是现代微处理器的 pipeline。
CPU 本质就是取指执行,而取指执行我们来看下五大步骤,分别是获取指令(IF)、指令解码(ID)、执行指令(EX)、内存访问(MEM)、写回结果(WB),再来看下维基百科上的一个图。
当然步骤实际可能更多,反正就是这个意思需要经历这么多步,所以说一次执行可以分成很多步骤,那么这么多步骤就可以并行,来提升处理的效率。
所以说指令流水线就是试图用一些指令使处理器的每一部分保持忙碌,方法是将传入的指令分成一系列连续的步骤,由不同的处理器单元执行,不同的指令部分并行处理。
就像我们工厂的流水线一样,我这个奥特曼的脚拼上去了马上拼下一个奥特曼的脚,我可不会等上一个奥特曼的都组装完了再组装下一个奥特曼。
当然也没有这么死板,不一定就是顺序执行,有些指令在等待而后面的指令其实不依赖前面的结果,所以可以提前执行,这种叫乱序执行。
我们再说回我们的分支预测。
这代码就像我们的人生一样总会面临着选择,只有做了选择之后才知道后面的路怎么走呀,但是事实上发现这代码经常走的是同一个选择,于是就想出了一个分支预测器,让它来预测走势,提前执行一路的指令。
那预测错了怎么办?这和咱们人生不一样,它可以把之前执行的结果全抛了然后再来一遍,但是也有影响,也就是流水线越深,错的越多浪费的也就越多,错误的预测延迟是10至20个时钟周期之间,所以还是有副作用的。
简单的说就是通过分支预测器来预测将来要跳转执行的那些指令,然后预执行,这样到真正需要它的时候可以直接拿到结果了,提升了效率。
分支预测又分了很多种预测方式,有静态预测、动态预测、随机预测等等,从维基百科上看有16种。
我简单说下我提到的三种,静态预测就是愣头青,就和蒙英语选择题一样,我管你什么题我都选A,也就是说它会预测一个走势,一往无前,简单粗暴。
动态预测则会根据历史记录来决定预测的方向,比如前面几次选择都是 true ,那我就走 true 要执行的这些指令,如果变了最近几次都是 false ,那我就变成 false 要执行的这些指令,其实也是利用了局部性原理。
随机预测看名字就知道了,这是蒙英语选择题的另一种方式,瞎猜,随机选一个方向直接执行。
还有很多就不一一列举了,各位有兴趣自行去研究,顺便提一下在 2018 年谷歌的零项目和其他研究人员公布了一个名为 Spectre 的灾难性安全漏洞,其可利用 CPU 的分支预测执行泄漏敏感信息,这里就不展开了,文末会附上链接。
之后又有个名为 BranchScope 的攻击,也是利用预测执行,所以说每当一个新的玩意出来总是会带来利弊。
至此我们已经知晓了什么叫指令流水线和分支预测了,也理解了 Dubbo 为什么要这么优化了,但是文章还没有结束,我还想提一提这个 stackoverflow 非常有名的问题,看看这数量。
这个问题在那篇博客开头就被提出来了,很明显这也是和分支预测有关系,既然看到了索性就再分析一波,大伙可以在脑海里先回答一下这个问题,毕竟咱们都知道答案了,看看思路清晰不。
就是下面这段代码,数组排序了之后循环的更快。
然后各路大神就蹦出来了,我们来看一下首赞的大佬怎么说的。
一开口就是,直击要害。
You are a victim of branch prediction fail.
紧接着就上图了,一看就是老司机。
他说让我们回到 19世纪,一个无法远距离交流且无线电还未普及的时候,如果是你这个铁路交叉口的扳道工,当火车快来的时候,你如何得知该扳哪一边?
火车停车再重启的消耗是很大的,每次到分叉口都停车,然后你问他,哥们去哪啊,然后扳了道,再重启就很耗时,怎么办?猜!
猜对了火车就不用停,继续开。猜错了就停车然后倒车然后换道再开。
所以就看猜的准不准了!搏一搏单车变摩托。
然后大佬又指出了关键代码对应的汇编代码,也就是跳转指令了,这对应的就是火车的岔口,该选条路了。
后面我就不分析了,大伙儿应该都知道了,排完序的数组执行到值大于 128 的之后肯定全部大于128了,所以每次分支预测的结果都是对了!所以执行的效率很高。
而没排序的数组是乱序的,所以很多时候都会预测错误,而预测错误就得指令流水线排空啊,然后再来一遍,这速度当然就慢了。
所以大佬说这个题主你是分支预测错误的受害者。
最终大佬给出的修改方案是咱不用 if 了,惹不起咱还躲不起嘛?直接利用位运算来实现这个功能,具体我就不分析了,给大家看下大佬的建议修改方案。
这篇文章就差不多了,今天就是从 Dubbo 的一段代码开始了探险之旅,分析了波 if 和 switch,从测试结果来看 Dubbo 的这次优化还不够彻底,应该全部改成 if else 结构。
而 swtich 从字节码上看是优于 if 的,但是从测试结果来看在分支很多的情况下能显示出优势,一般情况下还是打不过 if 。
然后也知晓了什么叫指令流水线,这其实就是结合实际了,流水线才够快呀,然后分支预测预执行也是一个提高效率的方法,当然得猜的对,不然分支预测错误的副作用还是无法忽略的,所以对分支预测器的要求也是很高的。
JMH 的测试代码我也打个包,想自己跑的同学后台输入「分支预测」即可获取
Spectre :https://www.freebuf.com/vuls/160161.html
Dubbo 博客 :http://dubbo.apache.org/zh-cn/blog/optimization-branch-prediction.html
https:// en.wikipedia.org/wiki/I nstruction_pipelining
https:// en.wikipedia.org/wiki/B ranch_predictor
没想到有这么多赞哈,贴个我个人GitHub
上面有我的很多文章汇总,稍微截一些~
大家觉得有帮助的话,给我来个赞哟~ 关注 @是Yes呀
现实世界是复杂而混乱的,而应用开发要解决的就是现实世界的问题,只有让代码迁就现实世界,而不是反过来。
switch 的条件只能是同一类型的不同值,比如颜色
case red
case blue
。。。
而现实世界有些时候遇到的问题是这样的
if color == red
else if shape == rectangle
else if height > 6 and color != blue
else
。。。
写数学算法 计算机基础框架的时候要幸福得多,毕竟此时世界是被清晰定义,有严谨逻辑的,但是一旦写业务逻辑代码,就要面对复杂混乱的现实世界,而现实世界问题才是占比最高的,这样的代码也是占总代码的绝大多数。
如果按面向对象重构原则来说,switch本身也是很不够妥当的的,除了Command类似模式下,凡是可以用switch的地方,都应该考虑使用继承类或其它设计模式来实现,以减少过于复杂的逻辑分支,增加代码可读性。
并且在一些基础语言中,switch很多时候不好使用,比如它不支持字符串,比如它不支持复杂逻辑判断并且default比起if else明显会出现很多的思维负担,用起来思维并不流畅。
更关键的是临时的功能需求往往是多变的,在实际使用时,才会发现问题所在,于是要增减功能,无论之后期是否要重构代码,用if else时,风险都会大大降低,更加有灵活性。
分支非常多的 if 和 else if 往往并不是一次写出来的,而是每次增加新功能时就开个新的分支。对于每一个增加分支的人来说,他们都不觉得自己有责任要重构整段代码,因为他们只想用最低的成本把自己要做的事情做完,于是代码质量变得越来越低。
一般来说,如果 if 和 else if 分支超过 3 个就可以考虑写成 switch。如果 switch 的分支超过 10 个就可以考虑写成 config,然后专门写一个函数根据 config 来做 mapping。如果需要进行的映射逻辑很复杂,但使用频率很高,可以考虑做一个专门的 rule engine 来处理这件事情,或者是一门 DSL。
这个问题如果是一般的程序员还真不知道为啥,只知道别人都是这样写的,所以跟着这样写了,很少有人会深入地去思考这个问题,你能有这个疑问,说明你有一颗求真的心(我当年也想过 )。
这个问题对编程没有一定功底,是答不出个所以然的。我认为主要可以从编码习惯和程序性能上去思考。
我首先来说一下编码习惯,先不说其它的,单从代码行数来看的话,实现相同的逻辑,switch
比if...else if
占用的行数是要多一些的,请看下面的示例代码:
switch版
public class Demo1 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; for (int i = 0; i < 10000000; i++) { b = i % 10; switch (b) { case 1: a = a + 2; break; case 2: a = a + 3; break; case 3: a = a + 4; break; case 4: a = a + 5; break; case 5: a = a + 6; break; case 6: a = a + 7; break; case 7: a = a + 8; break; case 8: a = a + 9; break; case 9: a = a + 10; break; } } e = System.nanoTime(); System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f); } }
if ... eles if 版
public class Demo2 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; for (int i = 0; i < 10000000; i++) { b = i % 10; if (b == 1) { a = a + 2; } else if (b == 2) { a = a + 3; } else if (b == 3) { a = a + 4; } else if (b == 4) { a = a + 5; } else if (b == 5) { a = a + 6; } else if (b == 6) { a = a + 7; } else if (b == 7) { a = a + 8; } else if (b == 8) { a = a + 9; } else if (b == 9) { a = a + 10; } } e = System.nanoTime(); System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f); } }
上面两个示例类Demo1
和Demo2
实现的逻辑一模一样,Demo1
使用switch
实现,代码总行数是40行;而Demo2
使用if ... else if
实现,总共只有30行(这里显示不了行号,你可以把代码复制到自己的编辑器去看)。
单从这点来看,if ... else if
就完胜了 ,毕竟程序员每多敲一个字母,对他手的伤害就会大一些,程序员的手还有其它很重要的用途的 !所以...新手程序员们要保护好自己的手哈。
说到新手,这里就不得不提使用switch
的一个坑了,一般新手都会犯这样的错误,就是忘记写break、忘记写break、忘记写break,这个问题我见过90%的新手都会遇到,break
不写编译不会报错,因为在语法上来说,不写也是对的,只是写于不写,所表达的意思不一样。
既然语法是对的,编译当然能通过。因为编译主要就是检测程序的语法是否合格,合格就将源码转换成机器可以执行的代码。所以这也是为什么不写break
,还能编译通过的原因。
但程序在执行时,有break
和没有break
,结果会完全不一样,就拿上面Demo1
的例子来说,少了一个break
,变量a
的结果都会有变化。所以,对于新手来说,switch
中的break
就是一个隐藏的炸弹,没用好就会产生bug。
但如果你的编程水平高,能彻底明白有break
和没有break
的区别,那在某些场景还是挺有用的,再来看下面的示例代码:
switch 版:
import java.util.Scanner; public class Demo3 { public static void main(String[] args) { System.out.print("请输入你的姓名:"); Scanner scanner = new Scanner(System.in); String name = scanner.next(); switch (name) { case "张三": case "李四": System.out.println("欢迎你"); break; case "王五": System.out.println("对不起,这里不欢迎你"); break; default: System.out.println("你叫啥?"); break; } } }
if ... else if 版
import java.util.Scanner; public class Demo4 { public static void main(String[] args) { System.out.print("请输入你的姓名:"); Scanner scanner = new Scanner(System.in); String name = scanner.next(); if ("张三".equals(name) || "李四".equals(name)) { System.out.println("欢迎你"); } else if ("王五".equals(name)) { System.out.println("对不起,这里不欢迎你"); } else { System.out.println("你叫啥?"); } } }
这个示例的逻辑很简单,在控制台输入姓名,然后如果是张三或李四,就打印欢迎你,对王五打印不欢迎的内容。
这里如果仅从代码行数去看的话,还是使用if ... else if
占优势,但从代码的可阅读性和可扩展性来说的话,明显使用switch
更合适。
为什么这么说?举个栗子,业务上现在是要求对张三和李四说欢迎,如果隔壁的产品经理哪天想给你找点事干,要求对赵一、赵二、赵三等等都说欢迎,那在Demo4
里面得不断往if
的条件后再追加条件;而在switch
版本里面只需要追加一个case
,而且看上去还一目了然,结构非常清晰,示例如下:
import java.util.Scanner; public class Demo3 { public static void main(String[] args) { System.out.print("请输入你的姓名:"); Scanner scanner = new Scanner(System.in); String name = scanner.next(); switch (name) { case "赵一": // 追加的需求 case "张三": case "李四": System.out.println("欢迎你"); break; case "王五": System.out.println("对不起,这里不欢迎你"); break; default: System.out.println("你叫啥?"); break; } } }
另外,在某些场景使用switch
也会增加程序的复杂性和可读性。就拿上面的Demo1
的代码来说,如果想要在switch
的case 1
里面跳出当前的for
循环,那该怎样做呢?
public class Demo1 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; for (int i = 0; i < 10000000; i++) { b = i % 10; switch (b) { case 1: a = a + 2; // 在此次如何跳出for循环??? break; case 2: // 此处省略诺干行...... } } } }
大家知道在for
循环中要跳出当前的for
循环,也是使用break
关键字,但此处是在switch
里面,switch
里的break
是退出当前的case
。所以。。。 对于小白来说,这里就懵了!
真的,一般人还真不知道怎样处理这个问题,有人会想到立一个flag,在switch
结束后去判断这个flag,或者直接判读i
的值,比如下面这样:
public class Demo1 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; for (int i = 0; i < 10000000; i++) { b = i % 10; switch (b) { case 1: a = a + 2; break; case 2: // 此处省略诺干行...... } // 在此处判断,满足条件就跳出循环 if (i == 0) { break; } } } }
还有人可能会用到Java
中的标签
语法,就像这样:
public class Demo1 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; LABEL_FOR:// 给此处的for循环定义一个标签 for (int i = 0; i < 10000000; i++) { b = i % 10; switch (b) { case 1: a = a + 2; break LABEL_FOR; // 跳出for循环 case 2: // 此处省略诺干行...... } } } }
这种方法一般人也想不到,因为很少有人这样写,有人写了四五年的代码都不一定见过,但这的确是Java
中支持的语法。
虽然这样比上面的判断方法看上去优雅,但增加了程序的复杂性和可读性。如果使用的是if else if
,根本不用搞这么复杂,直接使用break
即可,就像这样:
public class Demo2 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; for (int i = 0; i < Integer.MAX_VALUE; i++) { b = i % 10; if (b == 1) { a = a + 2; break; // 此处的break可以直接跳出当前for循环 } else if (b == 2) { // 此处省略诺干行......
所以,从编码风格来说的话,需要结合实际的业务需求去选择使用哪种语法。
从性能上来说的话,如果不是做大量的运算的话,性能上是几乎没有差异的。
就拿上面的Demo1
和Demo2
来说的话,两个程序运行耗时都是差不多。
但如果把for
循环的次数加大几倍,其结果就明显了,看下面的示例和结果:
switch 版
public class Demo1 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; for (int i = 0; i < Integer.MAX_VALUE; i++) { b = i % 10; switch (b) { case 1: a = a + 2; break; case 2: a = a + 3; break; case 3: a = a + 4; break; case 4: a = a + 5; break; case 5: a = a + 6; break; case 6: a = a + 7; break; case 7: a = a + 8; break; case 8: a = a + 9; break; case 9: a = a + 10; break; } } e = System.nanoTime(); System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f); } }
if ... else if 版
public class Demo2 { public static void main(String[] args) { long s = System.nanoTime(), e; int a = 0, b; for (int i = 0; i < Integer.MAX_VALUE; i++) { b = i % 10; if (b == 1) { a = a + 2; } else if (b == 2) { a = a + 3; } else if (b == 3) { a = a + 4; } else if (b == 4) { a = a + 5; } else if (b == 5) { a = a + 6; } else if (b == 6) { a = a + 7; } else if (b == 7) { a = a + 8; } else if (b == 8) { a = a + 9; } else if (b == 9) { a = a + 10; } } e = System.nanoTime(); System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f); } }
以上代码中,将for
循环的次数改为了Integer.MAX_VALUE
。再来看一下两个程序运行的结果:
先贴上我的机器配置:
说明:不同的机器配置,运行耗时会不一样。
Demo1运行3次的结果
运行时间:4464.96毫秒 运行时间:4501.70毫秒 运行时间:4660.67毫秒
Demo2运行3次的结果
运行时间:5892.39毫秒 运行时间:6014.14毫秒 运行时间:5440.15毫秒
通过对比会发现,在做大量运算时,switch
的性能更优。
所以,如果你看过一些框架的源码就会发现那些作者他们很多地方都用的是switch
,就是这个原因。
switch
和if ... else if
各有利弊,需要结合实际的业务需求去选择使用哪种语法。switch
和if ... else if
几乎没有差异,只有在需要做大量的运算,switch
的优势才能体现。----------------------------------------------------
觉得有用的麻烦点赞关注走一波,谢谢!!!
我曾经接手过一份代码,遇到过一个三十几个if else套if else的模块。
心理骂骂咧咧谁他喵写的这玩意,然后开始review历史。
大致情况是这样的:第一个程序员写下这段代码时,只有两个if else;后来开始逐渐加需求,先是一个、两个,随后量变引起质变,于是逻辑分支快速扩张。
这个时候已经没有人愿意去重构成switch或是其他什么设计模式了,毕竟复杂度摆在那里,万一崩了还得背锅。
三四个程序员接手这段代码之后,就变成我现在这种局面了。
第一个程序员绝对没有料到这么简单的逻辑在之后会变成这么复杂的模块,甚至在增添第一第二条else时,也只是很随意的加上。
所以我觉得,这个锅绝对是是甲方的,让他娘的随便改需求。
这么一想心里就好受多了,编程嘛,最重要的是要看的开。
于是我又增加了两条else,测试,提交,下班。
————————————————
回来更新一波,下文与本题目无关,算私货:
papi酱更新了一个视频,别人是看着看着就笑了,我是看着看着就哭了。
http://www. bilibili.com/video/av37 849097
之前的回答算是皮一下,带有一点自我调侃的意味。但大环境的事实如何,各位想必也是心知肚明的,不然也不会有这么多赞同了。
有时候真的不是我们不想写好代码,是不能写好代码。写着写着需求砍了、需求变了,什么设计模式都不顶用,最终还是怎样快怎样方便怎样来,因为根本没人知道这段代码还能不能活的过下一段需求变动。
有的人肯定要说怎么不订合同。合同肯定是有的,但是明明白纸黑字写的合同,该改还是得改,毕竟你要是不同意甲方那些“微小的变动”,以后还做不做了?!金主真能去得罪?
还是要学会得过且过,跟什么过不去也不能跟自己过不去,糟糕的代码忍一忍就完了:代码能跑、头发不少,对我们这些打工的人而言比什么都重要。
现实工作绝不是课本中的理想状态,会有无数的突发情况在等着你。你定义了半天观察者、备忘录,第二天这部分需求被砍了;写了半天接口,抽象类,忽然下午告诉你要加个十万八千里打不着边的啥东西,于是又开始加适配器,等你加完了告诉你又砍了。甚至有次半夜被喊起来改代码,等改完了发现需求被撤回了,气的我直接请了两天假调整心情。
设计模式和大的框架绝对是一个项目中非常重要的东西,但不是绝对重要的;一个好的PM团队,在某种意义上,才真正决定了这个项目的代码质量。
1、语法上并没有更简单。
2、break心智负担。
3、适用范围太小,为了统一美观还不如都用if else if
那么,唐僧在花果山念一段紧箍咒,就会当上猴王吗?
如来把孙悟空压在五行山下,花果山的猴子会认为如来是猴王吗?
不扎根基层,群众是不会认可的!
原答案被折叠了:
盖世猴王为何裸死猴山? 数百头母猴为何半夜惨叫? 动物园最大猴群为何屡遭黑手? 游客投掷的食物为何频频失窃? 连环强 J母猴案,究竟是何人所为? 动物园管理处的门夜夜被敲,究竟是人是鬼? 数百只小猴意外身亡的背后又隐藏着什么? 这一切的背后, 是人性的扭曲还是道德的沦丧? 是性的爆发还是饥渴的无奈? 敬请关注今晚8点CCTV12法制频道年度巨献《题主的不归之路》 让我们跟随着镜头走进题主的内心世界…
三峡
有了它很多长江生物灭绝了或正在灭绝。