BeginMan blog

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

《编写可读代码的艺术》真是一本好书,读完之后收获很多,再回头看自己写的代码,真特么恶心。如何写优秀的代码?优雅的代码?牛逼的代码呢? 我总结了下面几点:

1. 掌握该门语言良好的编程风格和规范。 2. 熟练该门语言的语法和标准库,这是关键。 3. 多阅读和参与一些牛逼的开源代码,从别人的代码中汲取养分。 4. 学习设计模式、软件架构等理论基础。

这本书呢,很全面,关键思想是代码应该写得容易理解。确切地说,使别人用最短的时间理解你的代码。

本书分成四部分:

  1. 表面层次上的改进 (命名、注释以及审美)
  2. 简化循环和逻辑 (在程序中定义循环、逻辑和变量,从而使得代码更容易理解)
  3. 重新组织你的代码(第2条是行的简化艺术,那么第3条则是块(库、函数)拆分的艺术)
  4. 精选话题(测试,略去)

一. 第一部分:修于表

1. 代码应当易于理解

关键思想:代码应当易于理解, 具有良好的可读性,代码的写法应当使别人理解它所需的时间最小化。因此尽管减少代码行数是一个好目标,但把理解代码所需的时间最小化是一个更好的目标。

我们的可读性之旅从我们认为“表面层次”的改进开始:选择好的名字、写好的注释以及把代码整洁地写成更好的格式。这些改变很容易应用。

2.把信息装到名字里

关键思想 把信息装入名字中

本章分成6个专题:

  1. 选择专业的词, 如不用get,而是用download等,这往往由上下文决定。
  2. 避免泛泛的名字,如tmp等,除非你自己十分清楚。
  3. 用具体的名字替代抽象名字
  4. 使用前缀或后缀来给名字附带更多信息
  5. 决定名字的长度
  6. 利用名字的格式来表达含义

对于命名来说,是程序员头疼的问题,下面是一些例子,这些单词更有表现力,可能适合你的语境:

好的名字应当描述变量的目的或者它所承载的值。

建议:tmp这个名字只应用于短期存在且临时性为其主要存在因素的变量。

对于循环迭代器,像i,j,k等常作为索引或key,尽管这些名字很空泛,但是大家都知道。但是如果涉及更具体的情况,如forloop找到一个user属于哪个club:

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++)
            // ... do stuff ...

在这种情况下,使用更精确的名字可能会有帮助。如果不把循环索引命名为(i、j、k),另一个选择可以是(club_imembers_iuser_i)或者,更简化一点(cimiui)。这种方式会帮助把代码中的缺陷变得更明显。

在给变量、函数或者其他元素命名时,要把它描述得更具体而不是更抽象。

因此,如果关于一个变量有什么重要事情的读者必须知道,那么是值得把额外的“词”添加到名字中的。

如:一个变量包含十六进制字符串:

string id; // example: "ab3fe21"

如果要读者记住这个id很重要的话,最好改名为hex_id前缀后缀来描述额外信息。

下表列出一些没有单位的函数参数以及带单位的版本:

关于变量名的长度,那么如何来处理这种平衡呢?如何来决定是把一变量命名为d、days还是days_since_last_update呢? 这是要你自己要拿主意的,最好的答案和这个变量如何使用有关系,但下面还是提出了一些指导原则。

  • 在小的作用域里可以使用较短的名字, 小作用域一般一眼就看出个究竟。如变量类型、初值、如何析构很容易看清。
  • 因此如果一个标识符有较大的作用域,那么它的名字就要包含足够的信息以便含义更清楚。

3.不会误解的名字

本章会关注另一个话题:小心可能会有歧义的名字。关键思想 要多问自己几遍:这个名字会被别人解读成其他的含义吗?

建议:

  • 命名极限最清楚的方式是在要限制的东西前加上max_或者min_
  • 推荐用first和last来表示包含的范围
  • 推荐用begin和end来表示包含/排除范围

在给布尔值命名的时候要确保布尔变量或布尔函数返回true或false的意义明确。比如下面的例子:

bool read_pwd = true;

这会有两种不同的解释:

  • 需要读密码
  • 是否已经读取密码

这里read这个词迷惑性较大,改成need_pwd或is_authenticated更加合适。通常来讲,加上像is、has、can或should这样的词,可以把布尔值变得更明确。

