代码大全笔记
前言:代码是写给人看的,只是机器恰好能运行而已。
第 II 部分 高质量代码
1. 可以工作的类
1.1 关于构造函数
- 尽可能的在构造函数中初始化所有的成员数据
- 使用 private 构造函数来达到强制的单例属性,构造器私有化可以防止创建对象
- 尽量使用深拷贝而不是浅拷贝
- 深拷贝:完全复制一个一模一样的对象,与源数据没有关系,不受影响;(通过重写类的 clone() 方法,实现 Cloneable 接口,clone 的同时也要拷贝所有的引用)
- 浅拷贝:仅复制了对象的引用,如果被拷贝的数据修改,源数据也会修改。浅拷贝通常是为了考虑性能。
1.2 关于继承
- 尽可能的不要有过深的继承树结构
- 尽量在类中少用其他类的子程序
- 通用的数据(字段)和行为(函数)尽量抽取到更高的基类
2.高质量的子程序(方法/函数)
2.1 为什么要创建子程序
- 降低复杂性 :一个方法总比一大段逻辑看起来要舒服很多
- 避免代码重复
- 支持子类化 :可以对子程序进行重写等操作
- 隐藏处理顺序:调用方无需关心里面执行的顺序
- 提高可移植性
- 简化复杂的布尔表达式
- 提高程序的性能:仅把一段代码放在一个位置更容易发现代码的效率问题;
- 提升代码的可读性:一个部分的作用抽象为一个方法,通过很好的方法名可以提升可读性
- 好的代码都是自解的
2.2 子程序的级别设计(内聚的级别)
级别排序为从强到弱
- 功能性内聚:仅执行一个操作的内聚
- 顺序性内聚:在内聚中规定了顺序行为,按照特定顺序执行,并且所有的步骤都完成后才能构成一个完整的功能
- 通信性内聚:都使用了一个数据,但是之间没有关联,就存在通信性内聚
- 瞬时性内聚:同一时间需要完成多项操作
- 过程性内聚:按照特定顺序执行
2.3 好的子程序名称
- 描述子程序能做的一切事情
- 避免使用无意义、空大、模糊的动词,如:handle、perform、output、Process等
- 不要用数字来区分子程序名称
- 很好的描述程序返回的内容
- 强势动词+对象宾语
- 准确的使用反义词
2.4 程序的长度
- 长度控制在 200 行以内,调查证明大于 200 出现问题会增多
- 平均长度在 100-150 行
2.5使用子程序参数
- 参数与调用顺序最好一致
- 如果几个子程序用了相同的参数,这些参数顺序最好一致
- 如果传递了参数,就必须要使用它
- 将状态或错误变量放在最后
- 对有关参数的接口假设进行文档化说明
- 参数限制在 7 个左右
2.6 特别注意事项
- 何时使用函数,何时使用过程
- 检查所有可能返回逻辑路径
3. 防御性编程
你永远不知道程序会做出什么事情来
重点在于防御呢些意想不到的错误
3.1 保护程序,使其免受无效输入的影响
- 检查源于外部的所有数据的值
- 检查子程序所有的输入参数值
- 决定如何处理错误的输入数据
- 在一开始就不要引入错误
3.2断言
断言允许程序在运行时自检查
- 用错误处理代码来处理预期会发生的情况,用断言处理永远不应该发生的情况
- 避免将要执行的代码放在断言中
3.3 错误处理的方法
- 返回中立值
- 日志文件记录
- 返回错误代码
- 调用错误处理子程序或对象
3.4 异常
- 使用异常来通知程序中不可以忽略的错误
- 尽可能了解代码库会抛出的异常
- 标准化使用异常方式
4. 伪代码编写过程
4.1 类和子程序构建步骤
- 创建类的常规设计
- 构建类中的每个子程序
- 设计子程序
- 检查设计
- 编写子程序
- 检查代码
- 将类作为一个整体来审查和测试
4.2 伪代码
- 使用非正式和自然语言来表示
- 避免使用目标编程语言的语法元素
- 足够低的层次上写伪代码,几乎可以直接转换成代码
4.3 使用 PPP 构建子程序
PPP(Pesudocode Programming Process) 伪代码程序过程
设计子程序
- 检查先决条件
- 定义子程序要解决的问题
- 命名子程序
- 决定如何测试子程序
- 研究标准库中可用的功能,防止功能重复
- 考虑错误处理
- 考虑效率问题
- 研究算法和数据类型(数据结构)
- 写伪代码
- 添加好子程序的头部注释
编码子程序
- 将伪代码转为程序
- 检查每一步是否需要分解
检查代码
- 用心检查错误
- 编译子程序
- 逐行执行代码
debug
- 消除子程序中的错误
第 III 部分 变量
5. 变量使用中的常规问题
5.1 简化变量声明
- 关闭隐式声明
- 声明所有变量
- 遵循命名规范 :定义一套命名规范
- 检查变量名
5.2 初始化变量指南
- 在声明时初始化每个变量
- 在靠近首次使用的地方初始化每个变量
- 尽可能地使用 final: 可以防止该变量初始化后再被赋值,并且 JVM 对 final 修饰的对象有特殊的优化处理
- 特别注意累加器和计数器
- 在构造函数中初始化该类的成员数据
- 对具名常量进行一次初始化
- 利用好编译器的警告⚠️信息
- 检查输入参数的合法性
- 使用预编程工具,初始化工作内存处
5.3 作用域
作用域或可见性,是指变量在整个程序内部的可见和可引用的程度。
- 变量引用局部化
- 尽可能地缩短变量的存活时间
- 缩小作用域的一般原则
- 在循环之前立即初始化循环中使用的变量
- 把相关的语句放到一起
- 开始的时候就严格限制可见性
5.4 数据控制类型
- 顺序控制类型
- if else
- for、while 循环类型
5.5 每个变量只有一个用途
- 每个变量限于单一用途
- 避免使用具有隐含含义的变量
- 确保使用了所有已声明的变量
6. 变量名称的威力
6.1 选择好名称的注意事项
- 长度要考虑最优 10~16 个字符
- 作用域决定变量名称
- 限定符如 total,max,sum 等最好放在最后
- 名称中常见的对仗词
- begin/end
- first/last
- locked/unlocked
- min/max
- next/previous
- old/new
- opened/closed
- visible/invisible
- source/target
- source/destinaton
- up/down
6.2 特定数据类型的命名
- 循环索引的命名 i、j、k
- 状态变量的命名
- 为状态变量取一个比 flag 更好的名称
- 临时变量的命名 temp 等
- 布尔变量的名称
- done(是否完成)
- error(发生了错误)
- found(是否找到某个值)
- success 或 ok (操作是否成功)
- 尽量使用肯定的名称而不是否定的
- 枚举类型的命名
- 通过前缀来实现对枚举的分组
- 常量的命名
- 根据常量代表的抽象实体来进行命名,而不是根据所指的数字
6.3 命名规范的威力
- 规范可以让代码结构化
- 规范可以让代码在团队学习中更易学习和理解
- 正式的代码规范是提高代码可读性的“标配”
6.4 Java 语言的非正式规范
- 命名规范
- i 和 j 是整数索引
- 常量全部大写,并用下划线分隔
- 类名和接口名中每个单词的首字母大写,包括第一个单词
- 变量名和方法名中的第一个单词小写
- 除大写的命名外,下划线不作为命名的分隔符
- 访问器方法使用 set 和 get 前缀
6.5 前缀的标准化
- UDT 用户自定义类型
- 语义前缀
6.6 创建可读的短名称
从某种意义上说,变量名称要断,这个要求是计算机发展早期遗留下来的问题
- 缩写的一般指导原则
- 标准缩写
- 删除虚词 and、or、the
- 可以在单词中间截断
- 使用重要的单词,不超过三个
- 缩写说明
- 不要删除一个字符来达到缩写
- 缩写保持一致
- 创建的名称要能够读出来
- 避免误读或发音错误的组合
- 使用同义词词典来避免命名冲突
- 可以创建标准的缩写文档来记录项目中的所有编码缩写
6.7 变量名称避坑指南
- 避免使用误导性的命名或缩写
- 避免使用含义相似的命名
- 避免使用含义不同但命名相似的变量
- 避免使用发音接近的命名
- 避免在命名中使用数字
- 避免单词拼写错误
- 避免使用易拼错的单词
- 避免使用自然语言(日常交流语句)
- 避免使用标准类型、变量和子程序的名字
- 避免使用易混淆的字符
7. 基本数据类型
7.1 一般的数字
7.2 整型
- 检查整数除法
- 检查整数溢出
7.3 字符和字符串
7.4 枚举类型
- 尽量用枚举代替布尔类型
- 枚举能够提升可读性
- 提升可靠性
- 提升易更改性
- 使用枚举检查数据的无效性
7.5 具名常量
- 具名常量类似于变量,只是分配常量后无法修改其值
- 例如 MAXIMUM_EMPLOYEES = 1000; 而不是直接使用 1000
- 让变量的含义一眼可见
- 具名常量的使用要一致
7.6 数组
- 确保索引都在数组范围内
- 尽可能地考虑使用容器而不是数组
- 检查数组边界点
- 提防索引串扰
7.7 自定义数据类型
- 以功能导向的方式来命名新创建的类型
- 用指向预定义类型名称来命名新创建的类型
- 避免预定义类型
- 不要定义容易认为是预定义类型的数据类型
8.不常见的数据类型
8.1 结构体
- 使用结构体能澄清数据关系,将一组数据捆绑在一起
8.2 全局数据
使用全局数据可能带来的问题:
- 全局数据注意无意的修改
- 注意可重入代码的问题
- 全局数据会阻碍代码的复用
- 数据初始化的顺序可能存在问题
使用全局数据的作用:
- 保存全局值
- 模拟全局常量
- 模拟枚举类型
- 简化对常用数据的使用
- 消除流浪数据
非必要,不要使用全局变量
- 所有的变量在最开始都声明为成员变量,并且赋予其可见范围,除非必要,再将其设置为全局变量
- 区分好全局变量与类变量
使用访问器子程序来替代全局数据
- Getter & Setter
- 在类中隐藏数据
- 所有的代码通过访问器子程序来操作数据
- 不要将所有的全局数据都放在一起
- 将对数据的访问保持在同一抽象层上
降低全局数据的风险
- 制定命名规范,一目了然
- 良好的注释列表
- 不使用全局数据保存中间变量
第 IV 部分 语句
9. 直线型代码的组织
9.1 顺序攸关的语句
有些代码逻辑必须有顺序的前后之分
- 组织代码,是依赖关系显而易见
- 子程序的命名要揭示依赖关系
- 使用子程序参数来解释依赖关系
- 用注释标注不明确的依赖关系
9.2 顺序无关的语句
有些代码的逻辑没有顺序的前后之分
- 自上而下的组织代码
- 把相关的语句分为一组
10. 使用条件语句
10.1 if 语句
- 普通的 if else 语句
- 先写代码的正常路径,再写不寻常的情况
- 基于对相等性的测试正确分支
- 将正常情况放在 if 中而非 else 之后
- if 语句后一定要跟一个有意义的语句
- 尽可能地测试 if 和 else 的正确性
- 检查 if 和 else 子句是否颠倒
- 用布尔函数调用简化方法
10.2 case 语句
选择有效的 case 顺序
- 选择多种方式组织 case 语句针对不同的情况
- 按照字母或数字顺序进行排序
- 将正常情况放在最前面
- 按照频率排列
case 的使用技巧
- 不要为了使用 case 语句而虚构变量
- 只用 default 子句来检测正当的默认情况
- 使用 default 子句来检测错误
- 避免直通到 case 语句的结尾,明确编码每个 case 的结束
11. 控制循环(迭代控制结构)
11.1 选择循环类型
何时使用 while 循环
如果事先不知道要循环多少次,就用 while 把
- 在开始出测试
- 在结尾处测试
何时使用 for 循环
- 已知循环次数
- 不需要内部控制的简单活动
11.2 控制循环
进入循环
- 尽量简化内容
- 把控制留在循环外部
- 循环内部的逻辑当做一个子程序
- 将初始化代码放在循环之前
- 将 while 用于无限循环
- 首选 for 循环
处理循环体
- 避免空循环
- 只在循环的开头或末尾进行循环内务处理
- 每个循环只执行一个功能
退出循环
- 确保循环会终止
- 使循环结束条件显而易见
- 避免在循环内部修改索引
- 避免依赖于循环索引终值的代码,不要在循环结束后使用循环索引的值,索引只用于控制循环次数
- 考虑使用安全计数器
提前退出循环
- 在 while 循环中使用 break
- 警惕其中散布大量的 break 循环
- 带标签的 break
- 慎用 break 和 continue
检查端点
- 循环的关注点通常要有三种情况
- 第一种情况
- 随机选择的中间情况
- 最后一种情况
- 在脑海中模拟和手动计算
使用循环变量
12. 常规控制问题
12.1 布尔表达式
用 true 和 false 进行布尔测试,而非 1 和 0
- 将布尔值隐式地和 true 与 false 进行比较
简化复杂的表达式
- 将复杂的拆分为简单
- 将复杂的表达式转移到布尔函数中
构建肯定形式的布尔表达式
- 在 if 语句中将否定判断转为肯定并将 if 字句与 else 字句翻转
- 用德摩根定理简化否定形式的布尔判断
用括号澄清布尔表达式
- 使用简单的技术技巧使圆括号对称
- 当遇到起始括号 " ( " 计数器 +1,遇到括号 " ) " 计数器 -1,最终结尾为 0 则没问题
- 用圆括号完整描述逻辑表达式
理解布尔表达式如何求值
按照数轴顺序来写数值表达式
- 符号尽可能的朝向一个方向
和 0 进行比较时的指导原则
- 隐式比较
- 将数字与 0 比较
- 对象类型和 null 进行比较
12.2 复合语句(语句块)
12.3 空语句
- 避免过多嵌套
12.4 驾驭深层嵌套
- 将嵌套 if 语句转换为 case 语句
- 将深层嵌套的代码整理为子程序
- 可以使用工厂模式来使得过程更面向对象
12.5 编程基础: 结构化编程
结构化变成的核心思想:单进单出的控制结构
break、continue、throw、catch、return
结构化编程的三个组成部分
- 顺序
- 即正常的语序结构
- 选择
- if switch-case 语句
- 迭代(循环)
12.6 控制结构与复杂度
- 尽可能的降低代码复杂度
- 如何度量复杂度
- 如何衡量复杂度
第 V 部分 代码部分
13. 软件质量概述
13.1 软件质量的特性
外部特性:
- 正确性
- 易用性
- 效率
- 可靠性
- 完整性
- 适应性
- 精确性
- 健壮性
内部特性:
- 可维护性
- 灵活性
- 可移植性
- 可重用性
- 可读性
- 可测试性
- 可理解性