编写可读代码的艺术

代码应当易于理解

表面层次的改进

把信息装到名字里

把信息装入名字:

  • 使用专业的词汇;
  • 避免泛泛的名字
  • 在适当的作用域使用适当的单词长短;
  • 通过后缀的方式增加额外的信息,比如单位,比如编码的描述;
  • 利用名字的格式来表达含义(通过使用下划线等区分类的变量)
  • 用具体代替抽象的名字

使用专业的词汇


清晰和精确比装可爱好。

重灾区:get size

1
def GetPage(url)

换成FetchPage或DownloadPage会好很多

size -> height nums memoryBytes

stop -> kill resume pause

find -> search extract locate recover

send -> deliver dispatch annouce distribute route

start -> launch create begin open

make -> create setup build generate compose add new

避免泛泛的名字

比如retval tmp data ret
使用更具象的名字能够帮你清楚的定位错误。

tmp的用武之地只在于经典的变量交换。

循环迭代器索引:i j k 的大灾难,加上数组名前缀 mi ci di

具体的名字代替抽象的名字

在给变量、函数或其他元素命名时,描述的更具体而不是更抽象。

比如参数 –run_locally 本来是用来输出额外的调试信息

  • 新成员不知道干嘛的
  • 有时候远程也需要输出调试信息
  • 有时候本地性能测试,不想输出调试信息
    其实更适合extra_logging



为了更具体,需要为名字附带更多的信息

  • 带单位的值: diff_ms max_kbsps size_mb
  • 附带其他重要属性 hex_id data_urlenc html_utf8 plaintext_password


那么一般名字应该有多长呢?

  • 小的作用域里可以使用短的名字,比如if for 甚至可以用单个字母
  • 不用担心长名字,现在都有自动补全了
  • 首字母缩写,经验原则是你还是否能理解这种缩写?比如QD就很好理解,WSD可能有点难,REC就更难了
  • 普遍的没问题 str->string cnt->count eval->evaluation doc->document
  • 丢掉没用的词 convert_to_string -> to_string

利用名字的格式来传递含义:
下划线 连字符和大小写。

1
2
3
4
5
6
7
8
static const int kMaxOpenFiles = 100;//常量并非全大写,区分宏
class LogReader {// 类名驼峰
public:
void OpenFile(string local_file//变量名);
private:
int offset_;//类成员变量用下划线
DISALLOW_COPY_AND_ASSIGN(LogReader);
};

总结本章:把信息塞到名字里去。

  • 使用专业的单词——例如,不用Get,而用Fetch或者Download可能会更好,这由上下文决定。
  • 避免空泛的名字,像tmp和retval,除非使用它们有特殊的理由。
  • 使用具体的名字来更细致地描述事物——Server Can Start()这个名字就比CanListenOnPort更不清楚。
  • 给变量名带上重要的细节——例如,在值为毫秒的变量后面加上ms,或者在还需要转义的,未处理的变量前面加上raw_。
  • 为作用域大的名字采用更长的名字——不要用让人费解的一个或两个字母的名字来命名在几屏之间都可见的变量。对于只存在于几行之间的变量用短一点的名字更好。
    有目的地使用大小写、下划线等 — 例如,你可以在类成员和局部变量后面加上”_”来区分它们。

不会误解的名字

关键原则:多问自己几遍,这个名字会被别人误解成其他的含义么?

例子:

  • filter:需要明确是挑出来,还是过滤掉
  • clip(text,length):减掉length,还是剪出来length -> truncate更好
  • length->max_length->max_chars
  • 差一问题:max min 进行明确,不要使用limit
  • first last表明范围,不要用start stop

一些基本的实践:

  • 给bool命名,增加is has can should 这样的词
  • 与使用者的期望匹配:举出了STL里面有一个size的函数调用,其实O(N2)的操作

审美


好的审美真的很重要。


三条原则:

  • 使用一致的布局,让读者很快习惯这种风格;
  • 让相似的代码看起来相似;
    • 并不一定局限于固定换行
    • 但要保持一种固定的顺序
  • 把相关代码分组,形成代码块
    • 按照逻辑分组分段,会更为清晰

让代码好看,有的时候也会涉及到一些对代码本身的改进,比如对方法的抽象。

1
2
3
class Logger {
...
}
1
2
3
4
class Logger
{
...
}

个人风格的一致性,一致的风格,比正确的风格更为重要。

该写什么样的注释

关键思想:注释的目的是尽量帮助读者了解的和作者一样多

什么不需要注释

那些很明显的逻辑(能够被快速推断出来的逻辑),或者是因为命名不准确做的多余注释,为了注释而注释。

明显的逻辑:

1
2
3
4
5
6
7
8
// The class definition for Account
class Account {
public:
// Contructor
Account();
// set the profit member to a new value
void SetProfit (double profit);
}

不好的名字:

1
2
// Realease the handle for this key, does not modify actual registry
void DeleteRegistry(RegistryKey * key);

记录你的思想

加入导演评论:上帝视角

1
// 使用二叉树比哈希表处理这些数据快很多
1
// 这个类太乱了,谁写的,应该建立一个子类来处理它

加入特定的标记

标记 通常的事情
todo 未处理
FIXME 已知的无法运行的代码
HACK 为解决问题做的粗糙方案
XXX 危险!这里有重要问题

给常量加注释:

1
const CHAPTER_MAX = 500 // 后台能接受的最大为500,超过会有超时

站在读者的角度

公布可能的陷阱:

1
void SendEmail(string to, string subject ,string body);

这个调用有可能会花费整整一秒的时间,标注可能的超时时间。

一些全局观:

  • 类之间如何交互
  • 数据如何在整个系统中流动
  • 入口点在哪里

注释会打断你的思绪么

其实很多时候会帮助你整理思绪。
把要实现的逻辑拆成几个不同的步骤,然后逐一时间,定义接口。

写出言简意赅的注释

图1

写出注释的关键一点在于:注释应该有很高的信息/空间率。

热评文章