首页 > 代码库 > 代码整洁之道 读书笔记

代码整洁之道 读书笔记



第1章 整洁代码
1.1 要有代码
1.2 糟糕的代码
     稍后等于永不
1.3 混乱的代价
     如果前期不注意,后期的添加代码、修改效率都非常低
1.3.1 华丽新设计
1.3.2 态度
1.3.3 迷题
1.3.4 整洁代码的艺术
1.3.5 什么是整洁代码
1.4 思想流派
1.5 我们是作者
     读和写代码的时间可能是10:1,可以用编辑器的回放功能查看自己的编写记录
1.6 童子军军规
1.7 前传与原则




第2章 有意义的命名
2.1 介绍
2.2 名副其实
     变量名太随意,haha、list1、ok 这些都没啥意义
2.3 避免误导
     包含List等关键字、字母o与数字0等
2.4 做有意义的区分
     反面教材,变量名:a1、a2、a3
     避免冗余,不要出现Variable、表字段中避免出现table、字符串避免出现NameString - 直接Name就好
2.5 使用读得出来的名称
     不要使用自己拼凑出来的单词,所谓的驼峰命名法,尽量使用完整的单词
2.6 使用可搜索的名称
     一些常量,最好不直接使用数字,而指定一个变量名,这个变量名可以便于搜索到
2.7 避免使用编码
     2.7.1 匈牙利语标记法
          避免像这样使用缩写
     2.7.2 成员前缀
          避免使用前缀,但是Android中一个比较好的喜欢用m表示私有等,个人感觉比较好
     2.7.3 接口和实现
          作者不喜欢把接口使用I来开头,实现也希望只是在后面添加imp
2.8 避免思维映射
     仅仅使用简单的a、b、c等缩略一些单词
2.9  类名
     类名与对象名应该是名词名词短语,不能使动词。
2.10 方法名
     方法名应当是动词或者动词短语
     不使用构造器,而使用相应的public(构造器太常用了,为这些参数起名字也比较费脑筋,所以这个不太赞同)
2.11 别扮可爱
     有的变量名叫haha、banana 
2.12 每个概念对应一个词
     项目中同时出现Control与Manager,为什么不统一使用其中一种?
2.13 别用双关语
     有时可能使用add并不合适,比例insert、apped。add表示完整的新添加的含义。     
2.14 使用解决方案领域名称
     看代码的都是程序员,所以起名字的时候可以多考虑专业词汇。
2.15 使用源自所涉问题领域的名称
2.16 添加有意义的语境
     可以把相关的变量放到一个类中,使用这个类来表明语境。
2.17 不要添加没用的语境
     名字中带有项目的缩写,这样完全没有必要
2.18 最后的话
     取好名字最难的地方在于需要良好的描述技巧和共有文化背景。     



第3章 函数
3.1 短小
     不要写几百行的函数
3.2 只做一件事
     尽量保证每个函数仅做一件事情
3.3 每个函数一个抽象层级
     自顶向下读代码:向下规则
3.4 switch语句
     把switch放到工厂中,外面不需要考虑里面如何具体实现。
3.5 使用描述性的名称
3.6 函数参数
     参数的个数尽量小于3个,越少越好
     3.6.1 一元函数的普遍形式
     3.6.2 标识参数 - 如果需要向方法传入true或者false说明方法肯定要做两件事情
     3.6.3 二元函数
     3.6.4 三元函数
     3.6.5 参数对象 - 如果参数过多,可以直接把这些参数封装到一个对象中
     3.6.6 参数列表 - 即使是可以传入多个参数,但是超过3个还是要考虑下如何精简
     3.6.7 动词与关键字
3.7 无副作用
     方法可能需要某种场景下调用才能有理想的结果。例如有些仅能在初始化的时候被调用,可以通过命名checkPassowrdAndInitializeSession
     输出参数,函数的内容是在输入参数上改动,这种情况尽量避免或者使用appedFooter(StringBuffer report)这样的命名
3.8 分隔指令与询问
     如果方法里面设置一个变量的值,并且有返回值,可以命名为setAndCheckIfExists
3.9 使用异常替代返回错误码     
     3.9.1 抽离Try/Catch代码块
          把try块中的内容抽取到一个方法中
     3.9.2 错误处理就是一件事
     3.9.3 Error.java依赖磁铁
