ITEM 57: MINIMIZE THE SCOPE OF LOCAL VARIABLES
这一项在性质上与 item 15 相似,即“最小化类和成员的可访问性”。通过最小化局部变量的范围,可以提高代码的可读性和可维护性,并降低出错的可能性。
较早的编程语言(如C)强制局部变量必须在块的头部声明,一些程序员出于习惯继续这样做。这是一个值得打破的习惯。作为一个温和的提醒,Java 允许您在语句合法的任何地方声明变量(与C一样,因为C99)。
最小化局部变量范围的最强大的技术是在第一次使用它的地方声明它。如果变量是在使用之前声明的,那么它就只是杂乱的——这是另一件分散读者注意力的事情,读者正在试图弄清楚程序的功能。在使用变量时,读者可能不记得变量的类型或初始值。
过早地声明局部变量不仅会导致它的作用域开始得太早,而且结束得太晚。局部变量的范围从声明它的地方扩展到封闭块的末尾。如果一个变量在使用它的块的外部声明,那么在程序退出该块之后它仍然是可见的。如果一个变量在其预期使用区域之前或之后被意外使用,其后果可能是灾难性的。
几乎每个局部变量声明都应该包含一个初始化器。如果您还没有足够的信息来合理地初始化一个变量,您应该推迟声明,直到您这样做。
这个规则的一个例外是 try-catch 语句。如果一个变量被初始化为一个表达式,而该表达式的计算可以抛出一个已检查的异常,那么这个变量必须在一个 try 块中初始化(除非封闭的方法可以传播这个异常)。如果该值必须在 try 块之外使用,那么它必须在 try 块之前声明,此时它还不能“合理地初始化”。例如,参见283页。
循环提供了一个特殊的机会来最小化变量的范围。for循环(包括传统形式和 for-each 形式)允许您声明循环变量,将它们的作用域限制在需要它们的特定区域。(这个区域由循环体和 for 关键字与循环体之间括号中的代码组成。)因此,如果循环终止后不需要循环变量的内容,那么最好使用 for 循环而不是 while 循环。
例如,这里是迭代一个集合的首选习惯用法(item 58):
// Preferred idiom for iterating over a collection or array
for (Element e : c) {
... // Do Something with e
}
如果您需要访问迭代器,可能需要调用它的 remove 方法,首选的习惯用法是使用传统的 for 循环来代替 for-each 循环:
// Idiom for iterating when you need the iterator
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
Element e = i.next();
... // Do something with e and i
}
要了解为什么这些for循环优于while循环,请考虑下面的代码片段,其中包含两个while循环和一个bug:
Iterator<Element> i = c.iterator();
while (i.hasNext()) {
doSomething(i.next());
}
...
Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) { // BUG!
doSomethingElse(i2.next());
}
第二个循环包含一个复制-粘贴错误: 它初始化一个新的循环变量 i2,但是使用旧的变量 i,不幸的是,它仍然在作用域中。生成的代码编译时没有错误,运行时没有抛出异常,但是它做了错误的事情。第二个循环不是遍历 c2,而是立即终止,这会给人留下 c2 为空的错误印象。因为程序会无声地出错,所以错误可能会在很长一段时间内保持不被检测到。
如果将类似的复制-粘贴错误与for循环( for-each 循环或传统循环)结合使用,则生成的代码甚至无法编译。第一个循环中的元素(或迭代器)变量不在第二个循环的作用域内。下面是它如何与传统的for循环:
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
Element e = i.next();
... // Do something with e and i
}
...
// Compile-time error - cannot find symbol i
for (Iterator<Element> i2 = c2.iterator(); i.hasNext(); ) {
Element e2 = i2.next();
... // Do something with e2 and i2
}
此外,如果使用for循环,则更不可能出现复制-粘贴错误,因为在两个循环中没有使用不同变量名的动机。循环是完全独立的,所以重用元素(或迭代器)变量名没有什么坏处。事实上,这样做通常很时尚。
与while循环相比,for循环还有一个优点: 它更短,这增强了可读性。
下面是另一个循环习惯用法,它最小化了局部变量的作用域:
for (int i = 0, n = expensiveComputation(); i < n; i++) {
... // Do something with i;
}
关于这个习惯用法,需要注意的重要一点是它有两个循环变量,i 和 n,它们的作用域完全正确。第二个变量 n 用于存储第一个变量的极限,从而避免了每次迭代的冗余计算。作为一个规则,如果循环测试涉及一个保证在每次迭代中返回相同结果的方法调用,那么应该使用这个习惯用法。最小化局部变量范围的最后一种技术是保持方法小而集中。如果您在同一方法中组合两个活动,与一个活动相关的局部变量可能在执行另一个活动的代码范围内。要防止这种情况发生,只需将方法分为两个: 每个活动一个。
网友评论