编写可读代码的艺术 (9)

第九章 变量与可读性

前面说了“解释变量”和“总结变量”的好处,他们可以把巨大的表达式拆开,并作为某种形式的文档。但是并不是所有的变量都能达到这个效果。我们这样做并不是为了多多引入变量,而是为了提高可读性。相反,有很多变量并不能显著地提高代码的可读性。对于这样的变量,我们需要舍弃他们。

变量越多,就越难全部跟踪它们的动向。

变量的作用域越大,就需要跟踪它的动向越久。

变量改变得越频繁,就越难以跟踪它的当前值。

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循环中,是读者一目了然。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值