Part Two
Simplifying loops and logic
第二部分主要围绕控制流,逻辑表达式来探讨,旨在降低读代码时候的“心理负担” ——复杂的循环,冗长的表达和一大堆的变量。
Making Control Flow Easy to Read
Key Idea: 使代码中的条件表达式,循环等尽可能自然,保证不让读者停下来重新读。
a. 条件表达式中参数的顺序,规则如下:左边部分更易变化,右边部分相对稳定。e.g if(length >= 10), while(bytes_received < bytes_expected)。
这里,引发一个探讨,c/c++中,常使用 if(rvalue == lvalue) (YODA NOTATION)来在编译阶段避免 “==” 写成 “=” 的错误。但这让代码不易读。庆幸的是,现代编译器已经能够警告 if(lvalue = rvalue) 这类的错误。。(mark,学到一点了)。
b. if/else 块的顺序。规则如下:
- 优先处理 正 的逻辑
- 优先处理 简单 的逻辑
- 优先处理 感兴趣 的逻辑
- 当 负类更简单且更让人感兴趣/危险时,才优先处理 负 类
c. ?: 三元操作符的使用。 仅在两个分支都简单的情况下使用。
key idea: 要最小化代码的理解时间而不是压缩代码的行数。
d. 避免使用do-while循环,因为这和正常的阅读顺序不符。而且 do-while也是错误和困惑之源。
e. 函数提早返回。按实际情况在函数中有多个return子句,这样会使表达更加完善。在纯C中,提早返回会使析构代码执行不完善,所以需要重构或者明智的调用goto析构。
f. goto不是声名狼藉。 goto语句在linux kernel中发挥着重要的作用,同时,在c工程中,也经常要调用,比如e中的情况:
if (p == NULL) goto exit;
.....
exit:
fclose(file1);
fclose(file2);
…
return;
当然,goto最好慎用,否则可能会导致代码像意大利面。。
g. 减少嵌套的次数。 我们大脑中stack,当pop时,不容易恢复之前状态。
key idea: 当你改变时,从一个全新的视角看代码,退一步,把它当成一个整体。
怎么减少嵌套呢? 若不在循环中,可以提早返回,若在循环中,则使用 continue跳过。例子如下
// 源代码,高亮部分为嵌套内容,增加阅读阻力
if(user_result == SUCCESS){
if(permission_result != SUCCESS){
reply.WriteErrors("error reading permissions");
reply.Done();
return;
}
reply.WriteErrors("");
} else{
reply.WriteErrors(user_result);
}
reply.Done();
// 以下为改进代码,使用提前返回减少嵌套
if(user_result != SUCCESS){
reply.WriteErrors("")
reply.Done();
return;
}
if(permission_result != SUCCESS){
reply.WriteErrors("error reading permissions");
reply.Done();
return;
}
reply.WriteErrors("");
reply.Done();
// 如何在循环中移除嵌套
for (int i=0; i<result.size(); i++){
if(result[i] != NULL){
non_null_count++;
if(result[i]->name != ""){
cout << "Considering candidate..." << endl;
……
}
}
}
// 以下采用 if(...) continue 移除嵌套
for(int i=0; i<result.size(); i++){
if(result[i] == NULL) continue;
non_null_count++;
if(result[i]->name == "") continue;
cout << "Considering candidate..." << endl;
.......
}
Breaking Down Giant Expressions
key idea: 将巨大表达分解成小块
a. 解释性变量名。 将复杂的表达式采用通俗、额外的变量名来表示。
b.使用总结性变量名。当变量名本身就是解释性时,但是还是显得大块,可以考虑将其实质概念抽取总结作为新的变量名。
// 原始
if (resuest.user.id == document.ower_id){....}...
// 总结性改进
bool user_owns_documnts = (resuest.user.id == document.ower_id);
if(user_owns_documents).....
c. 进行德摩根变换。这样能够将复杂的表达简单化。学过《数字电路》的话,可以采用卡诺图来减小逻辑规模
- ! (a | b) == (!a & !b)
- ! (a & b) == (!a | !b)
d.短路原则不要滥用。另外,有的时候,正向求解极其复杂,逆向求解却很容易。比如:1000台电器中至少有一个是坏的概率。若是正向求解,过程将采用穷举法,那可以说没有一个人能够正确解答,逆向的做法是,1-p(1000台都是良品),这如同当初高中使用反证法证明时是多么的酣畅淋漓。(也许人类就是喜欢挑刺)
e.将大段的表达分解。提取共同部分,用新的变量表达。
f.适当采用宏定义,归纳模式。
Variables and readability
该部分从三个部分阐释草率使用变量会导致程序难以理解。
- 变量越多越难保持跟踪
- 变量跨度越大越难保持跟踪
- 变量越常变化就越难得知其当前值。
a.排除变量 : 不定义仅仅使用一次的变量名。排除保持中间结果值变量,排除控制流变量。 核心思想就是:尽快完成任务,不拖泥带水。
b. 收缩变量作用域。 核心思想: 让变量就在你的眼皮底下吧。
1. 不让仅调用一次的变量成为你思想的地雷。在c++中,常常推荐for循环中的i为其局部变量 for(int i; i<100; i++),同理,还有if,try等相关结构。
PaymentInfo* info = databade.ReadPaymentInfo(); if(info){ cout << "user paid: " << info->amount() << endl; } // Many more code don't use info below .... // 这里,由于info的定义在if的作用域外,会让人大脑中一直想着后序的代码是否会再次出现info,这就是上面所说的 “地雷” // 改为 if(PaymentInfo* info = database.ReadPaymentInfo()) ..... // 这样,info的作用域和if相同,当结束if后,就不会有思想负担了。
2. 在使用使才定义变量。纯c中,变量的定义要放在函数的顶部,这样很不利于阅读且也不符合正常的编写逻辑。
c. 更偏向使用“常量” 。 变量的多变会让代码难以跟踪。因此,更倾向使用永久固定的常量,比如 (static) const 修饰。另外,在python中,内建的string就是不变量[immutable]。当然,变化次数少的变量也是值得推荐的。
d.总结下,用一个example.
数据格式:
<input type="text" id="index1" value="Dustin"> <input type="text" id="index2" value="Trevor"> <input type="text" id="index3" value=""> <input type="text" id="index4" value="Melissa"> ..... // 定义函数,将第一个空值幅新值 var setFirstEmptyInput = function (new_value){ var found = false; // found 是控制流中间变量,可以通过提早返回来排除 var i = 1; // i及elem的初始化和while循环可以通过将while-> for 循环来改进。 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; };
改进后如下:
var setFirstEmptyInput = function(new_value){ for(var i=1; true; i++){ var elem = documentgetElemntById('input' + i); if (elem === null) return null; if (elem.value === ''){ elem.value = new_value; return elem; } } };