本文章学习自英文版书籍《The Art of Readable Code》。
懂得注释什么
- 不要注释从代码就可以一眼看出来的内容
注意“一眼”很重要,如果不是一眼,请注释
# remove everything after the second '*'
name = '*'.join(line.split('*')[:2])
上面虽然没有注释看一会也可以明白,但是加上注释就快了很多。
- 不要为了注释而注释
// Find the Node in the given subtree, with the given name, using the given depth.
Node* FindNodeInSubtree(Node* subtree, string name, int depth);
不要搞一个破名,然后搞一堆注释,还是把名字想好先吧
注释就像是创作心声
// Surprisingly, a binary tree was 40% faster than a hash table for this data.
// The cost of computing a hash was more than the left/right comparisons.
代码的问题也要注释出来
给常数注释
换位思考:想象你是看代码的人
避免含糊不清的代词
it、this、举例子
显然第二段代码的注释更清楚
// Remove the suffix/prefix of 'chars' from the input 'src'.
String Strip(String src, String chars) { ... }
// Example: Strip("abba/a/ba", "ab") returns "/a/"
String Strip(String src, String chars) { ... }
- 注释要表达代码真真意图而不是代码字面意思(high-level,big picture)
void DisplayProducts(list products) {
products.sort(CompareProductByPrice);
// Iterate through the list in reverse order
for (list::reverse_iterator it = products.rbegin(); it != products.rend();++it)
DisplayPrice(it->price);
...
}
// Display each price, from highest to lowest
for (list::reverse_iterator it = products.rbegin(); ... )
让控制流更加容易读
- 左边变量,右边常量,第一个会更容易读一点
if (length >= 10)
if (10 <= length)
- if/else,先判断哪种情况好呢?
下面哪个好呢?
if (a == b) {
// Case One ...
} else {
// Case Two ...
}
or as:
if (a != b) {
// Case Two ...
} else {
// Case One ...
}
准则:
1、要正的,不要反的
if(debug)好于if(!debug)
2、先处理简单的,再处理难的,这样也可以让if和else两个语句块都在同一屏里面
3、先处理特殊的、感兴趣的
上面的原则可能会冲突,你要进行衡量。
简单的判断可以使用三目运算符,但是复杂的还是用if else
不要使用do while
函数提早返回
如果没有不是下面的return会感觉很不自然。
public boolean Contains(String str, String substr) {
if (str == null || substr == null) return false;
if (substr.equals("")) return true;}
有时可能会想要同一个退出点,以便可以执行一些收尾工作,一些语言提供了这样子的机制:
C语言没有这样子的机制,可以重构函数或者用下goto
- 减少嵌套
if (user_result == SUCCESS) {
if (permission_result != SUCCESS) {
reply.WriteErrors("error reading permissions");
reply.Done();
return;
}
reply.WriteErrors("");
} else {
reply.WriteErrors(user_result);
}
reply.Done();
解决方案:先判断那些不行就肯定不行的(抓大头),上面的代码显然有两种错误,第一种错误不行了,那肯定不行啊,返回,干净利索,第一种没错就会下去,继续判断第二种错误,最好是成功的!!!!
if (user_result != SUCCESS) {
reply.WriteErrors(user_result);
reply.Done();
return;
}
if (permission_result != SUCCESS) {
reply.WriteErrors(permission_result);
reply.Done();
return;
}
reply.WriteErrors("");
reply.Done();
如果是在一个循环里面嵌套用continue,原则同上面的解决方案
for (int i = 0; i < results.size(); i++) {
if (results[i] != NULL) {
non_null_count++;
if (results[i]->name != "") {
cout << "Considering candidate..." << endl;
...
}
}
}
解决方案:
for (int i = 0; i < results.size(); i++) {
if (results[i] == NULL) continue;
non_null_count++;
if (results[i]->name == "") continue;
cout << "Considering candidate..." << endl;
...
}
分解大块的表达式
- 使用一些临时变量代表比较长的表达式
下面第一段真的是辣眼睛!!!!
var update_highlight = function (message_num) {
if ($("#vote_value" + message_num).html() === "Up") {
$("#thumbs_up" + message_num).addClass("highlighted");
$("#thumbs_down" + message_num).removeClass("highlighted");
} else if ($("#vote_value" + message_num).html() === "Down") {
$("#thumbs_up" + message_num).removeClass("highlighted");
$("#thumbs_down" + message_num).addClass("highlighted");
} else {
$("#thumbs_up" + message_num).removeClass("highighted");
$("#thumbs_down" + message_num).removeClass("highlighted");
}
};
化简后,要遵循DRY——Don’t Repeat Yourself原则
var update_highlight = function (message_num) {
var thumbs_up = $("#thumbs_up" + message_num);
var thumbs_down = $("#thumbs_down" + message_num);
var vote_value = $("#vote_value" + message_num).html();
var hi = "highlighted";
if (vote_value === "Up") {
thumbs_up.addClass(hi);
thumbs_down.removeClass(hi);
} else if (vote_value === "Down") {
thumbs_up.removeClass(hi);
thumbs_down.addClass(hi);
} else {
thumbs_up.removeClass(hi);
thumbs_down.removeClass(hi);
}
}
另外一个例子
void AddStats(const Stats& add_from, Stats* add_to) {
add_to->set_total_memory(add_from.total_memory() + add_to->total_memory());
add_to->set_free_memory(add_from.free_memory() + add_to->free_memory());
add_to->set_swap_memory(add_from.swap_memory() + add_to->swap_memory());
add_to->set_status_string(add_from.status_string() + add_to->status_string());
add_to->set_num_processes(add_from.num_processes() + add_to->num_processes());
...
}
修改后:
void AddStats(const Stats& add_from, Stats* add_to) {
#define ADD_FIELD(field) add_to->set_##field(add_from.field() + add_to->field())
ADD_FIELD(total_memory);
ADD_FIELD(free_memory);
ADD_FIELD(swap_memory);
ADD_FIELD(status_string);
ADD_FIELD(num_processes);
...
#undef ADD_FIELD
}
- 使用De Morgan’s公式来化简代码逻辑表达式
1) not (a or b or c) ⇔ (not a) and (not b) and (not c)
2) not (a and b and c) ⇔ (not a) or (not b) or (not c)
If you have trouble remembering these laws, a simple summary is “Distribute the not and switch and / or .” (Or going the other way, you “factor out the not .”)
例子:
if your code is:
if (!(file_exists && !is_protected)) Error("Sorry, could not read file.");
It can be rewritten to:
if (!file_exists || is_protected) Error("Sorry, could not read file.");
- 不要滥用逻辑运算表达式
下面这段看的时候要好好想一想
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());
改成这段会比较好,虽然第一段很紧凑,但是可读性更重要
bucket = FindBucket(key);
if (bucket != NULL) assert(!bucket->IsOccupied());
-
一个例子
需求:判断两个区间是否重叠,下面图片中,A、B、C互相不重叠,D重叠与它们都重叠
数据结构设计如下:
struct Range {
int begin;
int end;
// For example, [0,5) overlaps with [3,8)
bool OverlapsWith(Range other);
};
第一种方案:虽然很简短,但是实际代码逻辑很复杂,并且有bug
bool Range::OverlapsWith(Range other) {
// Check if 'begin' or 'end' falls inside 'other'.
return (begin >= other.begin && begin <= other.end) ||
(end >= other.begin && end <= other.end);
}
第二种方案:通常遇到上面的情况,我们会向,肯定会有一种更好的解决方式,但这需要创造力,其中一个技巧是使用反向。要求是重叠,那我们就看能不能用不重叠的方式解决,然后就:
bool Range::OverlapsWith(Range other) {
if (other.end <= begin) return false; // They end before we begin
if (other.begin >= end) return false; // They begin after we end
return true; // Only possibility left: they overlap
}