3.10 别重复自己
     避免项目中功能重复的代码,更切记直接把一个函数copy到几个地方使用
3.11 结构化编程
     函数只要短小,偶尔出现return、break、continue语句没有坏处
3.12 如何写出这样的函数
3.13 小结
3.14 SetupTeardownIncluder程序
3.15 文献




第4章 注释
4.1 注释不能美化糟糕的代码
     注释除去编写的毫无疑义外,可能还会因为修改代码而忘记修改相应的注释,导致代码上面的注释其实不是不是下面代码的意思。
4.2 用代码来阐述
4.3 好注释
     4.3.1 法律信息 
     4.3.2 提供信息的注释 - 在正则表达式上添加符合要求的例子。返回值的含义包含在函数名中。
     4.3.3 对意图的解释 - 有些地方编写的代码可能跟常识不符合,但是是因为某种特定环境下必须这样写,可以用注释解释下。
     4.3.4 阐释
     4.3.5 警示 - 对代码修改的注意事项等。
     4.3.6 TODO注释
     4.3.7 放大
     4.3.8 公共API中的Javadoc - 可以学习下javadoc
4.4 坏注释
     4.4.1 喃喃自语 - 有些注释没解释清楚事情,好像是作者写给自己看的哑谜
     4.4.2 多余的注释 - 函数的代码很简单,很容易理解。但是注释却写的非常长
     4.4.3 误导性注释 - 某种特别情况注释中却没有描述,但是使用函数却不是和注释一样的效果。
     4.4.4 循规式注释 - 每个变量名、每个函数都有注释反倒成了愚蠢可笑。
     4.4.5 日志式注释 - 添加谁修改了,但是现在SVN等代码管理工具可以查看了
     4.4.6 废话注释
     4.4.7 可怕的废话
     4.4.8 能用函数或变量时就别用注释
     4.4.9 位置标记 - 使用//// 把一些功能分开,书中不建议使用,但是我个人感觉这样能使代码清晰些。
     4.4.10 括号后面的注释 - 多层嵌套,在括号的结尾添加上属于哪个控制关键字的结尾括号。这种情况可以抽取到多个方法中
     4.4.11 归属与署名 - 不需要,因为有源代码控制系统
     4.4.12 注释掉的代码 - 暂时不需要的代码可以直接删除,没必要注释掉,可以使用源代码控制系统找到之前版本
     4.4.13 HTML注释
     4.4.14 非本地信息
     4.4.15 信息过多 - 主需要最核心信息,没必要大段描述
     4.4.16 不明显的联系
     4.4.17 函数头
     4.4.18 非公共代码中的Javadoc - public需要添加javadoc告诉使用者一些信息,但是private方法很多时候不需要
     4.4.19 范例





第5章 格式
5.1 格式的目的
5.2 垂直格式
     5.2.1 向报纸学习
          可以自顶向下阅读,最上面的是比较重要的。
     5.2.2 概念间垂直方向上的区隔
          代码通常都是从上向下、从左向右读。
          函数间一定要留有空白,这样可读性会好些
     5.2.3 垂直方向上的靠近
     5.2.4 垂直距离
          函数内变量声明 - 在靠近使用的地方
          全局变量 - 在类顶部
          相关函数 - 当前函数调用的函数应该垂直放到一起
          相关概念 - 例如函数名前缀都一样的,或者重载的函数都放到一起
     5.2.5 垂直顺序
          
5.3 横向格式
          每一行代码放置多少个字符,如果需要横向滚动就让人难以接受,所以建议每行容纳120个字符左右,当然越短越好。
     5.3.1 水平方向上的区隔与靠近
          等号(=)两侧添加空格、计算符号(* / ) 左右添加空格
     5.3.2 水平对齐
          很多变量都变量名左侧对齐,这样很好看,但是处理成统一的格式需要时间,而且除了美观也没多大用途
     5.3.3 缩进
     5.3.4 空范围
          if函数的内容仅有一行就不添加括号,这样容易导致误读与修改上出现错误。
5.4 团队规则
5.5 鲍勃大叔的格式规则




第6章 对象和数据结构
6.1 数据抽象
     setter/getter添加这些方法的思考
