高健:If-else,真的很Low吗?


一、入门练习题
先看个练习题,根据学生的成绩进行评级。
成绩区间 | 评级 |
---|---|
0-59 | 挂科 |
60-69 | 及格 |
70-79 | 中等 |
80-89 | 良好 |
90-99 | 优秀 |
100 | 满分 |
其他分数 | 分数不合法 |
这道题的逻辑,非常简单,初学代码一周的人,都能写出来。
但是我发现,有很多工作多年的人,仍然会这上面翻车,把简单的问题复杂化了。
在我的一个回答下,很多人在评论区里,对我在代码中使用大量的if-else表示不满,觉得if-else很Low,但是说不出个所以然来。
因此,我认为有必要好好的做一次详细的讲解和性能测试,看看在多种写法里,哪种写法最好,哪种写法最糟糕。
面对多条件判断,通常会有五种写法。
- 多if 语句
- if-elif语句
- 优化版本多if语句
- 表驱动
- Match case
这五种写法,都能实现其功能。但是一般的开发,很难说出来各种写法的优缺点,无法在实际开发中灵活运用。
因此,我特地把这五种写法都写出来,每种执行1000万次,
主要从可读性和执行时间两个维度,进行评价。
1.多if语句写法
- 使用多个if语句会降低程序性能,即使条件不成立,也需要执行每个if语句,导致效率低下。此外,程序员容易遗漏某些条件,如在写这段代码时,我就未检查大于100或小于0的情况。
- 多个if语句会导致代码逻辑混乱,难以理解。很容易出现一个表达式满足多个条件语句的情况。
- 在互斥条件下,多个if语句的性能较差,因为每个条件都需要进行评估。
- 因此,不推荐使用多个if语句,因为它们不仅可读性差,而且执行时间较长。
2.If-elif语句
为了解决互斥条件问题,就引入了if-elif语句。
if-elif语句的性能表现优异,因为它只会执行第一个满足条件的分支,从而避免了多个if语句带来的冗余判断,大大提升了程序的执行速度。同时,也降低了程序员的认知负担,避免因为疏忽导致bug的出现。
在Python中,当遇到大量条件或对性能敏感,那么使用if-elif语句是一个较好的实践。
在可读性上和性能上,if-elif的表现都很不错,可以满足绝大多数使用场景。
3.优化版本多if语句
前面说到多if语句的问题,从性能上,我们可以对其进行优化,每个判断条件后,都加个return语句,使其在满足一个条件时,就退出函数。
在改进之后,因为及时return,看起来很像Switch-case break,其性能也超过了前两种写法,不过该写法的可读性依旧不如if-elif。如果习惯用多if语句,可以及时return的方法进行优化。
4.表驱动
当if语句过多时,考虑使用表驱动进行优化。
表驱动通常更快,更简洁,因为表的查找可以在常数时间内完成,而if-else语句需要逐个评估每个条件,直到找到为真的条件。
因此,在经典书籍中,通常建议使用表驱动来优化if-else。
表驱动不仅可以消除大量if-else语句,在复杂情况下性能更优,而且通过将数据和逻辑分离,可以更容易地修改代码,而不必修改代码本身,从而使代码更具可扩展性。
选择表驱动还是if-else语句,这取决于代码的性能、可读性、可维护性之间的权衡。
在实际开发中,需要根据实际情况灵活应用。表驱动在分段函数的情况下性能最差。对于简单条件,使用if-else语句可能更简单并具有更高的可读性。此外,创建和维护表可能需要额外的开销,在某些情况下可能不值得性能提升。
5. Match case
以前,在Python里是没有“Switch case”语法。Python 在 3.10版本后 , “Switch case” 千呼万唤始出来。
按照语法来说,严格点说,应该是Match case,和其他静态语言不一样的是,Python保留了一贯简洁的风格,Match case里默认标识break,如果不习惯,可以增加return进行标识和优化。
可以看到,在Match case里,仍需要做判断,性能接近if elif, 但是可读性,远远不如if elif。当然,如果你是Match case 爱好者,当我没说;如果和你交接的同事不是Match case爱好者,TA 大概率会疯狂吐槽你。
在开发中,通常不会用Switch case,除了可读性差。还因为在实际业务中,条件语句通常较为复杂,不仅仅涉及简单的等于或不等于关系,还包括范围判断和多重逻辑判断等。此时,if else 语句更为灵活,通过多个 if else 语句可以实现更加复杂的条件判断。
相比之下,switch case 语句仅能对单个变量进行等值比较,无法处理复杂的条件判断。此外,if else 语句代码的可读性更强,易于理解和维护。因此,if else 语句通常被视为更优的选择。
二、运行结果测试
我写了两版本,第一版是用Python写的,跑了一千万次,执行速度,优化版的多if语句执行最快,传统的if elif语句紧随其后。而表驱动法,在执行用时最长。
在Python里跑一亿次,可以更清晰的看到优化的if语句和传统if-elif语句执行速度的优势。
第二版,相同的逻辑,我写了C#版,这也体现出编译性语言的优势,比Python这种解释性语言快多了。
在C#里,跑一亿次,执行速度最快的是依旧是优化版的多if语句,传统的if else 和Switch case紧随其后,最慢的依旧是表驱动。这个性能差距,就太大了。
当上到两亿次的时,性能差距会更明显。
可以看到,无论是在Python里还是在C#里,在遇到逻辑相对简单的代码时,传统的If……else要远胜过表驱动法。
可读性和维护性,请问在这个业务场景下,if-else if的可读性,真的要比表驱动法的可读性差吗?if...else if的可维护性真的不如表驱动吗?
想学好编程,一定要动手去验证。
三、C#逻辑代码:
1.多If语句
2.if-else语句
3.多if语句优化版
4.表驱动
5. Switch Case
四、为什么C#会比Python快这么多?
这里再单独解释一下,用Python是因为写的快,用C#是因为程序跑起来快。
C#之所以快,有以下几个原因:
-
编译性语言vs 解释性语言:C# 是一种编译语言,它在运行之前会被翻译成机器码,这使得执行速度更快。而Python 是一种解释语言,它会逐行解释和执行代码,因此程序执行时间会更慢。
-
静态类型 vs 动态类型:C# 是一种静态类型语言,变量的数据类型在编译时已经确定,这使得内存分配更有效率,执行更快。而 Python 是一种动态类型语言,变量的数据类型在运行时才能确定,因此可能导致更慢的执行时间和效率较低的内存分配。
-
JIT 优化:C# 使用 Just-In-Time(JIT)编译器在运行时优化代码,这使得执行更有效率、更快速。Python 也有 JIT 优化,例如 PyPy,但它不像 C# 的 JIT 编译器那样被广泛使用。
-
低层编程结构:C# 提供了指针和内存管理等低层编程结构,这使得程序的性能可以更加精细地控制。相比之下,Python 没有这些低层结构,因此执行的更慢。
综上几个原因,使得Python在实际开发中,占比相对不高。
五、语法和语言的取舍
新工具的出现,很大程度上,在于弥补旧工具的缺陷以及应对新的业务场景。
语法和语言一样,没有最好的,只有最合适的,根据业务进行权衡和取舍。
初学编程时:
看山是山,看水是水
上手编程一段时间:
看山不是山,看水不是水
踩过编程里大大小小的坑以后:
看山还是山,看水还是水。
六、表驱动最佳实践
放到这道题,表驱动也有优化空间。
第一,可以通过把Map放到方法外边。
第二,通过除法,取出成绩高位,通过该方法查表。把在区间取值,转换根据值取值。
优化过后,该表驱动的速度,要远远优于未优化的表驱动方法,但还是落后于传统的if-else方法。
七、通过数组下标进行取值
通过观察,可以看到,在表驱动里,通过key取value,比如用3,代表挂科,用6代表及格,用10代表满分。
这种方法,在表驱动里最为通用。但在本例里,既然用数字做key,这个key可以用数组的索引来表示,因此可以进一步优化其结构,用List代替Map。