代码整洁之道
第一章 整洁代码
要想写好整洁的代码必须谨记几个原则。
- 稍后等于永不:不要去容忍写得十分糟糕的代码,积极地重构
- 永远假设代码是写给第三方看:使它变得可读可维护,cr的时候大家一眼就能看懂
- 不要重复代码:Don't repeat yourself
- 包括尽量少的实体: 如无必要,勿增实体
- 测试驱动:保证有足够多的测试,并且通过它
第二章 有意义的命名
-
一个变量的含义不应该太过依赖于它的注释
// bad var d int // 消逝的时间,以日计 // good var elapsedTimeInDays int -
避免误导,名称和含义必须保持一致
accountList 是不是List类型, 否则不如用accounts。
提防使用不同之处较小的名称比如1和l
-
有意义的区分
不要用一些毫无意义的东西,比如userInfo和user 没有区别, data 和productData 没有区别。 a, the variable 就不应该出现在变量名里,
命名里带上类型不是好习惯
-
能念出来的命名
不要用生捏出的词
-
使用可搜索的名称
一个变量的复杂程度往往可以和它的作用域成正比,方便搜索。
-
避免思维映射
如果一个变量名会在读者脑力翻译成其他含义,请修改它
-
类名和对象名应该是名词或者名词短语。
避免使用Manager,Processor,Data 这样的类名,也不应该是动词
-
方法名应该是动词或者动词短语
参考Javabean标准
-
每个概念对应一个词
给每个抽象概念选一个词,而且一以贯之,不要改来改去
-
添加有意义的语境
可以添加同样的前缀比如addr_, 以此提供语境
第二章 函数
-
短小
一般来说,不应该长于一屏。如果太长应该拆分成几个。太多重的if for循环也会降低可读性,避免出现一个函数里的多个for if嵌套
-
只做一件事
函数应该做一件事,做好这件事,只做这一件事。避免做一些函数签名中不包括事情。
-
每个函数一个抽象层级
确保函数中的语句都要在同一抽象层级上
-
使用描述性的名称‘
函数做的事情往往和名称成正比,函数分工越隔离,名称往往也越短。但是不要畏惧用长的描述性名称。
-
函数参数,尽可能还是要减少函数的入参个数。
如果函数需要很多入参,说明其中一些参数应该封装为类了。
-
命名中可以把参数的名称编码成函数名
-
无副作用
函数承诺只做一件事情,但有时候会把其他做的事情藏起来,这样会导致预期外的风险和古怪的耦合。亚亮的事故就是源自于函数的副作用!
-
面向对象编程一定程度上可以减少输出的个数
如果输出的参数太多。往往是因为不够面向对象
-
结构化编程
每个代码块都应该有一个入口一个出口,减少break和continue,不要有goto
第三章 注释
-
注释不能美化糟糕的代码
如果代码太糟糕,还是重写吧
-
好的注释>坏的注释>没有注释>错误的注释
-
如果阅读注释的时间比阅读代码的时间还长 就是坏注释
-
阐释性注释,需要确认其正确性
-
用好代码管理文件! 可以减少很多不必要的注释
-
TODO注释定时查看 删除不再需要的。
-
注释可以放大看来不合理的东西的重要性
-
注释掉的代码最好还是删掉,用git去记住它
第四章 格式
-
相关的代码块放在一起,比如一个变量的声明和初始化
-
用好空格去分割不同逻辑聚合的代码
-
声明变量应该尽可能靠近其使用的位置
如果函数很短,本地变量应该在头部出现。
实体变量应该在类的顶部声明
-
相关函数应该放在一起
如果一个调用了另外一个,就应该把他们放在一起且调用者放在被调用者上面
-
相关性越强的代码,彼此之间的距离就应该越短
-
长度不要太长,积极换行
第五章 数据结构
-
具象和抽象的重要性
隐藏实现关乎抽象,类应该向外暴露接口,而不是用取值器和赋值器
对象应该把数据隐藏于抽象之后
-
面向对象和面向过程编程的重要区分就是函数和变量的区别
面向对象,不方便添加新函数,因为要修改所有类。面向过程,难以添加新数据结构,因为必须修改所有函数
-
得魔忒耳律
模块不应该了解它所操作对象的内部情况。对象隐藏数据,保留操作。对象不应通过存取器暴露其内部结构。类C的方法f只应该调用以下对象的方法:C,由f创建的对象,作为参数传递给f的对象,由c的实体变量持有的对象
第六章 错误处理
- 错误处理不应该打乱代码的结构
- Try 像是一种事务, error的处理都可以看成对事务的一个特殊情况处理
- 别取null值,能返回非null的就不要返回null
- 更不要传递null值
第七章 边界
-
学习性测试很重要!
这也是一种投资,如果后续版本升级了你也可以通过学习性测试来验证功能是否改变
第八章 整洁的测试
-
TDD三定律
定律一 在编写不能通过的单元测试前,不可编写生产代码
定律二 只可编写刚好无法通过的单元测试,不能编译也算不通过
定律三 只可编写刚好足以通过失败测试的生产代码
-
保持测试的整洁性
脏测试 = 没测试
测试代码和生产代码一样重要
-
“单个断言”是个好准则,但不强求。需要保证的是单个测试中的断言数量应该最小化
ps:断言这个东西在实际的生产代码中十分危险,最好只出现在测试里
-
每个测试一个概念
-
整洁的测试遵循5条规则 F.I.R.S.T
快速,独立,可重复,自足验证,及时
第九章 类
-
类应该短小,不应该拥有太多不合理的指责
-
单一职责原则
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个全责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。
-
内聚
即每个方法都高度依赖于类的所有变量
-
为了修改而组织
-
依赖倒置原则,类应当依赖于抽象而不是依赖于具体细节
第十章 系统
-
将系统的构造与使用分开
启始过程和启始过程之后的运行时逻辑应该分离。
Lazy init 除外,有时候就违背了单一职责原则
依赖注入可以实现分离构造与使用
-
摒弃先做大设计的原则,自底向上
第十一章 迭代
-
通过迭代设计达到整洁目的
-
运行所有测试
测试越多,越关注测试就会越容易进行解耦,方便测试。测试也消除了对清楚代码就会破坏代码的恐惧
-
积极重构
-
不可重复
-
即可能少的类和方法
第十二章 并发编程
- 对象是过程的抽象。线程是调度的抽象
- 并发是一种解耦策略,它帮助我们吧做什么和何时做分解开。
- 并发编程的原则和技巧
- 单一职责原则
- 限制数据作用域
- 使用数据复本
- 线程尽可能的独立
- 警惕同步方法之间的依赖
- 不要把系统错误归咎于偶发事件
- 尽早并经常地在所有目标平台上运行线程代码
第十三章 味道与改进
-
不恰当的信息
-
废弃的注释应该删除
-
冗余的注释应该删除,比如什么也没说的注释
-
注释的代码应该删除!前提是用好git
-
过多的参数
-
输出参数违反也可以尽可能少,修改它所在对象的状态好了
-
标识参数是bad
-
永不调用的方法应该丢弃
-
明显的行为未被实现
-
不正确的边界行为
-
重复代表遗漏了抽象
-
在错误的抽象层级上的代码
-
基类依赖于派生类
-
信息过多,不应该暴露出太多的设计。良好的接口不需要提供太多的函数。
-
不要死代码
-
垂直分割的距离不要太远,定义和使用的位置尽可能近一些
-
消除特性依恋,但特性依恋有时候也是不得不做的事情
-
用选择算子来选择函数行为的参数-> 应该改为多个函数
-
位置错误的全责很重要
-
应该使用解释性变量
-
函数名称应该足以表达其行为
-
理解算法
-
把逻辑依赖改为物理依赖
-
用多态替代ifelse,或switch/case
-
用命名常量替代魔术数
-
结构甚于约定
-
封装条件,如果if的逻辑难以理解应该把意图的函数抽离出来
-
避免否定性条件。尽可能把条件表示为肯定形式
-
只做一件事
-
掩蔽时序耦合,被调用的次序显而易见很重要,可以用排列函数参数 a
a() b() c() 可以改成 resA = a() resB = b(resA) resC = c(resB) -
封装边界条件,不要出现四散的+1,-1
-
函数应该只在一个抽象层级上
-
在较高层级放置可配置数据
-
避免传递浏览
-
采用描述性名称
-
名称应该与抽象层级相符合
-
名称不应该有歧义
-
为较大范围选用较长的名称
-
名称应该说明副作用
-
使用覆盖率工具
-
别忽略小测试
-
测试边界条件
-
全面测试相近的缺陷