6.2 数据、对象的反对称性
6.3 得墨忒耳律
     6.3.1 火车失事
     6.3.2 混杂
     6.3.3 隐藏结构
6.4 数据传送对象
6.5 小结
     对象暴露行为,隐藏数据。



第7章 错误处理
7.1 使用异常而非返回码
     不在方法中使用返回的方式,而是使用编程语言提供的异常机制来拦截
7.2 先写Try-Catch-Finally语句
7.3 使用不可控异常
7.4 给出异常发生的环境说明
7.5 依调用者需要定义异常类
7.6 定义常规流程
7.7 别返回null值
     返回空的字符串或者空的列表(Collections.emptyList())
7.8 别传递null值
     避免传入null值而不是在函数内进行判断(还是在函数内做null判断吧,毕竟一起编写代码,难免别人会传入)
7.9 小结
7.10 文献




第8章 边界
8.1 使用第三方代码
8.2 浏览和学习边界
8.3 学习log4j
8.4 学习性测试的好处不只是免费
     在某个API的功能不完全了解的情况下,单独写一个demo测试下是否是自己理解的那样,非常省成本而且准确。
8.5 使用尚不存在的代码
     已经定义好接口,类似与写测试数据一样。
8.6 整洁的边界
8.7 文献




第9章 单元测试
9.1 TDD三定律
     1. 在编写不能通过的单元测试前,不可以编写生产代码;
     2. 只有编写刚好无法通过的单元测试,不能编译也算不通过;
     3. 只可编写刚好足以通过当前失败测试的生产代码。
9.2 保持测试整洁
     测试代码和生产代码一样重要。它需要被思考、被设计和被照料。
9.3 整洁的测试
     每个测试都呈现构造-操作-校验(BUILD-OPERATE-CHECK)模式。
     9.3.1 面向特定领域的测试语言
     9.3.2 双重标准
9.4 每个测试一个断言
9.5 F.I.R.S.T.
     快速(Fast):测试运行应该够快。
     独立(Independent):测试应相互独立。
     可重复(Repeatable):测试应当可在任何环境中重复通过。
     自足验证(Self-Validating):测试应该有布尔值输出。
     及时(Timely)测试应及时编写。
9.6 小结





第10章 类
10.1 类的组织
10.2 类应该短小
     10.2.1 单一权责原则
          书中建议:系统应该由很多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,比与少数其他一起协同达成期望的系统行为。
     10.2.2 内聚
     10.2.3 保持内聚性就会得到许多短小的类
10.3 为了修改而组织




第11章 系统
11.1 如何建造一个城市
     讨论如何在较高的抽象层级 - 系统层级上保持整洁。
11.2 将系统的构造与使用分开
     11.2.1 分解main
          构造函数都放到入口的main中?
     11.2.2 工厂
          应用程序需要确定何时创建对象时可以使用工厂。
     11.2.3 依赖注入
          可以分析构造与使用。
11.3 扩容
11.4 Java代理
11.5 纯Java AOP框架
11.6 AspectJ的方面
11.7 测试驱动系统架构
11.8 优化决策
11.9 明智使用添加了可论证价值的标准
11.10 系统需要领域特定语言
11.11 小结



第12章 迭进
12.1 通过迭进设计达到整洁目的
     Kent Beck关于“简单设计”的四条规则:
     1. 运行所有测试
     2. 不可重复
     3. 表达了程序员的额意图
     4. 尽可能减少类和方法的数量
12.2 简单设计规则1:运行所有测试
12.3 简单设计规则2~4:重构
12.4 不可重复
12.5 表达力
12.6 尽可能少的类和方法
12.7 小结



第13章 并发编程
13.1 为什么要并发
13.2 挑战
     并发的执行是无序的,需要确保线程安全。
13.3 并发防御原则
     13.3.1 单一权责原则 
          方法/类/组件应当只有一个修改的理由。建议:把并发与非并发的代码分离开。
     13.3.2 推论:限制数据作用域
          synchronized,谨记数据封装;严格限制对可能被共享的数据的访问。
     13.3.3 推论:使用数据复本
          避免数据的共享,多线程返回的数据是单独的,执行完成后在合并到主线程。
     13.3.4 推论:线程应尽可能地独立
