《可读代码的艺术》读书笔记2

本文章学习自英文版书籍《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<Product> products) {
products.sort(CompareProductByPrice);
  // Iterate through the list in reverse order
  for (list<Product>::reverse_iterator it = products.rbegin(); it != products.rend();++it)
    DisplayPrice(it->price);
...
}
// Display each price, from highest to lowest
for (list<Product>::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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容