避免使用反义词,如 bool disable_ssl=false;, 最好改成bool use_ssl=true;

总结:

  1. 避免使用多意性的词,如:filter,length,size, limit等,要具体化变量名称。
  2. 使用前后缀定义上下限,范围等。
  3. 给布尔变量或函数命名要使用is,has等意义明确的词,避免使用反义。

4.审美

好的源代码应当“看上去养眼”。有三条原则:

  1. 使用一致的布局,统一风格
  2. 让相似的代码看上去相似
  3. 把相关的代码分组,形成代码块

关于第一条在Python中是不符合规范的,在JS/C/C++/Java等很常见,比如下面的js代码:

var crypto 		= require('crypto');
var MongoDB 	= require('mongodb').Db;
var Server 		= require('mongodb').Server;
var moment 		= require('moment');

/*
	ESTABLISH DATABASE CONNECTION
*/

var dbName = process.env.DB_NAME || 'node-login';
var dbHost = process.env.DB_HOST || 'localhost';
var dbPort = process.env.DB_PORT || 27017;

这样的好处就是:对称美,容易发现bug,容易定位变量。坏处就是你需要维护这些对称,不管是tab还是space。

5.该写什么样的注释?

注释的目的是尽量帮助读者了解的和作者一样多。

本节分以下部分:

  1. 了解不需要注释
  2. 用代码记录思想
  3. 站在读者角度

小建议:

  • 不要为那些从代码本身就能快速推断的事实写注释。
  • 不要为了注释而注释。
  • 注释不应用于粉饰不好的名字。
  • 你不需要“拐杖式注释”——试图粉饰可读性差的代码的注释。写代码的人常常把这条规则表述成:好代码>坏代码+好注释。
  • 要为代码中的瑕疵写注释, 如这段代码哪里可能有问题,哪里需要改进等。加todo等小标记。

二.第二部分:简化循环和逻辑

通过试着最小化代码中的“思维包袱”来达到目的

7.把控制流变得易读

要注意条件语句中的参数的顺序,如:if (len>10) VS if (10 < len), 前者比后者可读性好。下面是指导原则:

其他原则

  • 对于if..else, 首先处理的是正,而非负, 如 if(a) else ..if(!a) else ..好。
  • 先处理简单的。
  • 先处理重要的或可疑的。
  • 关于三目运算:相对于追求最小化代码行数,一个更好的度量方法是最小化人们理解它所需的时间。

8.变量与可读性

有如下规则需要注意下:

  1. 变量越多,越难跟踪。
  2. 变量作用域越大,越容易出问题,跟踪它的动向就越久。
  3. 变量改动越频繁,越难跟踪它的当前值。

解决方法:

  1. 减少变量
    • 减少没有价值的变量
    • 减少中间结果
    • 减少控制流变量 如 Boolen isOk=false
  2. 缩小变量的作用域
  3. 只写一次的变量更好(const/final/常量等)使代码更容易理解。

(1). 减少变量

不要写没有价值的变量,如:

now = datetime.datetime.now()
msg.last_view_time = now

要考虑如下情况:

  1. now变量用了几次?
  2. now变量是否值得保留?
  3. 有没有拆分复杂的表达式?
  4. 还有没有做更多澄清?因为表达式datetime.datetime.now()已经很明晰了
  5. 如果没有now变量,会对代码有什么影响?

上述这段代码显而易见,now变量没啥用,直接改成:

msg.last_view_time = datetime.datetime.now()

(2).缩小变量的作用域

避免全局变量。很多编程语言提供了多重作用域/访问级别,包括模块、类、函数以及语句块作用域。通常越严格的访问控制越好,因为这意味着该变量对更少的代码行“可见”。

在OOP编程里,要减少类成员变量,因为它们就像类世界里的小型全局变量一样。可以使用方法参数的方式或静态方法来避免。静态方法是让读者知道“这几行代码与那些变量无关”的好办法。

在JS里的常用闭包来处理”私有”变量。

9.抽取不相关的子问题

本章的建议是“积极地发现并抽取出不相关的子逻辑”。

指导思想:

  1. DRY原则
  2. 一个函数只做一件事
  3. 创建大量通用代码
  4. 保持小代码库
    • 减少无用代码或功能
    • 项目拆分
    • 模块化
    • 服务化

(完~)