《代码整洁之道》读书笔记

如何写出更好的代码

Posted by Roger on August 11, 2016

布朗法则 :Later Equals Never

多少次我们为了进度写下一堆又一堆的烂代码,当每一次运行通过后是不是想着“先这样吧,后面再优化”,但是 later equals never 。或许这就是优秀和平庸的区别,细节决定成败。

代码质量与其整洁度成正比,而且与代码质量紧密相关。

第一章 整洁代码

什么样的代码才是整洁的代码?

  1. 能通过所有的测试。
  2. 没有重复代码。
  3. 体现系统中的全部设计理念。
  4. 包含尽量少的实体、比如类、方法、函数等。
  5. 代码要力求集中,每个模块、类和函数要全神贯注于一件事。

以上第五点是我自己结合网上的读书笔记添加的。做到这五点应该能算上是整洁的代码。

第二章 有意义的命名

将命名放在第二章,可见其重要性。在编程中我们大概有 10% 的时间都在思考如何给变量、方法、类来命名,如何正确的命名对代码质量起到至关重要的目的。以下是书中介绍的几个重要的点:

  1. 名副其实。 说起来很简单。选个好名字需要花时间,但省下的时间比花掉的多。注意命名,一旦有好的命名,就换掉旧的。

  2. 避免误导。比如不是List类型,就不要用个accountList来命名,这样形成误导。

  3. 做有意的区分。

  4. 使用便于搜索的的名称,单个字母或者数字常量是很难在一大堆文章中找出来。比如字母e,它是英文中最常用的字母。长名胜于短名称,搜得到的名称胜于自编的名称。 窃以为单字母的名称仅用于短方法中的本地变量。名称长短应与其作用域大小相对应。

  5. 类名应该是名词或短语,像Customer,Account,避免使用Manager,Processor,Data或者Info这样的类名。类名不应当是动词。方法名应该是动词或动词短语,如postPayment ,deletePage或Save,属性访问、修改和断言应该根据其值来命名,并加上get,set,is这些前缀。

  6. 别扮可爱,耍宝,比如谁知道HolyHandGrenada 函数是干什么的,没错这个名字挺伶俐,但是不过DeleteItems或许是更好的名字。

  7. 每个概念对应一个词。并且一以贯之。在一堆代码中有Controller,又有manager,driver。就会令人困惑。比如DeviceManager和Protal-Controller之间又什么本质区别?

第三章 函数

  1. 函数应该专注于一件事,做好一件事,并且要尽量的短小。

  2. 函数的语句要在同一抽象层级上,如果函数中混杂不同的抽象层级就会使得读者无法 判断哪个表达式是基础概念还是细节。

  3. 每个函数要跟着下一抽象层级的函数。

  4. 函数名要采用描述性的名称并且用尽量少的函数参数。

  5. 使用异常替代返回错误码。

  6. 避免在函数中重复使用代码。

  7. 写函数的一开始都是冗长复杂的,之后要用心分解函数、修改名称和消除重复。

第四章 注释

个人认为最好的注释就是没有注释,因为一旦使用注释即表示你的命名或代码已经无法表达真正意图。写注释的常见动机之一是糟糕代码的存在。带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样的多。与其花时间编写解释你搞出的糟糕的代码注释,不如花时间清洁那堆糟糕的代码。但是世事无完美,有些时候我们就需要注释来解释我们真正的意图。

PS:我认为有些记录业务逻辑的注释还是很有必要的,好记忆不如烂笔头

好注释:

  1. 法律信息。有时,公司代码规范要求编写与法律有关的注释。例如版权和著作申明。

  2. 提供信息的注释。

  3. 对意图的解释。 有时注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。

  4. 阐释。

  5. 警示,告诉别人要注意这个方法之类的。

  6. 放大。有的代码可能看着有点多余,但编码者当时是有他自己的考虑,这个时候需要注释下这个代码的重要性。避免后面被优化掉。

第五章 格式

这一章写的是一些基本的代码格式,一个开发小组应该认同一种开发格式,这个应该是主程应该决定并维护的工作。

第六章 对象和数据结构

  1. 过程式代码便于在不改动既有函数的前提下添加新类。

  2. 得墨耳律:模块不应了解它所操作对象的内部情形。

  3. 方法不应调用由任何函数返回的对象的方法。

PS.这一章的部分知识略显隐晦,需要多多参详。

