第九章 变量与可读性
前面说了“解释变量”和“总结变量”的好处,他们可以把巨大的表达式拆开,并作为某种形式的文档。但是并不是所有的变量都能达到这个效果。我们这样做并不是为了多多引入变量,而是为了提高可读性。相反,有很多变量并不能显著地提高代码的可读性。对于这样的变量,我们需要舍弃他们。
变量越多,就越难全部跟踪它们的动向。
变量的作用域越大,就需要跟踪它的动向越久。
变量改变得越频繁,就越难以跟踪它的当前值。
1、减少变量
首先看下个例子,了解下哪些算是没有价值的临时变量。
now = datetime.datetime.now()
root_message.last_view_time = now
只看这段代码中,now是不是值得保留的变量呢。并不是,原因如下:
a、它没有拆分任何复杂的表达式;
b、它没有做任何有意义的解释;
c、它只用了一次,没有压缩任何冗余代码。
没有这个now,这段代码同样很好理解。
root_message.last_view_time = datetime.datetime.now()
像now这样的临时变量通常是代码编译过后的剩余物,可能在定义的时候考虑到会多次使用它,但是实际上并没有这样用,所以也就不具备保留的必要了。
2、减少中间结果
下面是一段jave代码,从数组中删除一个值:
var remove_one = function (array, value_to_remove)
{
var index_to_remove = null;
for(var i = 0; i < array.length; i += 1)
{
if(array[i] == value_to_remove)
{
index_to_remove = i;
break;
}
}
if(index_to_remove != null)
{
array.splice(index_to_remove, 1);
}
};
变量index_to_remove只是用来保存临时结果,如果后续的代码不需要使用这个index的话,可以在得到该值时立即处理它而消除。
var remove_one = function (array, value_to_remove)
{
for(var i = 0; i < array.length; i += 1)
{
if(array[i] == value_to_remove)
{
array.splice(index_to_remove, 1);
return;
}
}
};
通过让代码提前返回,不再需要临时变量,这样大幅简化了代码。
同样我们也应该减少类似的控制流变量,比如:
while(/*condiition*/ && !done)
{
...
if(...)
{
done = true;
continue;
}
}
这只是一个例子,可能这段代码的后面还有更多的这种把done设置为true的代码。像done这样的变量,可以成为“控制流变量”,唯一的目的就是控制程序的执行,它并不包含任何程序数据。通常,可以使用更好的结构化编程消除这种变量:
while(/*condiition*/ && !done)
{
...
if(...)
{
break;
}
}
如果涉及到多层循环,一个break不够用时,最好可以通过提取新函数的方式来解决它。
3、缩小变量的作用域
我们都听过一条建议,“避免使用全局变量”。这是个好建议,因为很难追踪这些全局变量以及如何使用了他们。其实不光是全局变量,针对所有变量,我们都应该尽量缩小它的作用域。让变量在尽量少的代码行可见。
为什么要减小变量的作用域呢?因为这样可以减少读者同时考虑的变量的个数,更专注于理解代码。
看下面这段代码:
class LargeClass
{
string str_;
void Method1()
{
str_ = ...;
Method2();
}
void Method2()
{
//use str_
}
...
}
这个类中的str_变量,所有的使用都在类的内部,目前这样的定义属于类内的全局变量。如果这个类很大,str_只有少数函数有关,最好将这个小型全局变量做降级处理。
class LargeClass
{
void Method1()
{
string str_ = ...;
Method2(str_);
}
void Method2(string str)
{
//use str_
}
... //other methods can't see str
}
对类的方法,进行作用域的限制,可以使用静态。让方法只对类的内部可见。
另外,如果类中的方法相对独立的话,最好将这些大类拆分成独立的小类使用。
4、只写一次的变量更好
“永久固定”的变量更容易思考,就像define宏定义一样,这样的变量往往不需要读者做过多的思考。操作一个变量的地方越多,越难确定它的当前值。
示例
假设有一个网页,上面有几个文本输入字段,布置如下:
<input type="text" id="input1" value="Dustin">
<input type="text" id="input2" value="Trevpr">
<input type="text" id="input3" value="">
<input type="text" id="input4" value="Melissa">
id从input1开始增加。我们需要完成一个函数,名字就叫setFirstEmptyInput(),它接受一个字符串并把该字符串放在页面上第一个value为空的字段上。然后返回已更新的该行元素,如果没有空字段则返回null。
var setFirstEmptyInput = funtion (new_value)
{
var found = false;
var i = 1;
var elem = document.getElementById('input' + i);
while(elem != null)
{
if(elem.value == '')
{
found = true;
break;
}
i++;
elem = document.getElementById('input' + i);
}
if(found)
elem.value = new_value;
return elem;
};
这是最初版本的代码,第一眼看这段代码,是不是觉得有点复杂。有很多变量需要我们关注,我们尝试一下减少变量,比如控制变量found。
var setFirstEmptyInput = funtion (new_value)
{
var i = 1;
var elem = document.getElementById('input' + i);
while(elem != null)
{
if(elem.value == '')
{
elem.value = new_value;
return;
}
i++;
elem = document.getElementById('input' + i);
}
return null;
};
接着看下elem变量,这个变量,在循环中多次用到,仔细阅读代码的话,会发现,迭代的值就是elem,真正累加的值是i。我们对i的循环采用for语句。
var setFirstEmptyInput = funtion (new_value)
{
for(var i = 1; true; i++)
{
var elem = document.getElementById('input' + i);
if(elem == null)
return null;
if(elem.value == '')
{
elem.value = new_value;
return elem;
}
}
};
这段代码中,elem只作为一个for循环内的局部变量使用,i的初始值和变化值都在for循环中,是读者一目了然。