您的位置:首页 > 编程语言

代码质量-变量的作用域最小化原则与就近原则

2010-04-29 14:03 495 查看

代码质量-变量的作用域最小化原则与就近原则

一 变量的作用域

作用域或者可见性(visibility)指的是变量在程序内的可见和引用的范围。作用域可以看作是一种衡量变量的知名度的方法:它的名气有多大?一个作用域很小的变量只能在很小的范围内可见-比如说,循环下标变量只能用于一个循环的小范围内,一个作用域大的变量则在程序的很多地方都是可见的-比如说全局变量。

变量可以只对某一个代码块可见、也可以对子程序、类或者整个程序可见。

二 作用域最小化

那些介于同一个变量多个引用点之间的代码可称为“攻击窗口”。这个攻击窗口越分散,引起错误的可能性就越高,对它的可控制性就越差,如可能会有新代码加到这种窗口中,不当地修改了这个变量,或者阅读代码的人可能会忘记该变量应有的值。

一般而言,应使变量的作用域最小化,把变量引用点尽可能集中在一起,从而能够对变量施加控制。将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。

以下是一些可以用来减小作用域的特别有用的原则。

1、在循环开始之前再去初始化该循环里使用的变量,而不是在该循环所属的子程序的开始处初始化这些变量。

2、直到变量即将被使用时再为其赋值。

3、把相关语句放在一起。

4、把相关语句组提取成单独的子程序。

三 变量的就近原则

就近原则(Principle of Proximity)-把相关的操作放在一起,例如让注释靠近它所描述的代码,让控制循环的代码靠近循环本身等。变量的就近原则指尽可能在靠近第一次使用变量的位置声明和定义该变量。

就近原则实际上也是变量的作用域最小化的一种实现手段。过早地声明局部变量不仅会使它的作用域过早地扩展,而且结束得也过于晚了。局部变量的作用域从它被声明的点开始扩展,一直到外围块的结束处。如果变量在“使用它的块”之外被声明的,当程序退出该块之后,该变量仍是可见的。如果变量在它的目标使用区域之前或之后被意外地使用的话,后果将可能是灾难性的。

有人喜欢在方法或者子程序的开始部分统一声明和定义所有的变量,如下代码示例。这种风格会产生一些问题:

1、当使用done变量的代码开始执行的时候,done很可能已经倍修改了。即便你在第一次写这个程序的时候不会这样,后续的修改也可能会导致出现这样的错误。

2、一旦把所有的初始化代码都放在一起,可能会让人产生误解,认为所有这些变量都会在子程序中一直使用,而事实上done知识在后面才被用到。

3、可读性不好。当读到引用done变量的代码时,维护人员可能都忘记了done在哪里定义的,又需要在子程序里面来回搜寻变量的定义,迫使阅读者的目光在程序里跳来跳去。

public void function()
{
int accountIndex = 0;
double total = 0;
boolean done = false;

// other code

// code using accountIndex
...
// code using total
...
// code using done
while ( !done )
...
}

符合就近原则也就符合作用域最小化,好的代码示例如下:

public void function()
{
int accountIndex = 0;
// code using accountIndex
...
double total = 0;
// coding using total
...
boolean done = false;
// code using done
while ( !done )
...
}

四 循环内局部变量代码示例

有下面2种JAVA代码示例:

//code segment 1
public static void function()
{
for ( int i = 0; i < 10; i++ )
{
int value = i;

System.out.print( value );
}
}
//code segment 2
public static void function()
{
int value = 0;

for ( int i = 0; i < 10; i++ )
{
value = i;

System.out.print( value );
}
}

根据变量的作用域最小化原则和就近原则,segment 1有较好的代码质量。
有人仔细思考后,会提出segment 1代码的每次循环都要重新定义value,是不是意味着每次循环都要重新在栈中分配一个局部变量?是不是性能没有segment 2好?
我们都知道在变量作用域的范围之外是不能引用这个变量的,书本上面一般都说这个变量的生存期结束了。实际上局部变量的作用域范围和生存期是语法层次上的概念。例如,在JAVA中,局部变量的大小在编译时计算出来,并放置到class文件中,然后虚拟机就能够了解到方法的栈帧需要多少内存。JAVA栈帧的局部变量区被组织为一个以字长为单位、从0开始计数的变量表数组。当虚拟机调用一个JAVA方法时,它从对应类的类型信息中得到此方法的局部变量表的大小,并据此分配栈帧内存,然后压入JAVA栈中。也就是说在方法调用之前就分配好所有的局部变量,在方法结束时随退栈操作一起释放。
我们可以对比一下segment 1和segment 2代码的字节码:

// code segment 1 stack for local variable
-------------------------
index0       i
-------------------------
index1       value
-------------------------
0:   iconst_0
1:   istore_0        // i = 0
2:   goto    17
5:   iload_0
6:   istore_1        // value = i
7:   getstatic       #15; //Field java/lang/System.out:Ljava/io/PrintStream;
10:  iload_1
11:  invokevirtual   #21; //Method java/io/PrintStream.print:(I)V
14:  iinc    0, 1
17:  iload_0
18:  bipush  10
20:  if_icmplt       5
23:  return
// code segment 2 stack for local variable
-------------------------
index0       value
-------------------------
index1       i
-------------------------
0:   iconst_0
1:   istore_0        // value = 0
2:   iconst_0
3:   istore_1        //  i = 0
4:   goto    19
7:   iload_1
8:   istore_0        // value = i
9:   getstatic       #15; //Field java/lang/System.out:Ljava/io/PrintStream;
12:  iload_0
13:  invokevirtual   #21; //Method java/io/PrintStream.print:(I)V
16:  iinc    1, 1
19:  iload_1
20:  bipush  10
22:  if_icmplt       7
25:  return

两个字节码唯一的区别在于局部变量value是否在循环之前被初始化了,局部变量表的大小由编译时决定的,都是value和i,仅仅是在变量表里的顺序不同。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: