Fork me on GitHub
Google

编写可阅读代码的艺术-读书笔记

编写可阅读代码的艺术

1. 代码应易于理解

  • 1.1 可读性的基本标准:

    代码的写法应该使别人理解它需要的时间最少

  • 1.2 代码短并不是易理解的标准,2者基本没有关系: 如下面2段功能一致的代码。

代码片段1:

assert(!(bucket = FindBucket(key)) || !bucket -> IsOccupied())

代码片段2:

bucket = FindBucket(key);
if (bucket != null)
    assert(!bucket -> IsOccupied())

第一部分:表面层次的改进

名称,注释,代码风格

2. 信息装入到名称中

  • 2.1 选择专业的词
     GetPage   <  FetchPage < DownloadPage 
     Class BinaryTree{
        int Size();
     }
     Size()  <   NumNodes()  < Height()
  • 2.2 找到更有表现力的词 核心思想:清晰和精准比装可爱好
    send < deliver   dispatch   route  
    find < search   locate
    start< launch   begin   open
    make < create  build  set up  add  new
  • 2.3 避免使用 tmp retval这种泛泛的名字

    在交换算法中,tmp作为临时变量存在还是有意义的!

  • 2.4 循环迭代器

    1. i,j,k ,it,iter 一般都可以作为索引或者循环迭代器。
    2. 多重循环,可以根据具体需求,修改为 club_i ,member_i,user_i 或者简化为 ci,mi,ui
  • 2.5 用具体的名字代替抽象的名字 核心思想: 给变量,函数,类命名的时候,要把它描述得更具体而不是更抽象

    例子: –run_locally(本地运行)

    本意使程序输出更多的调试信息,但会运行得更慢。一般用于在本机上测试,但当运行在服务器上,性能很重要的时候,一般不会使用这个标记;

    带来的问题:

    1. 新成员不明白真正含义,可能认为是在本地运行的标记

    2. 偶尔我们需要在远程服务器上查看调试信息,使用这个看上去比较滑稽

    3. 有时我们在本地运行性能测试,不需要日志信息,所以不能使用–run_locally

    这种情况下,extra_logging这个意义会更好一点

  • 2.6 为名字附带更多信息

    16进制的id String id < String hex_id

  • 2.7 带单位的值

    如果是有度量的,最好是带上单位值:var start < var start_ms = (new Date()).getTime()

  • 2.8 附带其他重要属性 纯文本格式密码 password < plaintext_password 已转换为utf8的html html < html_utf8 核心思想:如果这个一个需要理解的关键信息,那么就放到名字里

  • 2.9 名字应该有多长 核心思想:在选择好名字的时候,一个隐含的约束就是,名字不能太长

  • 2.10 在小的作用域里可以使用短的名字 作用域小的标示符(对多少行其它代码可见)不用带上过多的信息:

    if (debug){
        Map<String,int> m;
        LookUpNameNumber(&m);
    }

m 虽然没有包含更多的信息,但是读者已经掌握理解这段代码的所有信息。

  • 2.11 输入长名称,已经不是问题

    基本上,常用的编辑器都已经有自动补全的功能,输入已经不是什么问题了。

  • 2.12 首字母缩略词和缩写

    程序员常用:eval 代替 evaluation, doc 代替document ,str 代替 string 使用项目缩写开头不是一个好的方式,BEManager,对新员工会有误解。

  • 2.13 丢掉没有用处的词

    ConvertToString < ToString()

  • 2.14 利用名字的格式来传递信息

    比如遵循一些规范,类名 开头大写,变量 小写开头……

    每种语言不一样,如C++有Google开源开发规范

  • 2.15 其它格式规范

    由公司或者团队所做出的一些开发规范或者约定。

总结:

核心就是把信息塞进名子里,让读者通过名字就能获取大量的信息。

3. 不会误解的名字

  • 3.1 推荐用max,min来(包含)极限

  • 3.2 推荐用first,last来表示包含的范围

  • 3.3 推荐用begin,end来表示包含/排除范围

  • 3.4 布尔值命名

    通常加上has,is,can,should这样的词,可以把布尔值变得明确。

    bool read_password = true; 存在二义性。修改为: bool need_password = true;

  • 3.5 与使用者的期望相匹配

    不要使用那种让大家有先入为主的名字。

    如,java中,get是一个轻量级的访问器,返回内部成员变量。如果你的代码中需要一个方法遍历所有经过的数据,并同时计算值的方法,getMean()显然会

    让大家误解,所以最好的方式是用computerMean()来表示

总结:

核心就是不会误解的名字是最好的名字。小心有歧义的名字。

4. 审美

核心思想: 如何使用好的留白,对齐和顺序来让你的代码变得更容易

  • 4.1 重新安排换行保持一致和紧凑

  • 4.2 用方法来整理不规则的东西

  • 4.3 在需要时使用对其列

    整齐的列可以很方便阅读

  • 4.4 将声明用块组装起来

    最好按逻辑分组,比如说功能块分组

  • 4.5 把代码分成段落

  • 4.6 个人风格与一致性

    一致的风格比正确的风格更重要

5. 该写什么样的注释

核心思想: 不要为那些从代码本身就能推断的事实写注释

  • 5.1 不要为了注释而注释

函数的声明与其注释时一致的,这类注释要删除或者改进它(增加更多的细节)

  • 5.2 不要给不好的名字加注释---应该把名字改好

    好代码 > 坏代码 + 注释

  • 5.3 记录你的思想

5.3.1 加入导演评论

//出乎意料的是,对于这些数据用二叉树比哈希表快40%
//哈希运算的代价比左/右比较大得多

5.3.2 为代码的瑕疵写注释

当代码需要改进:
//todo:  采用更快算法
标记          通常的意义
todo          我还没处理完的事情
fix me        已知的无法运行的代码
hack          对一个问题不得不采用比较粗糙的解决方案
xxx           危险,这里有重要的问题

5.3.3 给常量加注释

  • 5.4 站在读者的角度
  1. 意料之中的提问
  2. 公布可能的陷进
  3. 全局观注释
  4. 总结性注释
  • 5.5 克服”作者心里阻滞”
  1. 不管想什么,先写下来
  2. 读一下注释,看看有什么地方需要改进
  3. 不断改进

6 写出言简意赅的注释

核心思想: 注释应该有更高的 信息/空间 率

  • 6.1 让注释保持紧凑

  • 6.2 避免使用不明确的代词

如 it,this 等到底指代什么需要从代码中去获取,最安全的办法就是将这些代词换成明确的词,如 data

  • 6.3 润色注释

  • 6.4 精确描述函数的功能

例子:如统计一个文件中的行数 //Return the number of lines in this file 上面没有明确行的定义,是\n,还是\n\r,或者是\r 修改为: //Count how many newline byte(‘\n’) are in the file

  • 6.5 用输入/输出的例子来说明特别的情况

  • 6.6 声明代码的用途

  • 6.7 “具名函数参数”的注释

  • 6.8 采用信息含量高的词

当你发现注释非常长的时候,就得考虑是否可以用一个编程场景来描述它。

第二部分 简化循环和逻辑

7. 把控制流变得易读

核心思想: 把条件,循环及其他对控制流的改变做得越自然越好,运行一种方法让读者不用停下来就能重读你的代码

  • 7.1 条件语句中参数的顺序

    比较的左侧,被询问的表达式,它的值更倾向于不断变化。if (length >= 10) 优于 if(10 < length)

    比较的右侧,用来做比较的表达式,它的值更倾向于常量。while(received < expected) 优于 while(expected > received)

  • 7.2 if/else 语句块的顺序

    1.首先处理正逻辑,而不是负逻辑。 if(debug)而不是 if(!debug) 2.先处理简单的逻辑 3.先处理有趣或者可疑的逻辑

  • 7.3 ?:条件表达式

    简单的逻辑可以用三目运算符,复杂的逻辑可以换为if/else来处理

  • 7.4 避免do/while循环

  • 7.5 从函数中提前返回

  • 7.6 臭名昭著的goto

    避免使用goto

  • 7.7 最小化嵌套

   if(){
        if(){
        }
    }
    else{
    }

