命名
- 名副其实:避免出现命名模糊,应做到见名知其意
// bad naming
func getThem() -> [Int] {
var list: [Int] = []
for x in 0...99 {
list.append(x)
}
return list
}
// good naming
func getFlaggedCells() {
var cells: [Int] = []
let gameBoard = 0...99
for cell in gameBoard {
cells.append(cell)
}
return cells
}
- 避免误导: 比如前后拼写不一致
/// bad
func testName() {
var a = 1
let o = 1
let o1 = 2
let I = 2
if o == 1 {
a = o1
} else {
I = o1s
}
}
- 做有意义的区分,读者能够通过命名就能分别出两个方法之间的区别
-
使用读得出来的名称,不要造词
-
使用可搜索的名称,比如避免使用magic number
- 不推荐使用成员前缀
- 避免思维映射:不应当让读者在闹中把你的名称翻译为他们熟知的名称
- 类名:类名应该是名词,不应当时动词
- 方法名:应当是动词或者动词短语,eg: postPayment, delelePage
- 避免同一个单词用于不同目的
- 使用用专业术语命名
- 添加有意义的语境:主要用于拆分大函数
函数
- 目标:如何构建让读者一看就明白的函数
- 短小:函数的第一个规则就是短小
- 一个函数只做一件事:函数应该值做一件事,做好这件事,只做一件事。判断一个函数是否只做了一件事:就是看是否能够再拆出一个函数。
- 使用描述性的名称定义函数
- 函数参数:最理想的是零参数,其次是一个参数,两个参数,。。。如果要函数要对输入参数进行转换操作,转换结果就该体现为返回值
- 标识参数:标识参数丑陋不堪。reader(Boolean isSuite) 可以写为renderForSuite()和renderForSingleTest()
- 二元函数(两个参数的函数):可以使用一些机制将其转换为一元函数 writeField(outputStream, name) => outputStream.writeField(name)
- 三元函数(三个参数):应转换为参数对象
- 参数对象:函数中有三个及以上的参数,应将他们封装为对象
- 动词与关键字:函数和参数应当形成一种非常良好的动词/名词对形式
- 无副作用:一个函数只做一件事,eg:一个做加法的函数,却在函数内部修改了全局变量
- 分隔指令与询问:指令与询问分隔开来,防止混淆的发生
// 错误的
if (set("username", "bob")) ...
// 正确的
if (attributeExist("username") {
setAttribute("username", "bob")
}
- 使用异常替代返回错误码
- 抽离tryCatch代码块
- 错误处理就是一件事:错误处理的函数只做错误处理,不做其他事
- 别重复自己:比如避免类似于这种写法:Setup ,SuiteSetup, TeadDown, SuiteTearDown
- 结构化编程
注释
- 注释不能美化糟糕的代码:带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样得多,不能因为代码很糟糕而通过写注释来解释
- 有代码来阐述:很多时候,简单到只需要创建一个描述与注释所言同一事物的函数即可
- 好注释包含哪些:
- 法律信息
- 提供信息的注释
- 对意图的解释
- 阐述:注释某些晦涩难明的参数或返回值的意义翻译为某种可读形式
- 警示:用于警告其他程序员会出现某种后果的注释也是有用的
- TODO注释:是一种程序员认为应该做的,但是由于某种原因目前还没做的工作
- 放大:注释可以用来放大某种看来不合理之物的重要性
- 坏的注释有哪些:
- 楠楠自语
- 多余的注释
- 误导性的注释
- 循规式注释
- 日志型注释
- 废话注释
- 位置标记
- 括号后面的注释:会破坏函数结构
- 归属于署名:最好使用代码版本控制来处理归属
- 注释掉的代码:直接把代码删掉即可
格式
- 格式的目的:关乎团队沟通,增强代码的可读性,可读性会影响到代码的可维护性和扩展性
- 垂直格式
- 决定了代码文件的行数
- 向报纸学习:头条主题 -> 大纲 -> 概述 -> 故事细节。篇幅:短小精悍,有些稍微有点长,
- 概念间垂直方向上的区隔
- 垂直方向上的靠近
- 垂直距离
- 变量声明,应尽可能靠近其使用位置
- 实体变量:应该在类的顶部声明
- 相关函数:调用者函数应该尽可能放在被调用者上面
- 概念相关:概念相关的代码应该放到一起,相关性越强,彼此之间的距离就应该越短
- 垂直顺序
- 决定了代码的宽度
- 横向格式:代码行长度控制在100到120个字符之间
- 水平方向的区隔与靠近
- 水平对齐:推荐不水平对齐,不推荐C++的那种水平对齐方式
- 缩进:类中的方法相对改类缩进一个层级,方法的细实现相对方法声明缩进一个层级,代码块的事项相对于容器代码块缩进一个层级
对象和数据结构
- 数据抽象:不愿保罗数据细节,更愿意以抽象形态表述数据
- 数据,对象的反对称性
- 过程式代码便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动既有函数的前提下添加新类
错误处理
- 当错误发生时,程序员就有责任确保代码照常工作
- 使用异常而非返回码:如果使用返回码:调用者必须在调用之后即刻检查错误,但是这个步骤很容易被遗忘。如果是抛出异常,调用代码就被编译识别出来,不如不处理异常就编译不通过
- 先写try-catch-finally
- 使用不可控异常
- 给出异常发生的环境说明
- 依调用者需要定义异常类:便于被捕获
- 别返回null值
- 别传递null值
单元测试
- TDD三定律
- 定律一:在编写不能通过的单元测试前,不可编写生产代码
- 定律二:只可编写刚好无法通过的单元测试,不能编译也算不通过
- 定律三:只可编写刚好足以通过当前失败测试的生产代码
- 保持测试整洁:测试代码和生成代码一样重要,它需要被思考,被设计和被找了,它应该想生成代码一般保持整洁。测试越脏,代码就会变得越脏。最终,丢了测试,代码开始腐败
- 整洁的测试
- 三个要素:可读性,可读性,可读性
- 测试的三个环节:构造-操作-检验 (BUILD-OPERATION-CHECK)
- 每个测试一个断言:单个测试中的断言数量应该最小化
- 每个测试一个概念
- 最佳规则:应该尽可能减少每个概念的断言数量,每个测试函数只测试一个概念
- FIRST:
- 快速Fast:测试应该足够快
- 独立Independent:测试应该相互独立
- 可重复性repeatable:测试在任何环境中重复通过
- 自足验证Self-Validating:测试应该有布尔值输出
- 及时Timely:测试应及时编写
- FIRST:
类
- 类应该短小
- 单一权则原则:类和模块应有且只有一条加以修改的理由
- 内聚:类应该只有少量实体变量。类中的每个方法都操作一个或多个这种变量,通常而言,方法操作的变量越多,就越粘聚到类上。如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。
- 为了修改而组织:在整洁的系统中,我们对类加以组织,以降低修改的风险。在理想系统中,我们通过扩展系统而非修改现有代码来添加新特性
- 隔离修改:接口代替类
系统
- 将系统的构造与使用分开:软件系统应将启动过程和启动过程之后的运行时逻辑分离开,在启动中构建应用对象,也会存在互相缠结的依赖关系
- 分解main函数:将构造与适使用分开的方法之一是将全部构造过程搬迁到main或称之为main的模块之中。main函数创建系统所需的对象,再传递给应用程序,应用程序只管使用。
- 工厂:使用抽象工厂模式让应用程雪控制何时创建,但构造的细节却隔离与应用程序之外
- 依赖注入(DI):分离构造与使用,在依赖管理情景中,对象不应该负责实例化对自身的依赖,应该将这份权责移交给其他”有权力”的机制,从而实现控制的反转
- 扩容:架构都可以递增式地增长,只要我们持续将关注面恰当地切分
- 测试驱动系统架构:最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java对象实现,不同的领域之间最不具有侵害性的方面或类方面工具整合起来,这种架构能测试驱动,就像代码一样
迭进
-
简单设计的四条原则
- 运行所有测试
- 不可重复
- 表达程序员的意图(保证表达力)
- 尽可能减少类和方法的数量
-
简单设计规则1:运行所有的测试
- 全面测试并持续通过所有测试的系统,就是可测试的系统
- 遵循有关编写测试并持续运行测试的简单,明确的准则,系统就会更贴近低耦合度,高内聚的目标。编写测试引致更好的设计
- 测试消除了对清理代码就会破坏代码的恐惧
- 通过实例起到文档作用
-
简单设计规则2~4:重构
- 重构过程中,可以应用有关优秀设计的一切知识。提升内聚性,降低耦合度,切分关注面,模块化系统性关注面,缩小函数和类的尺寸,选用更好的名称。
不可重复:重复是拥有良好设计系统的大敌。它代表着额外的工作,额外的风险和额外且不必要的复杂度。
表达力:下一位读代码的人最有可能是自己。选用较好的名称,将大函数切分小函数,时时照拂自己创建的东西。用心是最珍贵的资源
极可能少的类和方法(优先级最低)