《The Art of Readable Code》 笔记 (一)


第1章  代码应易理解 (Code should be easy to understand)

基本原则:好的代码,能够减少 “别人” 理解它的时间。 “别人” 不仅指的是 “其它人”,也可能是 “以后的自己”

1  合习惯

Node* node = list->head;
if (node == NULL) return;

while(node->next != NULL) {
    Print(node->data)
    node = node->next;
}
if(node != NULL) Print(node->data);

  上面代码,等效于下面代码,但是很明显,后者更容易理解。

for (Node* node = list->head; node != NULL; node = node->next)
    Print(node->data);

2  少不一定好

1)  少而好

// if 语句
if(exponent >= 0) {
    return mantissa * (1 << exponent);
} else {
    return mantissa / (1 << -exponent);
}

// 条件运算符
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);

2)  少而不好

// 不易理解
assert ((!(bucket = FindBucket(key))) || !bucket->IsOccupied());

// 易理解
bucket = FindBucket(key);
if(bucket != NULL) asset(!bucket->IsOccupied());

3  加注释

// Fast version of "hash = (65599 * hash) + c"
hash = (hash << 6) + (hash << 16) -hash + c;

====================================================================================================================== 

第2章  封装信息到名字 (Packing information into names)

 

2.1  use specific words

  GetPage() 不如 FetchPage() 和 DownloadPage() 更具体; BinaryTree 类中的 Size() 不如 Height()、NumNodes() 或 MemoryBytes() 更明确

class BinaryTree {
  int Size();
  ...        
}

  同理,Thread 类中的 通常是 Kill(),而不是 Stop(),并且一般 Pause() 和 Resume() 成对出现

class Thread {
  void  Stop();
  ...          
}

  一些常用词语的替代词

send  deliver, dispatch, announce, distribute, route
find  search, extract, locate, recover
start  launch, create, begin, open
make  create, set up, build, generate, compose, add, new

2.2  avoid generic names

1) retval

  下面的 retval 求的是 “平方和”,因此,用 sum_squares 代替更为合适

var euclidean_norm = function (v) {
    var retval = 0.0;
    for (var i = 0; i < v.length; i += 1)
        retval += v[i] * v[i];
    return Math.sqrt(retval);
};

2) tmp

  tmp 合适的例子

if (right < left) {
    tmp = right;
    right = left;
    left = tmp;
}

  但是,下面这个 tmp 就不如改为 user_info 了

String tmp = user.name();
tmp += " " + user.phone_number();
tmp += " " + user.email();
...
template.set("user_info", tmp);

 同样,下面的 tmp_file 也比 tmp 更为明确

tmp_file = tempfile.NamedTemporaryFile()
...
SaveDate(tmp_file, ...)

 3) i, j, k

    i, j, k 非常容易混淆,不如改为 clubs_i, members_i, users_i 方便,再次简化为 ci, mi, ui

for (int i = 0; i < clubs.size(); i++)
    for (int j = 0; j < clubs[i].members.size(); j++)
        for (int k = 0; k < users.size(); k++)
            if (clubs[i].members[k] == users[j])
                cout << "user[" << j << "] is in club[" << i << "]" << endl;

2.3  use concrete names

   例如,当监听端口时, ServerCanStart() 是抽象的,不如 CanListenOnPort() 具体

   之前的谷歌规范中,为了避免 c++ 编译器自动生成拷贝构造函数和赋值算子,使用了如下宏:

class ClassName {
private:
    DISALLOW_EVIL_CONSTRUCTORS(ClassName);
public:
    ...
};

  其定义为:

#define DISALLOW_EVIL_CONSTRUCTORS(ClassName) \
  ClassName(const ClassName&); \
  void operator=(const ClassName&);

  实际上,这个名字并不好,现在已经改为了 DISALLOW_COPY_AND_ASSIGN(ClassName) 

  c++11 中,由于 delete 关键字的引入,已经解决了此问题,无须使用该宏了。可参见 C++11 之 delete 和 default

===========================================================================================================

2.4  附加额外信息

1)  encode value type 

  对于某些变量,附加额外的信息可以让人更好的理解,比如,一个16进制的变量,显然 hex_id 要比  id 更为贴切

string  id;     // Example: "af84ef845cd8"
string  hex_id;

2)  encode value units

  下面的 JavaScript 代码,乍看没有任何问题,但实际上 getTime() 返回值的单位是 ms 而不是 s  

var start = (new Date()).getTime(); // top of the page
...
var elapsed = (new Date()).getTime() - start; // bottom of the page
document.writeln("Load time was: " + elapsed + " seconds");

  将 _ms 作为后缀加到变量的后面,则会使代码变得更为清晰

var start_ms = (new Date()).getTime(); // top of the page
...
var elapsed_ms = (new Date()).getTime() - start_ms; // bottom of the page
document.writeln("Load time was: " + elapsed_ms / 1000 + " seconds");

   除了时间以外,还有一些别的单位如下表所示:

 Fucntion parameter  Renaming parameter to encode units
 Start (int  delay)  delay -> delay_secs
 CreateCache (int  size)  size -> size_mb
 ThrottleDownload(float  limit)  limit -> max_kbps
 Rotate (float  angle)  angle -> degrees_cw

 3)  encode other attributes

  如上述漫画所示,一些有关安全的变量命名,也常常需要一些额外的信息

 Situation  Variable name  Better name
 A password is in "plaintext" and should be encrypted before further processing  password  plaintext_password
 A user-provided comment that needs escaping before being displayed  comment  unescaped_comment
 Byte of html have been converted to UTF-8  html  html_utf8
 Incoing data has been "url encoded"  data  data_urlenc

2.5  长名长域,短名短域

1)  短名短作用域

  变量 m 并没有封装任何信息,但是因为只在 if 作用域内有效,所以并不对妨碍代码的理解

if (debug) {
  map m;
  LookUpNamesNumbers(&m);
  Print(m);
}

2)  善用缩写

  当变量名实在太长时,可考虑缩写,其使用原则就是:新加入的团队成员,也能够理解该缩写的意思。例如,evaluation 常缩写为 eval,document 可缩写为 doc,string 缩写为 str

3)  去掉无用词

  比如,ToString() 优于 ConvertToString(),ServeLoop() 也比 DoServeLoop() 简洁

2.6  使用大写或下划线

   在谷歌 C++ Style Guide 中,成员变量名字后面都带有下划线 "_";常量的命名形式为 kConstantName,以便和 #define MACRO_NAME 宏区分开来;类名一般是各个首字母大写,同样成员函数名也是如此

static const int kMaxOpenFiles = 100;
class LogReader {
public:
    void OpenFile(string local_file);
private:
    int offset_;
    DISALLOW_COPY_AND_ASSIGN(LogReader);
};

相关