13.4 了解Java库
     限定资源 - 并发环境中有着固定尺寸或数量的资源。例如数据库连接盒固定尺寸读/写缓存等。
     互斥 - 每一时刻仅有一个线程能访问共享数据或共享资源
     线程饥饿 - 一个或一组线程在很长时间内或永久被禁止。例如:总是让执行的快的线程先运行,例如执行得快的线程没完没了,则执行时间长的线程就会“挨饿”
     死锁 - 两个或者多个线程互相等待执行结束。每一个线程都拥有其他线程需要的资源,得不到其他线程拥有的资源,就无法终止。
     活锁 - 执行次序一致的线程,每个都想要起步,但发现其他线程已经“在路上”。由于竞争的原因,线程会持续尝试起步,但在很长的时间内都无法如愿,甚至永远无法启动。
13.5 了解执行模型
     *** 13.5.1 生产者-消费者模型
          一个或多个生产则会线程创建某些工作,并置于缓存或队列中。一个或多个消费者线程从队列中获取并完成这些工作。生产者消费者之间的队列是一种限定资源。
     *** 13.5.2 读者-作者模型
     *** 13.5.3 宴席哲学家
13.6 警惕同步方法之间的依赖
     避免使用一个共享对象的多个方法。
13.7 保持同步区域微小
13.8 很难编写正确的关闭代码
13.9 测试线程代码
     13.9.1 将伪失败看作可能的线程问题
     13.9.2 先使非线程代码可工作
     13.9.3 编写可插拔的线程代码
     13.9.4 编写可调整的线程代码
     13.9.5 运行多于处理器数量的线程
     13.9.6 在不同平台上运行
     13.9.7 装置试错代码
     13.9.8 硬编码
     13.9.9 自动化
13.10 小结
13.11 文献



第14章 逐步改进
14.1 Args的实现
     我怎么做的? 要编写整洁代码,必须先写肮脏代码,然后再整理它。例如写作文,先写草稿,再写作文等。
14.2 Args:草稿
     14.2.1 所以我暂停了
          再新增列需求需要修改代码的之后,才能发现有哪些代码需要改动,改进的方法就是找出这些改动的地方:
          1. 每个参数类型都要有解析器范式元素、从而为该种类型选择HashMap的方法。
          2. 每种类型都需要再命令行字符串中解析,然后再转换为真是类型。
          3. 每种参数类型都需要一个getXXX方法,按照其真实类型向调用者返回参数值。
     14.2.2 渐进
14.3 字符串参数
14.4 小结
     修改前已经编写好Junit测试,这样能保证每次修改的时候都不会对系统功能造成影响。
     找出之后会爆炸式增长的地方。
     先添加一个变量等一步步的小范围修改,每次修改都需要通过Junit测试。



第15章 JUnit内幕
15.1 JUnit框架
15.2 小结



第16章 重构SerialDate
16.1 首先,让它能工作
16.2 让它做对
16.3 小结
16.4 文献


第17章 味道与启发
注释 
C1.不恰当的注释 
让不恰当的注释保存到源代码控制系统。 
C2.废弃的注释 
过时、无关或不正确的注释就是废弃的注释不应该保留必须马上删除。 
C3.冗余的注释 
注释应该谈及代码自身没提到的东西,否则就是冗余的。 
C4.糟糕的注释 
值得编写的注释必须正确写出最好的注释,如果不是就不要写。 
C5.注释掉的代码 
注释掉的代码必须删除。 
 环境 
E1.需要多步才能实现的构建 
构建系统应该是单步的小操作。 
E2.需要多步才能实现的测试 
只需要单个指令就可以运行所有单元测试。 
 函数 
F1.过多的参数 
函数参数应该越少越好,坚决避免有3个参数 的函数。 
F2.输出参数 
输出参数违反直接,抵制输出参数。 
F3.标识参数 
布尔值参数令人迷惑,应该消灭掉。 
F4.死函数 


       



永不被调用函数应该删除掉。 
 
