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