您的位置:首页 > 编程语言

编写可读代码的艺术(第二部分 简化循环和逻辑)

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。

按照什么原则呢?

比较符号的左侧比较符号的右侧
“被询问的”表达式,它的值更倾向于不断变化。用来作比较的表达式,它的值更倾向于表达式
按照这个原则,就可以解释1 3为何优于2 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).

操纵一个变量的地方越多,越难确定它的当前值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: