程序代码,首先是给人看的,其次才是给机器运行的。
为了保证代码的可读性,我们需要很多好习惯。
在上期文章里,我分享了见名知意,和层次清晰,这两个好习惯,今天我来分享第3个和第4个好习惯——避免歧义,和减少重复。
3. 避免歧义
所谓歧义,是指大家对同一个事物,出现了不同解读方式的情况。
比如说,在我国古代,人们写字是不加标点符号的,于是,
"无鸡鸭也可",这句话就有2种解读方式:
① 无鸡鸭,也可;
② 无鸡,鸭也可;
在程序代码中,可能也会有类似的情况。
例如,下方这段代码:
var arr = [ 0, 0 ];
var i = 0;
arr[i] = ++i; // 有歧义
首先定义了一个叫 arr 的数组,长度为 2,它的两个元素都等于 0;然后定义一个叫 i 的变量,初始值为 0;这之前都没有歧义,下面就开始有歧义了;
"arr[i] = ++i;" 这一句,可以有两种理解方式:
① arr[i] = i + 1; ++i; 相当于 arr[0] = 1;
② ++i; arr[i] = i; 相当于 arr[1] = 1;
第三行代码,在不同的编程语言里,可能会有不同的运行结果;甚至是同一种编程语言,在不同环境下运行这种代码,有可能也出现不同的结果。
这种情况被称作 "未定义行为" (Undefined Behavior, UB),大家应该避免它。
还有时候,虽然代码运行的结果唯一,但是我们阅读的时候,可能会理解错误。
例如,不同级别的运算符同时出现,我们最好加上小括号:
int len = (right - left) >> 1;
这样看,显然是先计算 right - left,然后进行位移运算,不会有歧义;
而如果写成:
int len = right - left >> 1;
虽然代数运算的优先级高于位运算,但是,有人看到它,可能会误解成,先计算 left 右移 1 位的值,然后用 right 减去这个值,从而产生歧义。
再比如,判断条件同时出现 and 和 or 运算的时候,最好也套括号:
if (x > 1 || (y != null && z < 0)) {
// ...
}
或者是,把复杂的条件拆分:
if (x > 1) {
// ...
}
if (y != null && z < 0) {
// ...
}
4. 减少重复
很多编程初学者,容易出现一个问题,经常有多处相同或相似的代码。这个习惯不好,因为,代码的冗余,会降低可读性,并且也不利于后期的维护。
正确的做法是:
把相同意义的值定义为变量,把相似的逻辑定义为函数,把相似的结构封装为类或结构体。
例如,当我们需要用到圆周率时,不要直接写成 3.14 之类的字面值,而是要定义成变量,即使它在运行过程中,数值一直不变。
如果你用的编程语言支持常变量,那你最好把这种数值定义为常变量,例如 C++:
const double Pi = 3.14;
当我们需要修改圆周率的值时,只修改这一个地方即可,而不需要到处改。
那么,查找替换行不行呢?答案是: 不行 !
如果有另外的变量碰巧也等于 3.14,但它和圆周率没关系,那我们替换的时候,就把不该修改的地方也改了。
对于代码中多次出现的相似的逻辑,我们应该写成函数的形式;
例如,对数组元素求和,我们对比一下:
var arr_1 = [ 1, 0, 5, 7 ];
var arr_2 = [ 8.5, -2, 0.3 ];
var arr_3 = [ 1024, -32, 25 ];
// 好的写法:
function GetSum(arr) {
var sum = 0;
for (var i = arr.length; --i >= 0;) {
sum += arr[i];
}
return sum;
}
var res = [ 0, 0, 0 ];
res[0] = GetSum(arr_1);
res[1] = GetSum(arr_2);
res[2] = GetSum(arr_3);
// 差的写法:
res[0] = 0;
for (var i = arr_1.length; --i >= 0;) {
res[0] += arr_1[i];
}
res[1] = 0;
for (var i = arr_2.length; --i >= 0;) {
res[1] += arr_2[i];
}
res[2] = 0;
for (var i - arr_3.length; --i >=0;) {
res[2] += arr_3[i];
}
显然,第一种写法,代码结构清晰,可读性更高;
另外,如果后期需要修改求和的逻辑,我们只需要改动这个函数,而不需要到处改。
至于类和结构体,且听我下回分解。
下期预告:
5. 勤于注释; 6. 善于封装;