赞 | 2 |
VIP | 143 |
好人卡 | 1 |
积分 | 1 |
经验 | 216792 |
最后登录 | 2019-10-10 |
在线时间 | 24 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 61
- 在线时间
- 24 小时
- 注册时间
- 2008-8-5
- 帖子
- 1924
|
2010-09-03 冗余代码
所谓冗余,是指没有意义、永远不会被执行的代码,或多次运算且结果相同的不必要代码。前两者很容易理解,这里我们主要说一下“多次运算的结果相同的不必要代码”。这种冗余不但可能导致代码可读性的降低(多次重复编写一大段代码),还可能降低代码运行的效率(代码容量的增加导致的程序装载效率以及多次进行结果必然相同的表达式运算)。在软件工程的哲学中有一个广为人知的原则,叫做 DRY 原则,全称是 Do not Repeat Yourself,是避免冗余的基本原则,意思是不要让相同的代码出现大于一次。例如:
- a = 0
- b = 32
- for i in 0..100
- a += i
- end
- for i in 5..323
- b += i
- end
- p a + b
复制代码 我们这里有两个整数 a 和 b,分别初始为 0 和 32,我们想让它们的值递增上一系列连续的整数,于是写了两个框架基本上一样的循环。其中第二个循环就是冗余代码,我们完全可以编写一个函数(子例程)来完成这一个运算:- def fn(a, b)
- ret = 0
- for i in a ..b
- ret += i
- end
- return ret
- end
- a = 0
- b = 32
- a += fn(0, 100)
- b += fn(5, 323)
- p a + b
复制代码 这是提高了代码可读性的冗余消除。另一种情况:- def fn(a, b)
- $global = a*b
- return a*b
- end
复制代码 这里,return 语句则是一句冗余代码,因为它在第一次计算 a*b 且结果保存在了 $global 后第二次计算了 a*b,而这是不必要的,因为 $global 这个变量是永远可见的,可以直接引用它并返回:
- def fn(a, b)
- $global = a*b
- return $global
- end
复制代码 然而这还属于无伤大雅的冗余,毕竟如今的 CPU 条件下,可以在极其高效的对数时间内完成乘法运算;糟糕的是出现需要花费时间执行的代码出现了冗余:- arr = [1, 3, 52, -2, 0, -234, 33, 6, 76, 44, 3, -2, 5]
- res = 0
- res += 6 if arr.include?(6)
- res += arr.sort[2]
- if arr.include?(6)
- res += arr.sort[3] << 3
- p "#{res}foo"
- end
- p "#{res}bar"
复制代码 这里,arr.include? 和 arr.sort 都出现了两次,而由于期间 arr 没有改变,故这两者的第二次出现都是冗余代码;恰好这两者都是耗时间的操作——include? 需要线性时间内完成,而 sort 需要线性对数时间内完成。我们将冗余消除后,理论上运行效率会提升将近一倍:
- arr = [1, 3, 52, -2, 0, -234, 33, 6, 76, 44, 3, -2, 5]
- res = 0
- has6 = arr.include?(6)
- sorted_arr = arr.sort
- res += 6 if has6
- res += sorted_arr[2]
- if has6
- res += sorted_arr[3] << 3
- p "#{res}foo"
- end
- p "#{res}bar"
复制代码 然而你也不能只留意在源脚本中实际出现了多次的代码冗余,有时在某些特殊控制结构里,比如循环,即时表达式在字面上仅出现了一次,实则也可能是一种冗余:- arr = [1, 3, 52, -2, 0, -234, 33, 6, 76, 44, 3, -2, 5]
- for i in 0..100
- sorted_arr = arr.sort
- p sorted_arr[rand(sorted_arr.length)]
- end
复制代码 这里,arr.sort 会被循环执行,但实际上进行排序的数组没有改变,每次排序的结果都是相同的,所以我们可以把这一行拿到循环外面去,避免了冗余:- sorted_arr = arr.sort
- for i in 0..100
- p sorted_arr[rand(sorted_arr.length)]
- end
复制代码 假设数组长度是 n,那理论上这里的冗余避免后效率是避免前的将近 100 倍。
DRY 原则也不是绝对要遵守的,要看情况而定——有时出现的冗余,比如多次通过 [] 数组下标访问数组元素,影响并不大,因为它们并不耗费多少时间,为了它们去分配一个临时变量来保存可能并不值得。在编译性语言以及有即时编译机制的解释性语言中,聪明的编译器和解释器会自动进行各种优化,如公共子表达式消除、死代码消除、常数合并,能杜绝大部分冗余,但 RM 所使用的 Ruby 1.8 MRI 解释器直接解释源代码,没有任何优化,所以是需要用户来考虑效率的 |
|