《The Art of Readable Code》 读书笔记 02

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. 进行德摩根变换。这样能够将复杂的表达简单化。学过《数字电路》的话,可以采用卡诺图来减小逻辑规模

  1. ! (a | b) == (!a & !b)
  2. ! (a & b) == (!a | !b)

    d.短路原则不要滥用。另外,有的时候,正向求解极其复杂,逆向求解却很容易。比如:1000台电器中至少有一个是坏的概率。若是正向求解,过程将采用穷举法,那可以说没有一个人能够正确解答,逆向的做法是,1-p(1000台都是良品),这如同当初高中使用反证法证明时是多么的酣畅淋漓。(也许人类就是喜欢挑刺)

    e.将大段的表达分解。提取共同部分,用新的变量表达

    f.适当采用宏定义,归纳模式


 

   Variables and readability

     该部分从三个部分阐释草率使用变量会导致程序难以理解。

  1. 变量越多越难保持跟踪
  2. 变量跨度越大越难保持跟踪
  3. 变量越常变化就越难得知其当前值。

   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;
        }
    }
};

 

 

 




 

 

 

 

 

 

posted @ 2013-02-08 21:07  xield  阅读(229)  评论(0编辑  收藏  举报