The Art of Readable Code - 代码可读性艺术
The Art of Readable Code
好代码的核心在于能够以最短时间被理解。代码不仅是给机器执行的指令,更是给人阅读的文档。
Chapter 1
- 什么是好代码?
好代码的核心在于能够以最短时间被理解 Show me the code !
// Good
for (Node* node = list->head; node != NULL; node = node->next)
Print(node->data);// Bad
Node* node = list->head;
if (node == NULL) return;
while (node->next != NULL) {
Print(node->data);
node = node->next;
}
if (node != NULL) Print(node->data);第一段代码简洁、紧凑、清晰,易于理解。第二段则混乱不堪,简直是我代码的典型。这并不是好代码。但是要写出简洁高效的代码需要在动手时多花几分钟思考,这是有难度的,但又是非常值得的,因为这是好习惯。
代码不是越短越好
// Good case
bucket = FindBucket(key);
if (bucket != NULL) assert(!bucket->IsOccupied());
// Bad case
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());虽然下面的代码只占了一行,可读性却大打折扣,而且没有明显的性能提升。
// Fast version of "hash = (65599 * hash) + c"
hash = (hash << 6) + (hash << 16) - hash + c;如果有时候必须要写复杂的代码,可以灵活使用注释。
Chapter 2
- Pack information into your names
用命名来传递信息能提高代码的可读性,谁都不想看那些被混淆过的代码。 选择特定的单词def GetPage(url): 很常见的命名方式,很容易就看出是在获取页面。但到底是从哪里获取呢?从本地还是远程?有更好的写法 比如FetchPage() 或是 DownloadPage()。这样能表达页面是从网上下载的。 Finding More "Colorful" Words
代码如同文章,使用更多样的单词也是提升代码的一个办法。但一定要清晰准确,不能盲目使用,跟英语写作同理。 Avoid Generic Names Like tmp and retval 不要使用通用的名称,给变量命名要体现变量本身的价值。
String tmp = user.name();
tmp += " " + user.phone_number();
tmp += " " + user.email();
...
template.set("user_info", tmp);用tmp确实没有问题,但是命名为user_info更能体现变量存储的内容。
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[j] == users[k])
cout << "user[" << j << "] is in club[" << i << "]" << endl;我们常用 i、j、k、iter 来命名,但这里有更精妙的写法来避免意外。
if (clubs[ci].members[mi] == users[ui])具体代替抽象,这样命名能有效减少BUG产生。 Prefer Concrete Names over Abstract Names 比如使用ServerCanStart()来测试端口是否被占用,这样表达并不清晰,CanListenOnPort()会更好。
Chapter3
- 避免歧义
命名限制最好的做法是把Min_ Max_ 放在代码最前面
// Bad
CART_TOO_BIG_LIMIT=10;
// Good
MAX_ITEMS_IN_CART=10;在boolean变量上用is_ or has_ 避免disable_。
Chapter 4
- 行对齐与统一很重要,尽量压缩垂直空间。
像这样子注释非常乱,没有对齐。
public class PerformanceTester {
public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(
500, /* Kbps */
80, /* millisecs latency */
200, /* jitter */
1 /* packet loss % */);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
45000, /* Kbps */
10, /* millisecs latency */
0, /* jitter */
0 /* packet loss % */);
public static final TcpConnectionSimulator cell = new TcpConnectionSimulator(
100, /* Kbps */
400, /* millisecs latency */
250, /* jitter */
5 /* packet loss % */);
}虽然对齐了,但占用了太多垂直空间,而且有重复注释
public class PerformanceTester {
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(
500, /* Kbps */
80, /* millisecs latency */
200, /* jitter */
1 /* packet loss % */);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
45000,/* Kbps */
10, /* millisecs latency */
0, /* jitter */
0 /* packet loss % */);
}
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(
100, /* Kbps */
400, /* millisecs latency */
250, /* jitter */
5 /* packet loss % */);修改后这样才是最好的,没有重复注释,也进行了对齐。
public class PerformanceTester {
// TcpConnectionSimulator(throughput, latency, jitter, packet_loss)
// [Kbps] [ms] [ms] [percent]
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(500, 80, 200, 1);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(45000, 10, 0, 0);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(100, 400, 250, 5);
}- 使用方法来消除重复代码
如果在写assert时重复了很多行,建议单独分出一个方法来消除重复代码,同时使得代码更美观。
- 把声明分成有意义的组 这样做的目的是增强可读性。
class FrontendServer {
public:
FrontendServer();
void ViewProfile(HttpRequest* request);
void OpenDatabase(string location, string user);
void SaveProfile(HttpRequest* request);
string ExtractQueryParam(HttpRequest* request, string param);
void ReplyOK(HttpRequest* request, string html);
void FindFriends(HttpRequest* request);
void ReplyNotFound(HttpRequest* request, string error);
void CloseDatabase(string location);
~FrontendServer();
};class FrontendServer {
public:
FrontendServer();
~FrontendServer();
// Handlers
void ViewProfile(HttpRequest* request);
void SaveProfile(HttpRequest* request);
void FindFriends(HttpRequest* request);
// Request/Reply Utilities
string ExtractQueryParam(HttpRequest* request, string param);
void ReplyOK(HttpRequest* request, string html);
void ReplyNotFound(HttpRequest* request, string error);
// Database Helpers
void OpenDatabase(string location, string user);
void CloseDatabase(string location);
};Chapter 5
- 无需注释易读的代码
要明白什么代码需要注释,做多余的注释毫无意义. 一眼就能看懂的代码: 无需注释 能表达自身意思的代码: 无需注释 看起来复杂又不能快速表达自身的代码: 需要注释 HACK代码: 强烈建议注释 名字丑的代码: 不要注释,用更好的名字代替 临时的代码: 无需注释,除非代码会留下来. 但是要注明这是临时的代码
- 用注释表达你的想法
代码表达了你的想法,但旁人看起来不一定精确.需要时最好用注释交代一下代码的含义和你想做的事情. 别人能读懂你的代码,但不一定能读懂你的心.