编写可读代码的艺术:次·循环逻辑优化


前言:上篇记录了本书第一部分,即代码表面层次的改进,包括一些改进代码可读性的简单方法,一次一行,在没有很大风险或者花很大代价的情况下就可以应用,具体见这里。本文在第一部分的基础上记录分享第二部分。第二部分将深入讨论程序的“循环和逻辑”:控制流、逻辑表达式以及让代码正常运行的那些变量。


如何编写可读代码

  1. 简化命名、注释和格式的方法,使每行代码都言简意赅。
  2. 梳理程序中的循环、逻辑和变量来减小复杂度并理清思路。
  3. 在函数级别解决问题,例如重新组织代码块,使其一次只做一件事。
  4. 编写有效的测试代码,使其全面简洁,同时可读性更高。

把控制流变得易读

关键思想:把条件、循环以及其他对控制流的改变做的越“自然”越好。运用一种方式使读者不用停下来重读你的代码。

条件语句中参数的顺序

决定是 a < b 好一些,还是 b > a好一些。

比较的左侧 比较的右侧
“被问询的”表达式,值更倾向于不断变化 用来做比较的表达式,值更倾向于常量

eg1: if(length >= 10)
eg2:while(bytes_received < bytes_expected)

if/else 语句块的顺序
  1. 首先处理正逻辑而不是负逻辑的情况。eg:if(debug)而不是 if(!debug)
  2. 先处理掉简单的情况。更可能使 if 和 else 在屏幕之内都可见
  3. 先处理有趣的或者是可疑的情况
三目运算符

建议默认情况下都用 if/else,三目运算符只有在最简单的情况下使用

从函数中提前返回
  1. 可以在函数中出现多条 return 语句,并且推荐在函数中提前返回。
  2. 想要单一出口点的一个动机是保证调用函数结尾的清理代码,但现代编程语言为这种保证提供了更精细的方式:
语言 清理代码的结构化术语
C++ 析构函数
Java、Python try finally
Python with
C# using
最小化嵌套

嵌套很深的代码难以理解,每个嵌套层次都是在读者的“思维栈”上又增加了一个文件。

  1. 嵌套如何累积形成?
    插入新代码-------- 当你对代码改动时,从全新的角度审视它,把它作为一个整体来看待。
  2. 通过提早返回来减少嵌套
    通过马上处理“失败情况”并从函数早返回来减少嵌套,例如:
    改动前:
if(user_result == SUCCESS){
    if(permisson_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(user_result);
    repley.Done();
    return;
}
if (permisson_result != SUCCESS){
     reply.WriteErrors("error reading permissions");
     reply.Done();
     return;
}
reply.WriteErrors("");
reply.Done();
  1. 减少循环内的嵌套
    与 if(...) return; 在函数中所扮演的保护语句一样; if(...) continue; 语句是循环中的保护语句。
    改动前:
for ( int i = 0; i < result.size(); i++) {
    if( result[i] != NULL) {
        non_null_count++;
        if(result[i] -> name != ""){
            cout << "Considering candidate..." << endl;
            .....
        }
    }
}

改动后:

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;
    ....
 }
你能理解执行的流程吗?

简化低层次控制流:把循环、条件和其他跳转写的简单一度
高层次流动也要清晰。
实践中,一些编程语言和库的结构让代码“幕后”运行或者流程难以理解,他们很有用,使代码更具有可读性,冗余更少,但是较难理解,难以追踪bug,要适度使用。

编程结构 高层次程序流程是如何变得不清晰的
线程 不清楚什么时间执行什么代码
信号量/中断处理程序 有些代码随时都有可能执行
异常 可能会从多个函数调用中向上冒泡一样地执行
函数指针和匿名函数 很难知道到底会执行什么代码,因为在编译时还没有决定
虚方法 object.virtualMethod()可能会调用一个未知子类的代码

拆分超长的表达式

代码中的表达式越长,他就越难以理解。把超长表达式拆分成更容易理解的小块。

  1. 用做解释的变量
    解释变量:引入一个额外的变量,让它来表示一个小一点的子表达式。
    目的:可以帮助解释子表达式的含义。

  2. 总结变量
    总结变量:可以看出表达式含义,不需要解释,把它装入一个新变量中仍然有用。
    目的:用一个短很多的名字来代替一大块代码,这个名字会更容易管理和思考。

  3. 使用德摩根定理
    对于一个布尔表达式的两种等价写法:“分别取反,转换与/或”
    (1)not ( a or b or c ) <=> ( not a ) and ( not b ) and ( not c )
    (2)not ( a and b and c ) <=> ( not a ) or ( not b ) or ( not c)
    改动前:

if (!(file_exists && !is_protected)) Error("Sorry, could not read file.");

改动后:

if( !file_exists || is_protected) Error("Sorry, could not read file.");
  1. 拆分巨大的语句
    DRY——Dont't Repeat Yourself.

变量与可读性

变量的草率运用会让程序更加难以理解,产生很多问题:
(1)变量越多,越难全部跟踪它们的动向。
(2)变量的作用域越大,就需要跟踪它的动向越久。
(3)变量改变得越频繁,就越难以跟踪它的当前值。

减少变量
  1. 没有价值的临时变量:没有拆分复杂表达式;没做更多澄清;只用过一次。
  2. 减少中间结果:得到后立即处理
  3. 减少控制流变量:控制流变量通常可以通过更好地运用结构化编程而消除
缩小变量的作用域

避免全局变量,让所有的变量都缩小作用域,对尽量少的代码行可见。编程语言提供多重作用域/访问级别,包括模块、类、函数以及语句块作用域。通常越严格的访问控制越好,因为这意味着该变量对更少的代码行“可见”。比如,Java中private、protected、public和default区别:

类内部 本包 子类 外部包 说明
public 具有最大的访问权限,可以访问任何一个在CLASSPATH下的类、接口、异常等。用于对外的情况,也就是对象或类对外的一种接口的形式。
protected 用来保护子类的。它的含义在于子类可以用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西。
default 针对本包访问而设计的,任何处于本包下的类、接口、异常等,都可以相互访问,即使是父类没有用protected修饰的成员也可以。
private 访问权限仅限于类的内部,是一种封装的体现,例如,大多数的成员变量都是修饰符为private的,它们不希望被其他任何外部的类访问。
只写一次的变量更好

操作一个变量的地方越多,越难确定它的当前值。那些只设置一次值得变量(或const、final、常量)使得代码更容易理解。

例子

运用上述内容所讲方法优化代码。
改动前:

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;

改动后:

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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 148,637评论 1 318
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 63,443评论 1 266
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 99,164评论 0 218
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,075评论 0 188
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,080评论 1 266
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,365评论 1 184
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,901评论 2 283
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,649评论 0 176
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,122评论 0 223
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,734评论 2 225
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,093评论 1 236
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,548评论 2 222
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,028评论 3 216
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,765评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,291评论 0 178
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,162评论 2 239
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,293评论 2 242

推荐阅读更多精彩内容