编写可读代码的艺术(第二部分 简化循环和逻辑)
2018-03-31 00:12
1006 查看
把控制流变得易读
条件语句中参数的顺序
关键思想:把条件、循环以及其它对控制流的改变做得越自然越好,运用一种方式使读者不用停下来读你的代码。对比
if(length<10){// 1 } if(10>length){// 2 } while(bytes_received < bytes_expected){// 3 } while(bytes_expected > bytes_received){// 4 }
1优于2,3优于4。
按照什么原则呢?
比较符号的左侧 | 比较符号的右侧 |
---|---|
“被询问的”表达式,它的值更倾向于不断变化。 | 用来作比较的表达式,它的值更倾向于表达式 |
“尤达表示法”已经过时了,现在编译器都会提醒。(所以找一款好的编辑器吧,事半功倍)。
if/else语句块的性质
你通常可以互换if/else各语句块的位置// case1和case2可以互换 if (){ //case 1 }else{ //case 2 }
但在某些情况下,其中一种顺序比另一种更好。
首先处理正逻辑而不是负逻辑的情况,例如if(debug)而不是if(!debug)。
先处理掉简单的情况。这种方式可能还会使得if和else在屏幕之内都可见,这很好。
先处理有趣的或者是可疑的情况。
当然有时候这三种倾向性会有冲突,这就需要自己判断了。
?:条件表达式(三目运算符)
建议:默认情况下都用if/else,三目运算符?:只有在最简单的情况下使用,不然可能会让代码难以理解。避免do/while循环
如题从函数中提前返回
臭名昭著的goto
最小化嵌套
// 问题1:不断切换SUCCESS和non_SUCCESS的条件,容易使人混乱。 // 问题2:逻辑可能正确,但条件混在一起却很容易使人混乱。 if (user_ result== SUCCESS) { if (permission_result != SUCCESS) { reply.NriteErrors("error reading permissions"); reply.Done() ; return; } reply.WriteErrors(""); }else { reply.WriteErrors (user_result); } reply.Done();
嵌套是如何积累而成的
这段照抄,感觉分析的很到位在我们修正前面的事例代码之前,先来看看什么导致它现在的这个样子的。一开始代码是很简单的:
if(user_result == SUC cd3b CESS){ reply.WriteErrors(""); }else{ reply.WriteErrors(user_result); } reply.Done();
但是后来的程序员增加了第二个操作:
if (user_ result== SUCCESS) { if (permission_result != SUCCESS) { reply.NriteErrors("error reading permissions"); reply.Done() ; return; } reply.WriteErrors(""); ...
这个改动有合理的地方——该程序员要插入一段新代码,并且她找到了最容易插入的地方。新代码很整洁,而且很明确。这个改动的差异也很清晰——这看上去对于她来讲,像是个简单的改动。
但是以后当其他人遇到这段代码时,所有的上下文早已不在了。这就是你在本节一开始读到这段代码时的情况,你不得不一下子全盘接受它。
关键思想:当你对代码做改动时,从全新的角度审视它,把它作为一个整体来看待。
通过提早返回来减少嵌套
下面改进,像这种嵌套可以通过马上处理“失败情况”并从函数早返回来减少:if (user_result != SUCCESS) { reply.WriteErrors (user_result) ; reply.Done(); return ; } if (permission_result != SUCCESS) { reply.WriteErrors(permissio_result) ; reply.Done( ); return ; } reply.WriteErrors("") ; reply.Done();
上面这段代码只有一层嵌套,而不是两层。但更重要的是,读者不再需要从思维堆栈里“出栈”了——每个if块都以一个return结束。
减少循环内的嵌套
提早返回这个技术并不总是合适的。例如,下面代码在循环中有嵌套:for (int i= 0; i < results.size(); i++) { if (results[i] != NULL) { non_null_count++; if (results[i]->name !="") { cout <<"Considering candidate..." << endl; ... } } }
在循环中,与提早返回类似的技术是continue:
for (int i= 0; i < results.size(); i+) { if (results[i]== NULL) continue; non_null_count++; if (results[i]->name == "") continue; cout <<"Considering candidate..." << endl; ... }
与if(…)return;在函数中所扮演的保护语句一样,这些if(…) continue;语句是循环
中的保护语句。
一般来讲,continue语句让人很困惑,因为它让读者不能连续地阅读,就像循环中有goto语句一样。但是在这种情况中,循环中的每个迭代是相互独立的(这是一种“foreach” 循环),因此读者可以很容易地领悟到这里continue的意思是“跳过该项”。
你能理解执行的流程吗
拆分超长的表达式
用做解释的变量
我们大多数人同时只能考虑3~4件“事情”。代码中的表达式越长,他就越难理解。用作解释的变量
插入一个解释变量// 1==>2 if line.split(' :')[o].strip()=="root":// 1 username= line.split(' :' )[0].strip()// 2 if username == "root" :
总结变量
if (request.user.id == document.owner_id) { // user can edit this document... } ... if (request.user.id != document.owner_id) { // document is read-only... }
这里的表达式request.user.id== document.owner.id看上去可能并不长,但它包含5个变量,所以需要多花点时间来想一想如何处理它。
这段代码中的主要概念是:“该用户拥有此文档吗?”这个概念可以通过增加一个总结
变量来表达得更清楚。
不得不说下面的写法确实更容易理解。
boolean user_owns_document = (request.user.id == document.owner_id); if (user_owns_document) { // user can edit this document... } ... if (!user_owns_document) { // document is read-only... }
使用德摩根定理
口诀:分别取反,转换与/或利用口诀简化下面代码:
if (! (file_exists && !is_protected)) Error("Sorry, could not read file."); //转化为: if (! file_exists| |is_protected) Error("Sorry, could not read file." );
滥用短路逻辑
关键思想:要小心“智能”的小代码段,它们往往在以后会让别人读起来感到困惑。简而言之,你不要为了简化代码,而给别人设置理解上的坑。例子: 与复杂的逻辑战斗
拆分巨大的语句
把重复使用的长字符串提取出来命名放在函数内的起始位置,这样的好处是:多处使用时,避免重复打入大量信息,从而减少录入错误的情况
如需改动,改一处即可
缩短了代码的长度,使得代码更容易阅读
另一个简化表达式的创意方法
介绍了C++的宏,和上一点有类似功能。变量和可读性
三个问题:变量越多,就越难全部跟踪它们的动向。
变量的作用域越大,就需要跟踪它的动向越久。
变量改变得越频繁,就越难以跟踪它的当前值。
减少变量
上一章讲了如何引入“解释”和“总结”变量来使代码更可读。这些变量很有帮助是因为它们把巨大的表达式分开。并且可以作为某种形式的文档。在本节中,我们感兴趣的是减少不能改进可读性的变量。当移除这种变量后,新代码会更精练而且同样容易理解。
没有价值的临时变量
now = datetime.datetime.now() root._message.last view_ time = now
now是一个值得保留的变量吗? 不是,下面是原因:
它没有拆分任何复杂的表达式。
它没有做更多的澄清一一表达式datetime.datetime.now()已经很清楚了。
它只用过一次,因此它并没有压缩任何冗余代码。
没有了now,代码一样容易理解。
减少中间结果
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) { 1ndex.to_ remove = i; break; } } if (index_to_remove !== null) { array.splice(index_to_remove,1) ; } };
其实可以直接写成下面这样,不需要中间变量index_to_remove:
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(i,1); return ; } } };
减少控制流变量
boolean done =false; while (/* condition */ && !done) { ... if (...) { done = true; continue; } }
通过结构化编程去除控制变量:
while (/* condition */) { ... if (...) { break; } }
缩小变量的作用域
我们都听过“避免全局变量”这条建议。这是一条好的建议,因为:很难跟踪这些全局变量在哪里以及如何使用它们。
并且通过“命名空间污染’(名字太多容易与局部变量冲突)代码可能会意外地改变全局变量的值。
关键思想:让你的变量对尽量少的代码可见,即缩小作用域。
C++中if语句的作用域
PaymentInfo* info= database.ReadPaymentInfo() ; if (1nfo) { cout<<"User paid:"<< info->amount() << endl; } //Many more lines of code below
如果info只在if条件句中使用:
if (PaymentInfo* info= database.ReadPaymentInfo() { cout << "User paid:"<< info->amount() <<end1;
只写一次变量就好
不断变化的变量让人难以理解。跟踪这种变量的值更有难度。“永久固定”的变量更容易思考。当前,像这种常量:
static const int NUM_THREADS = 10;
不需要读者思考很多。基于同样的原因,鼓励在C++中使用const (在Java中使
用final).
操纵一个变量的地方越多,越难确定它的当前值。
相关文章推荐
- 【编写可读代码的艺术】第二部分 简化循环和逻辑
- 《编写可读性代码的艺术》读书笔记 第二部分 简化循环和逻辑
- 可读代码的艺术(一):简化循环与逻辑
- 【编写可读代码的艺术】第四部分 精选话题
- 编写可读代码的艺术(第三部分 重新组织代码)
- 编写可读代码的艺术读书笔记--简化和重新组织代码
- 【编写可读代码的艺术】第三部分 重新组织你的代码
- 编写可读代码的艺术(三)不要起误解的名字以及代码上的‘审美’
- 编写可读代码的艺术
- 编写可读代码的艺术(四)注释的“艺术性”
- 《编写可读代码的艺术》读书笔记(下)简化循环和逻辑和重新组织代码
- 读书笔记-编写可读代码的艺术[上]
- 读书笔记-编写可读代码的艺术[上]
- HBase 协处理器编程详解,第二部分:客户端代码编写
- 编写可读代码的艺术(第一部分)
- 编写可读代码的艺术
- 读书笔记-编写可读代码的艺术[中]
- 编写可读代码的艺术 读后感
- 《编写可读性代码的艺术》读书笔记 第三部分 重新组织代码
- 《编写可读性代码的艺术》读书笔记 第四部分 精选话题