11.3 重构计算顺序
我们已经看了如何在使用不可变数据结构的代码中,跟踪函数之间的依赖关系。一旦我们知道了依赖关系是什么,有时就可以调整操作的顺序,使程序更有效,而保持原来的意思不变。清单11.12 就是这种类型优化的一个简单例子。
清单11.12 重构程序中的计算(C#)
var num = Calculate1(10); [1] var test = TestCondition(); if (test == true) return Calculate2(num); else return 0; | var test = TestCondition(); if (test == true) { var num = Calculate1(10); [2] return Calculate2(num); } else return 0; |
在第一个版本中,我们是在程序开始时调用Calculate1 函数[1],而调用的结果只在TestCondition 返回true 时使用;如果不是这种情况,我们就没有任何理由执行Calculate1 函数,浪费CPU 时间!在第二个版本中,我们把这个计算移到了if 条件内[2],所以,只在需要这个结果时才计算。
这个修改很简单,你可能有写过更有效的版本,只是没有思考过。随着程序逐渐增长,像这样的优化变得更加难以发现。清单11.13 是一个稍微复杂的例子。
清单11.13 传递计算结果给函数(C#)
int TestAndCalculate(int num) {
vartest = TestCondition(); [1]
if (test== true)
returnCalculate2(num); [2]
elsereturn 0;
}
TestAndCalculate(Calculate1(10)); [3] <-- 在后面的程序中使用
在这个例子中,函数的参数为值num,但函数可能根本就不需要这个值。如果条件[1]的计算结果为false,函数返回0,num 值就没有意义了;当调用此函数时[3],函数Calculate1 总是会执行,即使我们后来发现不需要它的结果。
在Haskell(另一种流行的函数式语言)中,如果代码不需要Calculate1 的结果,不会调用Calculate1;因为,Haskell 使用了不同的计算策略(evaluation strategy)。在回到优化清单11.13 之前,我们要讨论几种策略。