第七章 错误处理

  1. 使用异常而非返回码。 先写try、catch、finally语句 (关于 try catch return null 的顺序问题,请参考 Link

  2. 使用不可控异常

  3. 给出异常发生的环境说明。 > 应创建信息充分的错误信息,并和异常一起传递出去。在消息中,包括失败的操作和失败的类型。如果你的应用程序由日志系统,传递足够的信息给catch块,并记录下来。

  4. 依调用者需要定义异常类。

  5. 定义常规流程。

  6. 别返回null值。别传递null值。

第八章 边界

这一章介绍的是在使用第三方程序包或者开源代码的时候,如何保持软件系统边界的问题。

第九章 单元测试

TDD三定律:

  1. 除非这能让失败的单元测试通过,否则不允许去编写任何的生产代码。

  2. 只允许编写刚好能够导致失败的单元测试。 (编译失败也属于一种失败)

  3. 只允许编写刚好能够导致一个失败的单元测试通过的产品代码。

测试代码和生产代码同样重要,需要被思考、设计并且不断的修改。它应该像生产代码一样保持整洁。有了测试,你就不担心对代码的修改,没有单元测试,每次修改都可能带来缺陷。

整洁的测试三要素:可读性,可读性和可读性。

每个测试一个断言。

每个测试只测试一个概念。

F.I.R.S.T :Fast(快速)、Independent(独立)、Repeatable(可重复)、Self-Validating(自足验证)、Timely(及时)

第十章 类

  1. 类应该短小

  2. 单一权责原则(SRP):类或模块应有且只有一条加以修改的理由。系统应该有许多短小的类而不是巨大的类组成。

  3. 内聚:如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。内聚性高,意味着类中的方法和变量相互依赖,相互结合成一个逻辑整体。

第十一章 系统

本章中用建造城市来比喻构造一个系统,说明了系统中需要注意的点,将系统的构造与使用分开,这是两个不一样的过程:

  1. 工厂,有时候应用程序需要确定何时创建对象,我们可以使用抽象工厂模式。将构造的细节隔离于应用程序之外。

  2. 依赖注入(DI/IOC)。在依赖管理情景中,对象不应该负责实例化对自身的依赖,反之,它应该将这份权责移交给其他有权利的机制,从而实现控制的反转。

  3. 扩容:“一开始就做对的系统”纯属神话,反之,我们应该只实现今天的用户的需求。然后重构,明天再扩容系统,实现新用户的需求。这就是迭代和增量敏捷的精髓所在。就像城市不断的再拆掉,再建设。

  4. 面向切面编程。AOP中,被称为方面(aspect)的模块构造指明了系统中哪些点的行为会以某种一致的方式被修改,从而支持某种特定的场景。这种说明是用某种简洁的声明(Attribute)或编程机制来实现的。

第十二章 迭进

简单设计规则:

  1. 运行所有测试
  2. 不可重复
  3. 表达了程序员的意图
  4. 尽可能的减少类和方法

测试消除了对清理代码就会破坏代码的恐惧。

第十三章 并发编程

并发是一种解耦策略,它帮助我们把做什么(目的)和何时(时机)做分解开。

关于并发一些比较中肯的说法:

  1. 并发会在性能和编写额外代码上增加一些开销
  2. 正确的并发是复杂的,即便对于简单的问题也是如此
  3. 并发缺陷并非总能重现,所以常被看做偶发事件而忽略,未被当做真的缺陷看待
  4. 并发常常需要对设计策略的根本性修改

一些基础定义:

image

在并发编程中用到的几种执行模型。

  1. 生产者-消费者模型。一个或多个生产者线程创建某些工作,并置于缓存或者队列中。一个或者多个消费者线程从队列中获取并完成这些工作。生产者和消费者之间的队列是一种限定资源。

  2. 读者-作者模型。当存在一个主要为读者线程提供信息源,但只是偶尔被作者线程更新的共享资源,吞吐量就会是个问题。增加吞吐量,会导致线程饥饿和过时信息的积累。协调读者线程不去读取正在更新的信息,而作者线程倾向于长期锁定读者线程。

  3. 宴席哲学家。许多企业级应用中会存在进程竞争资源的情形,如果没有用心设计,这种竞争会遭遇死锁,活锁,吞吐量和效率低等问题。

小结

后面的章节是一些实例的代码改进,对于如何实践上述的优化过程很有教学意义。

看完这本书,得到了许多指导和建议。印象最深的还是开头的那句话:Later equals never 。有些事情,现在不做,以后就更不会做了。