《编写可读代码的艺术》阅读笔记(一)

表面层次的改进

一、命名

1. 把信息装到名字里

  • 选择更专业的词:就是要寻找更有表现力,更精准的词汇来进行命名,文章给出了一个可替换单词的表格:

    单词替换选择
    senddeliver, dispatch, announce, distribute, route
    findsearch, extract, locate, recover
    startlaunch, create, begin, open
    makecreate, set up, build, generate, compose, add, new
  • 避免空泛的词汇:比如tmp, retval这样的单词,除非有特殊的理由,比如:tmp这个名字之应用于短期存在临时性为其主要存在性素的变量。
    循环迭代器中的i, j, k也有更好的选择,比如user_i能更好的表示这个循环索引是用来指示user的。

  • 使用具体的名字来替代抽象的名字:想要检测服务是否可以监听某个端口,就不要用ServerCanStart()这样的名词,最好用CanListenOnPort()这样更加具体描述这件事情的名称。

  • 给变量名带上重要的细节

    • 对单位,编码方式等等进行一个解释,比如给带单位的变量,在后面带上单位,比如值为毫秒的变量后面加上_ms;还可以为未处理的数据前面加上一个raw_
  • 为作用域大的名字采用更长的名字:不应该把那些只有一两个字母的名字用在很大的作用域下;相对的,在小的作用域里面,就不要用太长的名字……

  • 有目的地使用大小写、下划线等:比如JavaScript中,就建议,构造函数应该首字母大写,而普通的函数则是小写。献上:JavaScript编码规范

2. 不会误解的名字

关键思想:多问自己几遍:“这个名字会被别人解读成其他的含义吗?”,要相信,一个好名字是不会被误解的

  • 举例:用filter()时,阅读者并不知道,是要将特定目标从里面挑选出来还是过滤掉。

推荐策略

  • 用min和max来表示(包含)极限:如果想要表示极限,而且这个极限是在取值范围内的,比如两位正整数,用[10,99]来表示,那么我们就可以用max_XXXX表示最大的极限99,用min_XXXX表示最小的极限10.
  • 用first和last表示包含的范围[first, last]

  • 用begin和end表示包含/排除范围[begin, end)

  • 明确的布尔值命名:用is, has, can, should这样的词来使得布尔值更明确,避免使用反义名词

  • 要和使用者的期望匹配:用户会期望get()或者size()是轻量级的方法。可以用用computer*()来代替复杂度很高的get*()操作,用countSize()或者countElements()来替代复杂度很高的size()等等

    • 例子:使用者很容易把get*()理解成一个很轻量的操作,他们可能认为get*()的时间复杂度只有O(1)?比如,getMean()这个函数是用来遍历,计算出平均值,但是使用者可能就觉得这个计算复杂度会很低,调用它的代价会很小。最好用computerMean()来代替它。

二、审美

  • 使用一致的布局,让读者很快就习惯这种风格
  • 让相似的代码看上去相似
  • 把相关的代码行分组,形成代码块

三、注释

注释的目的是让读者了解的和作者一样多

1. 写什么样的注释

1)不用写注释的地方

  • 不要为那些从代码本身就能快速推断的事实写注释
  • “好代码>好注释+坏代码”——不要用注释来粉饰那些烂代码和烂命名

2)需要写注释的地方

  • 用注释记录思想

    • “导演评论”:可以加入一些注释,用来解释这段代码是如何运行的,或者评论这段代码
    • “记录瑕疵”:可以记录一些代码中的瑕疵,甚至可以利用某些标记,比如

      标记意义
      TODO我还有没处理的事情
      FIXME已知的无法运行的代码
      HACK对一个问题不得不采用比较粗糙的解决方案
      XXX危险!这里有重要问题
    • “常量注释”:利用注释来解释为什么这个常量是这个值

  • 站在读者的角度

    • 意料之中的提问写上注释:比如为什么这里要这么实现?
    • 意料之外的行为写上注释:比如公布可能存在的陷阱:这个函数在某种情况下可能会存在问题
    • 全局观注释:用来解释所有的部分是如何一起工作的,比如,类之间如何交互,数据如何在系统中流动,入口在哪里…比如:

      // 这个文件包含了一些辅助函数,为我们的文件系统提供了更便利的接口
      // 它处理了文件权限及其他基本的细节
    • 总结性注释:用来总结前面的代码块,使读者不至于迷失在细节中

2. 写言简意赅的注释

注释应当有很高的信息/空间率

  • 让注释保持紧凑

  • 避免使用不明确的代词(如,it,this等等)

  • 润色粗糙的句子

  • 精确地描述函数的行为

    • 比如文件中只包含hello\n这段文字,究竟认为是一行还是认为两行,所以利用“//计算文件包含多少行(\n)”来代替“//返回文件的行数”会更精准
  • 用输入输出举例来说明特别情况,举例:

    //Examle: Strip("abba/a/ba", "ab") returns "/a/"
    String Strip(String src, String chars) {...}
  • 声明代码的意图:更高层次的意图,而不仅仅是这段代码干了什么。

    • 比如注释://将价格从高到低排列,而不仅仅注释说明:// 反向遍历队列
  • 嵌入注释来帮助理解参数含义:

    void Conect(int timeout, bool use_encryption) { ... }
    // Call the function with commented parameters
    Connect(/* timeout_ms = */ 10, /* use_encryption = */ false);
  • 采用信息含量高的词汇