《代码整洁之道》读书笔记
原文链接 http://www.rogerblog.cn/2016/08/11/Clearn-Code/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
布朗法则 :Later Equals Never
多少次我们为了进度写下一堆又一堆的烂代码,当每一次运行通过后是不是想着“先这样吧,后面再优化”,但是 later equals never 。或许这就是优秀和平庸的区别,细节决定成败。
代码质量与其整洁度成正比,而且与代码质量紧密相关。
第一章 整洁代码
什么样的代码才是整洁的代码?
- 能通过所有的测试。
- 没有重复代码。
- 体现系统中的全部设计理念。
- 包含尽量少的实体、比如类、方法、函数等。
- 代码要力求集中,每个模块、类和函数要全神贯注于一件事。
以上第五点是我自己结合网上的读书笔记添加的。做到这五点应该能算上是整洁的代码。
第二章 有意义的命名
将命名放在第二章,可见其重要性。在编程中我们大概有 10% 的时间都在思考如何给变量、方法、类来命名,如何正确的命名对代码质量起到至关重要的目的。以下是书中介绍的几个重要的点:
名副其实。 说起来很简单。选个好名字需要花时间,但省下的时间比花掉的多。注意命名,一旦有好的命名,就换掉旧的。
避免误导。比如不是List类型,就不要用个accountList来命名,这样形成误导。
做有意的区分。
使用便于搜索的的名称,单个字母或者数字常量是很难在一大堆文章中找出来。比如字母e,它是英文中最常用的字母。长名胜于短名称,搜得到的名称胜于自编的名称。 窃以为单字母的名称仅用于短方法中的本地变量。名称长短应与其作用域大小相对应。
类名应该是名词或短语,像Customer,Account,避免使用Manager,Processor,Data或者Info这样的类名。类名不应当是动词。方法名应该是动词或动词短语,如postPayment ,deletePage或Save,属性访问、修改和断言应该根据其值来命名,并加上get,set,is这些前缀。
别扮可爱,耍宝,比如谁知道HolyHandGrenada 函数是干什么的,没错这个名字挺伶俐,但是不过DeleteItems或许是更好的名字。
每个概念对应一个词。并且一以贯之。在一堆代码中有Controller,又有manager,driver。就会令人困惑。比如DeviceManager和Protal-Controller之间又什么本质区别?
第三章 函数
函数应该专注于一件事,做好一件事,并且要尽量的短小。
函数的语句要在同一抽象层级上,如果函数中混杂不同的抽象层级就会使得读者无法 判断哪个表达式是基础概念还是细节。
每个函数要跟着下一抽象层级的函数。
函数名要采用描述性的名称并且用尽量少的函数参数。
使用异常替代返回错误码。
避免在函数中重复使用代码。
写函数的一开始都是冗长复杂的,之后要用心分解函数、修改名称和消除重复。
第四章 注释
个人认为最好的注释就是没有注释,因为一旦使用注释即表示你的命名或代码已经无法表达真正意图。写注释的常见动机之一是糟糕代码的存在。带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样的多。与其花时间编写解释你搞出的糟糕的代码注释,不如花时间清洁那堆糟糕的代码。但是世事无完美,有些时候我们就需要注释来解释我们真正的意图。
PS:我认为有些记录业务逻辑的注释还是很有必要的,好记忆不如烂笔头
好注释:
法律信息。有时,公司代码规范要求编写与法律有关的注释。例如版权和著作申明。
提供信息的注释。
对意图的解释。 有时注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。
阐释。
警示,告诉别人要注意这个方法之类的。
放大。有的代码可能看着有点多余,但编码者当时是有他自己的考虑,这个时候需要注释下这个代码的重要性。避免后面被优化掉。
第五章 格式
这一章写的是一些基本的代码格式,一个开发小组应该认同一种开发格式,这个应该是主程应该决定并维护的工作。
第六章 对象和数据结构
过程式代码便于在不改动既有函数的前提下添加新类。
得墨耳律:模块不应了解它所操作对象的内部情形。
方法不应调用由任何函数返回的对象的方法。
PS.这一章的部分知识略显隐晦,需要多多参详。
第七章 错误处理
使用异常而非返回码。 先写try、catch、finally语句 (关于 try catch return null 的顺序问题,请参考 Link)
使用不可控异常
给出异常发生的环境说明。 > 应创建信息充分的错误信息,并和异常一起传递出去。在消息中,包括失败的操作和失败的类型。如果你的应用程序由日志系统,传递足够的信息给catch块,并记录下来。
依调用者需要定义异常类。
定义常规流程。
别返回null值。别传递null值。
第八章 边界
这一章介绍的是在使用第三方程序包或者开源代码的时候,如何保持软件系统边界的问题。
第九章 单元测试
TDD三定律:
除非这能让失败的单元测试通过,否则不允许去编写任何的生产代码。
只允许编写刚好能够导致失败的单元测试。 (编译失败也属于一种失败)
只允许编写刚好能够导致一个失败的单元测试通过的产品代码。
测试代码和生产代码同样重要,需要被思考、设计并且不断的修改。它应该像生产代码一样保持整洁。有了测试,你就不担心对代码的修改,没有单元测试,每次修改都可能带来缺陷。
整洁的测试三要素:可读性,可读性和可读性。
每个测试一个断言。
每个测试只测试一个概念。
F.I.R.S.T :Fast(快速)、Independent(独立)、Repeatable(可重复)、Self-Validating(自足验证)、Timely(及时)
第十章 类
类应该短小
单一权责原则(SRP):类或模块应有且只有一条加以修改的理由。系统应该有许多短小的类而不是巨大的类组成。
内聚:如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。内聚性高,意味着类中的方法和变量相互依赖,相互结合成一个逻辑整体。
第十一章 系统
本章中用建造城市来比喻构造一个系统,说明了系统中需要注意的点,将系统的构造与使用分开,这是两个不一样的过程:
工厂,有时候应用程序需要确定何时创建对象,我们可以使用抽象工厂模式。将构造的细节隔离于应用程序之外。
依赖注入(DI/IOC)。在依赖管理情景中,对象不应该负责实例化对自身的依赖,反之,它应该将这份权责移交给其他有权利的机制,从而实现控制的反转。
扩容:“一开始就做对的系统”纯属神话,反之,我们应该只实现今天的用户的需求。然后重构,明天再扩容系统,实现新用户的需求。这就是迭代和增量敏捷的精髓所在。就像城市不断的再拆掉,再建设。
面向切面编程。AOP中,被称为方面(aspect)的模块构造指明了系统中哪些点的行为会以某种一致的方式被修改,从而支持某种特定的场景。这种说明是用某种简洁的声明(Attribute)或编程机制来实现的。
第十二章 迭进
简单设计规则:
- 运行所有测试
- 不可重复
- 表达了程序员的意图
- 尽可能的减少类和方法
测试消除了对清理代码就会破坏代码的恐惧。
第十三章 并发编程
并发是一种解耦策略,它帮助我们把做什么(目的)和何时(时机)做分解开。
关于并发一些比较中肯的说法:
- 并发会在性能和编写额外代码上增加一些开销
- 正确的并发是复杂的,即便对于简单的问题也是如此
- 并发缺陷并非总能重现,所以常被看做偶发事件而忽略,未被当做真的缺陷看待
- 并发常常需要对设计策略的根本性修改
一些基础定义:
在并发编程中用到的几种执行模型。
生产者-消费者模型。一个或多个生产者线程创建某些工作,并置于缓存或者队列中。一个或者多个消费者线程从队列中获取并完成这些工作。生产者和消费者之间的队列是一种限定资源。
读者-作者模型。当存在一个主要为读者线程提供信息源,但只是偶尔被作者线程更新的共享资源,吞吐量就会是个问题。增加吞吐量,会导致线程饥饿和过时信息的积累。协调读者线程不去读取正在更新的信息,而作者线程倾向于长期锁定读者线程。
宴席哲学家。许多企业级应用中会存在进程竞争资源的情形,如果没有用心设计,这种竞争会遭遇死锁,活锁,吞吐量和效率低等问题。
小结
后面的章节是一些实例的代码改进,对于如何实践上述的优化过程很有教学意义。
看完这本书,得到了许多指导和建议。印象最深的还是开头的那句话:Later equals never 。有些事情,现在不做,以后就更不会做了。