《The Art of Readable Code》 读书笔记 01

放假前在学校图书馆借了一本新书《The Art of Readable Code》,寒假回来看看,写写其中的Key Idea 、summary和一些读书笔记。

    Preface

    前言部分主要概况讲了本书的核心思想——Code shoule be easy to understand。接着探讨什么是好代码,是内容紧凑还是对每个过程都详细阐释?从而引发出核心概念:Code should be written to minimize the time it would take someone else to understand it.(代码应让人在尽可能短的时间内理解),这个人,很有可能就是以后的自己。

     通俗来说,简短的代码总是比长代码好读懂,但是不能因为追求简短而使代码难于理解。因此,尽管要追求代码的短小精悍,更应该在最小化理解代码的时间。同 时,易于读懂的代码通常更加易于优化,有更好的架构,易于测试.etc。Easy to understand,对代码来说,是最核心的,因此,当遇到其他冲突要素时,不能忘记这一点。


   Part One:  Surface-Level Improvements

     Packing information into names

    1. 选用明确词汇。

    比如,“get”就不是一个很明确的词汇,比如 def GetPage(url): 函数,我们将不能明确从哪里获取,是本地缓存还是数据库亦或是网络。比如从网络获取,我们可以选用fetch,download去替换。FetchPage,DownloadPage…….。一次类推,比如表达树的size,可以用NumNodes,进程stop可以用kill。另外,我们可以选用更加丰富的词汇来表达:

Word Alternatives
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

    Key idea: It's better to be clear and precise than to be cute. 清晰准确,甚于灵巧可爱。

    2. 避免使用泛化的名字,比如tmp,retval,foo等

    比如retval(I'm a return value),我们应该选用描述变量值含义的词汇。比如tmp,它应仅仅用于短期存在或临时性是该变量的重要成分(此时tmp应该作为前缀或后缀,tmp_file),除此之外,我们都应选用能够描述含义的词汇。

    另外,对于循环迭代中的 i,j,k,iter等,在多重循环时可以附加一些index的讯息。比如

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++)
            

    这里,i可以改为 ci, j改为 mi, k改为 ui,这样,就能够很清晰的明白每个循环变量是什么意思了。类似的,选用 r -> row, c -> column 用在矩阵运算中。

    3. 选用具体的名字而不是抽象的

     比如,ServerCanStart()是个抽象的名称,可以改成CanListenOnPort()这个具体的名称。

    为防止类拷贝,可以将拷贝构造函数和=运算私有化,谷歌中以前使用宏DISALLOW_EVIL_CONSTRUCTORS(ClassName),这里

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

    这里,宏名选用"evil"不太好,这里可以改为 DISALLOW_COPY_AND_ASSIGN(ClassName) …

  4. 给名字添加额外讯息。

    比如,string id; 这里的id没有附加更多的讯息,若该id是由十六进制数构成,可以改成 hex_id等。

    比如,变量是一个表示度量值的数,比如时间,重量。我们可以加上单位信息,这样对于代码就能够更好的看懂。delay -> delay_ms, angle -> degree_cw (顺时针)。

    同时,对于某些应用场合,需要给变量加上处理状态的讯息或编码的讯息,比如 password -> plaintext_password,表示未经处理的密码字符串。html -> html_utf8 表示

该网页是采用utf8编码等。

   5.选用适合长度的名字

    通过变量的使用范围跨度来度量。若是范围小,比如几行之间,可以选用短名字。

    若是范围跨度大,可以选用长点的名字表达更加丰富的信息,但是不能太长了。另外,编程敲打长名字,可以使用编辑器的自动补全功能。 vim中可以用ctrl+p,甚至安装插件,按个tab就行。

    另外,对于缩写,应该遵循通用的简称,比如string -> str, document-> doc, evaluation -> eval 等。

    最后,可以抛弃无用的字眼,比如在类型转换, ConvertToString() -> ToString(),这里,To就有convert的含义,所以convert可以抛弃。

   6. 选用不同名字格式表达不同的含义,可以参考google编程风格。

  

static const int kMaxOpenFiles = 100;  // 常量使用前面加k表示,而不是全大  写,同宏名区别

class LogReader{    // 类名首字母大写的驼峰法
    public:
         void OpenFile(string local_file);  // 局部变量名使用小写字母加下划线