一般性问题 
G1.一个源文件存在多个语言 
尽量减少源文件语言的数量和范围。 
G2.明显的行为未被实现 
遵循“最少惊异原则”,函数或者类应该实现其他程序员有理由期待的行为,不要让其他程序员看代码才清楚函数的作用。 
G3.不正确的边界行为 
代码应该有正确的行为,追索每种边界条件并进行全面测试。 
G4.忽视安全 
关注可能引起问题的代码,注重安全与稳定。 
G5.重复 
消除重复代码,使用设计模式。 
G6.在错误的抽象层级上的代码 
抽象类和派生类概念模型必须完整分离,例如:与实现细节有关的代码不应该在基类中出现。 
G7.基类依赖于派生类 
基类应该对派生类一无所知。 
G8.信息过多 
类中的方法,变量越少越好,隐藏所有实现,公开接口越少越好。 
G9.死代码 
找到并删除所有不被调用的代码。 
G10.垂直分隔 
变量和函数的定义应该靠近被调用代码。 
G11.前后不一致 
函数参数变量应该从一而终,保持一致,让代码便于阅读和修改。 
G12.混淆视听 
没用的变量,不被调用的函数,没有信息量的注释应该清理掉。 
G13.人为耦合 
不互相依赖的东西不该耦合。 
G14.特性依恋 






类的方法应该只对自身的方法和变量感兴趣,不应该垂青其他类的方法和变量。 
G15.选择算子参数 
避免布尔类型参数,使用多态代替。 
G16.晦涩的意图 
代码要尽可能具有表达力,明白的意图比高效和性能重要。 
G17.位置错误的权责 
“最少惊异原则”,把代码放在读者想到的地方,而不是对自己方便的地方。 
G18.不恰当的静态方法 
如果要使用静态方法,必须确保没机会打算让它有多态行为。 
G19.使用解释性变量 
把计算过程打散成一系列命名良好的中间值使程序更加可读性。 
G20.函数名称应该表达其行为 G21.理解算法 
G22.把逻辑依赖改为物理依赖 
依赖应该是明显而不应该是假设的依赖。 
G23.用多态替代If/Else或Switch/Case G24.遵循标准约定 G25.用命名常量替代魔术数 G26.准确 
代码中的含糊和不准确要么是意见不同的结果,要么源于懒散,都必须消除。 
G27.结构甚于约定 G28.封装条件 
把条件封装成方法。 
G29.避免否定性条件 
使用肯定性条件。 
G30.函数只该做一件事 G31.掩蔽时序耦合 
创建顺序队列暴露时序耦合,每个函数都产生一下函数所需参数,就可保障正确的时序。 
G32.别随意 






代码不能随意,需要谨慎考虑。 
G33.封装边界条件 
例如:+1或-1操作必须封装起来。 
G34.函数应该只在一个抽象层级上 
封装不在一个抽象层级上的代码,保持每个函数只在一个抽象层级上。 
G35.在较高层级放置可配置数据 
把配置数据和常量放到基类里。 
G36.避免传递浏览 
“得墨忒耳律”,编写害羞代码,让直接协作者提供所需的服务,而不要逛遍整个系统。 
 JAVA 
J1.通过使用通配符避免过长的导入清单 J2.不要继承常量 J3.常量VS.枚举 
使用枚举enum代替常量。 
 名称 
N1.采用描述性名称 
名称对应可读性有90%的作用,必须认真命名。 
N2.名称应与抽象层级相符 
不要取沟通实现的名称:取反映类或函数抽象层级的名称。 
N3.尽可能使用标准命名法 N4.无歧义的名称 
N5.为较大作用范围选用较长名称 N6.避免编码 
不应该在名称中包含类型或范围的信息,例如:m_,f等前缀。 
N7.名称应该说明副作用 
名称应该说明类、变量或函数的所有信息,不应该隐藏副作用。 






 测试 T1.测试不足 
保证足够的测试。 
T2.使用覆盖率工具 
覆盖率工具可以更好地找到测试不足的模块、类、函数。 
T3.别略过小测试 
T4.被忽略的测试就是对不确定事物的疑问 
用@Ignore表达我们对需求的疑问。 
T5.测试边界条件 
边界判读错误很常见,必须测试边界条件。 
T6.全面测试相近的缺陷 
缺陷趋向于扎堆,如果在函数中发现一个缺陷,那么就全面测试这个函数。 
T7.测试失败的模式有启发性 
你可以通过测试失败找到问题所在。 
T8.测试覆盖率的模式有启发性 
通过测试覆盖率检查,往往可以找到测试失败的线索。 
T9.测试应该快速 
慢测试会导致时间紧时会跳过,导致可能出现问题。