第57项:将局部变量的作用域最小化

  本项与第15项(使类和成员的可访问性最小化)本质上是类似的。将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。

  较早的程序设计语言(如C语言)要求局部变量必须在一个代码块的开头处进行声明,出于习惯,有些程序猿们目前还是继续这样做。这是个值得打破的习惯。在此提醒Java允许你在任何可以出现语句的地方声明变量(与C一样,自C99起)。

  要使局部变量的作用于最小化,最有力的方法是在第一次使用它的地方声明 。如果变量在使用之前进行声明,这只会造成混乱————对于试图理解程序功能的读者来说,这又多了一种只会分散他们注意力的因素。等到用到该变量的时候,读者可能已经记不起该变量的类型或者初始值了。

  过早地声明局部变量不仅会使它的作用域开始得太早,而且结束得也过于晚了。局部变量的作用域从它被声明的点开始,一直到代码块(block)的结束处。如果变量是在“使用它的块”之外被声明的,当程序退出该块之后,该变量仍然是可见的。如果变量在它的目标使用区域之前或者之后被意外地使用的话,后果将可能是灾难性的。

  几乎每个局部变量的声明都应该包含一个初始化表达式 。如果你还没有足够的信息来对一个变量进行有意义的初始化,就应该推迟这个声明,知道可以初始化为止。这条规则有一个例外的情况与try-cache语句有关。如果一个变量被一个表达式初始化,这个表达式可能会抛出一个受检异常(checked exception),该变量就必须在try块的内部初始化(除非封闭方法可以传播异常【意思应该是方法包含throws exception】)。如果变量的值必须在try块外部被使用到,它就必须在try块之前被声明,但是在try块之前,它还不能被“有意义地初始化”。请参照【原书】第283页的例子。

  循环中提供了特殊的机会来将变量的作用域最小化。无论是传统的还是for-each形式的for循环,都允许声明循环变量(loop variable),它们的作用域被限定在正好需要的范围之内。(该区域由循环体和for关键字与正文之间括号中的代码组成。)因此,如果在循环终止之后不再需要循环变量的内容,for循环就优先于while循环

  例如,下面是一种遍历集合的首选做法(第46项):

// Preferred idiom for iterating over a collection or array
for (Element e : c) {
    ... // Do Something with e
}

  如果你需要访问迭代器(iterator),也许是为了调用它的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,遗憾的是,这时i仍然还在有效范围内。结果代码仍然可以通过编译,运行的时候也不会抛出异常,但是它所做的事情却是错误的。第二个循环并没有在c2上迭代,而是立即终止,造成c2为空的假象。因为这个程序的错误是悄然发生的,所以可能在很长时间内都不会被发现。

  如果类似的“剪切-粘贴”错误发生在任何一种循环中(无论是传统循环的还是for-each循环),结果是,代码根本不能通过编译。在第二个循环开始之前,第一个循环的元素(或者迭代器)变量已经不在它的作用域范围之内了:

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循环,犯这种“剪切-粘贴”错误的可能性就会大大降低,因为通常没有必要在两个循环中使用不同的变量名。循环是完全独立的,所以重用元素(或者迭代器)变量的名称不会有任何危害。实际上,这也是很流行的做法。

  使用for循环与使用while循环相比较还有另外一个优势:更简短,从而增强了可读性。

  下面是另外一种对局部变量的作用域进行最小化的循环做法:

for (int i = 0, n = expensiveComputation(); i < n; i++) {
    ... // Do something with i;
}

  关于这种做法要关注的事项是,它有两个循环变量i和n,它们都具有完全正确的作用域。第二个变量n用于存储第一个变量的极限,从而避免了每次迭代中冗余计算的成本。通常,如果循环测试中涉及方法的调用,并且保证在每次迭代时返回相同的结果,则应使用这种习惯做法。

  最后一种将局部变量的作用域最小化的方法时使方法【体尽可能】小而集中 ,如果把两个操作(activity)合并到同一个方法中,与其中一个操作相关的局部变量就有可能会出现在执行另一个操作的代码范围之内。为了防止这种情况发生,只要把这个方法分成两个,每个方法各执行一个操作。

你可能感兴趣的:(Effective,Java,第三版翻译)