    private:
         int offset_;    // 类成员变量使用小写,并在最后添加下划线
         DISALLOW_COPY_AND_ASSIGN(LogReader); // 宏名全大写
};

  名字不能被误解

    key idea: Actively scrutinize(细查) your names by asking yourself, "What other meanings could someone interpret from this name?"

    比如: filter() 过滤。 这里,过滤有双重理解,"to pick out"  or "to get rid of" 。 若是 to pick out ,则改为 select, 若是 to get rid of, 则改为 exclude。

   比如: clip() 剪除      def clip(text, max_len):   有两种含义: 1、remove length from the end   2、 truncates to a maximum length。同时,这里,max_len也是容易让人歧义,是指 words还是bytes亦或是characters,应该改为 max_chars 等。

   filter, length, limit是容易模棱两可的词汇。

   1、涉及最大最小的限制时,添加max或min的前缀,比如 max_len, min_len。

   2、前闭后闭范围[...],使用 first 和 last

   3、前闭后开范围[...),使用 begin 和 end

   4、布尔值,添加前缀 has, is, can, should 等表明,布尔值命名不要使用否定式。ex. bool disable_ssl = fasle; ->  bool use_ssl = true;

   5、不滥用约定熟成的变量名。 比如 get*(), 他应该是运算复杂度为 O(1)的方法,若是使用 getAverage(),它是一个复杂度为O(n)的运算,会误导人们去使用,导致增加时间消耗,应改为computeAverage(),这样,就告诉人们得到average是要compute的,而不是伸手既得的。

        比如 list::size(),在stl中,为了统一命名,对list容器采用了size()方法,而list::size()方法是O(n)的,vector::size()是O(1)的,这样就会误导用户使用size()增加时间消耗。庆幸的是,最新的C++标准中,list::size()是O(1)的。

        另外,在有多个候选名可以用时,要选择最能表达动机的词语。


 

    Aesthetics 美感

   Good source code should be just as "easy on the eyes"

   1、重排换行位置得到更加一致和紧致的效果

public class PerformanceTester{
    // TcpConnectionSimulator(Throughput, lantency, jitter, packet_loss)
    //                            [kbps]   [ms]       [ms]    [percent]
    public static final TcpConnectionSimulation wifi = 
        new TcpConnectionSimulator(              500,        80,                 200,          1);

    public static final TcpConnectionSimulation t3_fiber= 
       new TcpConnectionSimulator(             45000,      10,                     0,          0);

    public static final TcpConnectionSimulation wifi = 
       new TcpConnectionSimulator(              100,        400,                 250,         5);
}

  当然,这里忽略了敲打空白和排列的时间,上述代码肯定很容易看懂。

   2、对于重复形式的代码,编写函数,且函数命名有意义。

   3、多形参采用列对齐排列。如上面的代码。

   4、选择一个固定的排列顺序,比如按字母,按逻辑顺序,并在而后的调用中保持。

   5、大段代码按逻辑组合,添加空白行和功能注释,使代码层次鲜明。

   6、选用一致的代码风格,并保持。


 

 Knowing what to Comment

 key idea: The purpose of commenting is to help the reader know as much as the writer did.

 1、什么不该被注释?

   a. 不注释能从代码本身迅速传达含义的语句。

   b. 不要为了注释而注释,避免这点,应该详细添加表达代码逻辑层次的注释。

   c. 不为烂名字而注释,而应该改名字。 good code > bad code + good comments!

  2、 记录你的想法

    a. 包含“导演评论”,即洞察性的语句,能够避免重复劳动、无意义劳动。同时指出改进的方向。

    b. 注释代码中的“瑕疵”

Marker Typical meaning
TODO: 半成品
FIXME: 此处代码有问题
HACK: 诚然不雅的解决问题
XXX: 危险,主要问题在这

   c. 对常量注释()

   d.站在代码阅读者的角度去看

       猜测读者会在哪里产生疑惑。

       ex.

struct Recoder{
    vector<float> data;
    ...
    void Clear(){
         vector<float>().swap(data);  // why not just data.clear()?   
    }
}

上述代码中,读者将不太了解为什么要调用空对象的swap方法释放data的空间。这里,是唯一的方式强行使vector将自己的内存归还内存池。所以,应补上注释如下:

// Force vector to relinquish its memory (Look up "STL swap trick")
vector<float>().swap(data);

     另外,有一些函数的行为将占据大量的系统资源,需要注释说明其运行环境及限制条件。

    e.大框架注释   对整个系统的每个关键环节,节点间联通关系等进行注释,让后来人能够理解代码的意图。

    f. 对代码块进行注释,使读者免于陷入细节中。


 

    Making Comments Precise and Compact 注释更加精确紧致

  key idea: Comments should have a high information-to-space ratio。 注释应该言简意赅

   1.保持代码紧凑。

// The int is the CategoryType
// The first float in the inner pair is the 'score'
// The second is the  'weight'

typedef hash_map<int, pair<float, float> > ScoreMap;

// 这里,三行的注释可以归结成一行
// CategoryType->(score, weight)

  2.避免模棱两可的代词。

// Insert the data into the cache, but check if it's too big first

这里,it的指代不明,可能是data,也可能是cache。改进方法:

// 1.将代词补全
// but check if the data is too big first

// 2.将句型重新排列
// If the data is small enough, insert it into the cache

   3. 雕琢粗糙的表达。 这个,看语文功底了.

   4.精确描述函数行为。 比如一个函数要返回文件的行数,不能仅仅这样注释,应该要注释如何度量何为“行”的标准,如度量'\n'个数(unix),‘\n\r’(Windows)。

   5. 采用一些输入输出的样例来阐释特殊情况。这一点,如同acm的题,sample input, sample out ...

   6. 陈述代码的高层意图

   7. 可以对神秘的函数参数进行内联注释, e.g. Functoin(/* arg = */ ...)

   8. 选用信息密度强的词汇表达。也就是言简意赅。

 

 

   

posted @ 2013-02-07 18:56  xield  阅读(220)  评论(0编辑  收藏  举报