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

96
sunnyaxin
2017.04.18 16:07* 字数 1502

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


如何编写可读代码

  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;
    }
}
读书笔记