** 避免这种多层嵌套:** 1.通过提前返回来减少嵌套 2.减少循环内的嵌套

8. 拆分超长的表达式

核心思想:把你超长的表达式拆分成更容易的小块

  • 8.1 用做解释的变量
   if line.split(‘:’)[0].strip() == “root”:
      ......

拆分:
    username = line.split(‘:’)[0].strip()
    if username == “root”:
       ......
  • 8.2 总结变量
    if(request.user_id == document.owner_id){
       ……
    }

if条件有太多的变量,可能需要花点时间来理解。改为:

    finial boolean user_owns_document = (request.user_id == document.owner_id)
    if(user_owns_document){
        ......
    }
  • 8.3 使用德摩根
    not(a or b or c)  ==  (not a) and (not b) and (not c)
    not(a and b and c) == (not a) or (not b) or (not c)
  • 8.4 滥用短路逻辑

    小心“智能”的小段代码

  • 8.5 拆分巨大的语句

9. 变量和可读性

  • 9.1 减少变量

    1. 没有价值的临时变量
    2. 减少控制流变量
  • 9.2 缩小变量的作用域

    让你的变量对尽量少的代码行可见

  • 9.3 只写一次的变量更好

    操作一个变量的地方越多,越来确定它的当前值。

第三部分 重新组织代码

10. 抽取不相关的子问题

工程学就是把大问题拆分成小问题,再把这些问题的解决方案放在一起。本章的建议,积极的发现和抽取出不相关的子逻辑。

  • 10.1 纯工具代码

有一组核心功能,大多数程序都会用。如操作字符串,文件读写类

  • 10.2 其它多用途代码

  • 10.3 创建大量通用代码

  • 10.4 项目专有的功能

  • 10.5 简化已有的接口

    简单而且强大

  • 10.6 按需重构接口

  • 10.7 过犹不及 (不要盲目的不断抽取)

总结:把一般代码和项目专有代码分开

11. 一次只做一件事

核心思想:应该把代码组织得一次只做一件事情。本章是给代码做整理碎片的工作

  • 11.1 任务可以很小

  • 11.2 从对象中抽取值

12. 把想法变成代码

  • 12.1 清楚的描述逻辑,可以用自然语言描述

  • 12.2 了解函数库是有帮助的

  • 12.3 把这个方法应用于更大的问题

    1. 用自然语言描述解决方案
    2. 递归的使用这种方法

13. 少写代码

核心思想:最好读的代码就是没有代码

  • 13.1 别费神实现那个功能–你不会需要它

  • 13.2 质疑和拆分你的需求

仔细检查你的需求,把它消减成一个简单的问题,用很少的代码来实现。

  • 13.3 保持小代码库

  • 13.4 熟悉你周边的库

第四部分:精选部分

14. 测试与可读性

  • 14.1 使测试易于阅读和维护

测试代码的可读性与非测试代码的可读性是同样重要的。

  • 14.2 使这个测试更可读

普遍的测试原则:对使用者隐去不重要的点,以便更重要的细节会突出。

  • 14.3 让错误的消息具有可读性

    1. 更好版本的assert()
    2. 手工打造错误消息
  • 14.4 选择好的测试输入

    核心思想:使用一组简单的输入,它能完整的使用被测代码.

    1. 简化输入值
    2. 一个功能的多个测试
  • 14.5 为测试函数命名

    1. 被测试的类
    2. 被测试的函数
    3. 被测试的场景或bug

不要使用test1,test2这类,一个简单的测试函数名就是将上面这些信息拼凑在一起。可能在加一个Test_前缀。

  • 14.6 对测试比较好的开发方式

    TDD

  • 14.7 走得太远

  1. 牺牲真实代码的可读性,只为使能测试
  2. 着迷100%的覆盖率
  3. 测试成为产品开发的阻碍

15. 设计并改进“分钟/小时计数器”

一个具体的例子

comments powered by Disqus

top