代码维护原则 博客分类: 编程习惯 编码习惯
程序员文章站
2024-02-05 17:49:46
...
《代码不朽》_编写客维护软件的10大原则
电子工业出版社
主要内容及体会记录如下:
一、编写短小的代码单元
1.原则
2.
代码单元是独立维护和执行的最小代码集合。在Java中,代码单元指的是方法或者构造函数。代码单元总是作为一个整体执行。
代码单元是可以被重用及测试的最小单元。
3.目的
3.1 短小的代码单元易于测试
单一原则,一个代码单元只做一件事情。相比较一个大段的代码单元,更容易测试。
3.2 短小的代码单元易于分析
条例清晰明了。不必添加多余的注释在各行代码上进行说明或备注。
3.3 短小的代码单元易于重用
代码短小,逻辑就足够简单,重复调用的可能性就很高。
4.使用
4.1 添加新的功能时
可以单独建立一个方法单元,将实现写在单元内,而将方法调用的位置放到指定的位置;
而不是直接在调用的方法内写功能。
好处:
如果有一天被告知此功能暂时不需要上线,避免注释掉大段的代码;只需要把调用该方法的位置注释掉即可。
单一原则,不互相干涉。如果几个方法都对同一个对象进行修改,就会出现各种问题;避免同名变量
4.2 代码书写完毕后的重构
4.2.1 提取方法
将几行实现一个功能的代码,抽取出来,作为一个独立的代码单元;
4.2.2 在方法内部使用对象
当方法的参数数量多于4个时,将参数抽象成一个对象
既减少传入参数的数量(避免类型、位置对应不上的问题)也减少方法内部的处理
二、编写简单的代码单元
1.原则
2.代码测试
功能覆盖率,代码编写完毕后要进行初步的测试,测试的原则就是要覆盖每个分支
减少分支数量,就是在一定程度上减少测试成本及时间
3.使用
3.1 分支包括
3.2 减少else
使用return ,
比如:判断某集合不空,就进行某种处理。
if(CollectionUtils.isEmpty(list))
return ;
空了就不处理,也就不需要else
能够减少好多代码且条例清晰
3.3 抽象对象
比如:多个分支,多个参数处理、switch 的 各个case 的处理逻辑基本相同且参数很多
将参数抽象成对象,然后对此对象进行处理
三、不写重复代码
1.原则
2.
复制代码似乎能够快速完成目标,既然代码都有了,何必要再写一遍?复制会导致代码重复出现,而冲农夫代码就是问题的根本所在。
3.目的
3.1 重复代码更加难以分析
3.2 代码重复更加难以修改
4.使用
提取方法,在代码开发过程中或开发完毕后,尝试将各个功能拆分,找出共同点
四、保持代码单元的接口简单
1.原则
2.目的
2.1 短接口更易于理解和重用
2.2 短接口的方法更易于修改
五、分离模块之间的关注点
1.原则
2.针对于如何提高系统中单个代码单元的可维护性
3.目的
3.1 小型、松耦合的模块允许开发人员独立进行工作
3.2 小型、松耦合的模块降低了浏览代码库的难度
3.3 小型、松耦合的模块避免了让新人感到手足无措
4.使用
4.1 根据不同关注点拆分类
举例:
一个用户接口
第一次开发:用户信息的增加、删除、修改、查询
第二次开发:用户信息通知接口
第三次开发:用户锁定接口
完全放在一个接口文件中时,代码量过大,且不易维护
建议:按照不同的关注点,拆分成多个接口
4.2 隐藏接口背后的特定实现
举例:
普通相机类:基本功能
高级相机类:如果为了兼容高级相机提供的特定功能,而扩充普通相机方法中的功能,就会显得代码过多
建议:抽象出接口,普通相机与高级相机分别实现接口,高级相机中添加特定的方法
调用接口时,利用多态的原理,保证调用的对象不同
六、架构组件松耦合
1.原则
2.举例
层与层之间设计中给的单向依赖
用户接口 --》 服务层 --》 业务逻辑层 --》 数据抽象层 --》 数据库层
如果相互之间调用混乱了,对于后续维护就不方便
如 用户接口直接调用 数据抽象层的接口
3.目的
3.1 低组件以来允许独立的维护
3.2 低组件依赖可以分离维护职责
4.使用
4.1 限制作为组件接口的模块的大小
4.2 在更高的抽象层次上来定义组件接口,这是为了限制跨越组件边界的请求类型,避免请求了解太多的实现细节
4.3 避免使用透传调用,即接收传入调用,又同时代理给其他组件。那么此时接口要同时为两个不同的组件进行服务,违反单一职责原则。
七、保持架构组件之间的平衡
1.原则
2.目的
2.1 好的组件平衡能让查找和分析代码变得更加容易
2.2 好的组件平衡能隔离维护所带来的影响
2.3 好的组件平衡能够分离维护职责
3.使用
3.1 顶层系统组件个数在理想状态下应为9个,通常来说位于 6 到 12 个 之间
3.2 各个组件的代码量应该大致相同
八、保持小规模代码库
1.原则
2.解释
代码库时存储在一个仓库中的所有源代码的集合,可以独力地进行编译和部署,并且由一个团队进行维护。
3.目的
3.1 以大型代码库为目标的项目更容易失败
3.2 大型代码库更加难以维护
3.3 大型系统会出现更密集的缺陷
4.使用
4.1 功能层面的方法
4.1.1 控制需求蔓延
项目开发过程中,需求不断增加,不断的迭代,有些需求看上去高大上,其实,实际应用中的效果不是很好,或者说反而会拖累当前的系统,那这样的需求的上线价值与以后的系统维护成本之间要做好权衡。
4.1.2 功能标准化
将功能实现代码标准化;底层实现的方式基本相同,可重用性的代码会增加
4.2 技术层面的方法
4.2.1 不要复制粘贴代码
复制粘贴,就说明代码可单独抽出一个方法;同时如果出现问题,同样的错误不用修改很多次;
4.2.2 重构已有代码
增加可维护性,减小代码体积
4.2.3 使用第三方库和框架
九、自动化开发部署和测试
1.原则
2.目的
2.1 自动化测试让测试可以重复
2.2 自动化测试会让开发更有效率
2.3 自动化测试让代码行为可预测
2.4 测试是对被测试代码的一种说明文档
2.5 编写测试能让你编写更好的代码
测试驱动开发,若一个方法内部实现了多个功能,那么测试用例就要考虑多种分支可能,所以为了方便测试,就会考虑如果将方法按照功能点进性拆分
3.使用
3.1 Junit 测试入门
3.2 编写良好单元测试的基本原则
3.2.1 正常情况和特殊情况都要进行测试
3.2.2 像维护非测试代码一样维护测试代码
3.2.3 编写独立的测试,他们的输出应该只反映被测试主体的行为
3.3 测试覆盖率来确定是否有足够的测试用例
覆盖率:指的是单元测试中执行的代码行数占代码库总行数的百分比。
通常80%以上。
十、编写简洁的代码
1.原则
2.使用
2.1 不要编写单元级别的代码坏味道
忌:过长的代码单元;复杂的代码单元;长接口的代码单元
2.2 不要编写不好的注释
取消代码行内的注释;
代码远离注释;
不要为了注释而去写注释,每行代码上都有一行注释;而是必要的位置才去写注释
2.3 不要注释代码
版本控制上会留有一份旧的代码,所以不要留有注释的代码;
注释的代码对于一个新人来说就是一个具有挑战性的问题:
为什么前面的那个哥们把它注释掉了,是一个坑还是一个雷?我要不要试着修复它,万一出问题了怎么办?要不把它留着,留给后面的接班人?最好还是不动了,免得惹祸上身。
2.4 不要保留费期代码
方法中无法到达的代码
无用的私有方法
注释中的代码:避免在注释中使用代码
2.5 不要使用过长的标识符名称
避免:
使用表示多个职责的:generateConsoleAnnotataionScriptAndStylesheet
包含过多技术术语:GlobalProjectNamingStrategyConfigure
2.6 不要使用魔术常量
代码中不要出现数字,所有的数字均可用常量代替
2.7 不要使用为正确的处理的异常
2.7.1 捕获一切异常。
2.7.2 捕获特定异常。
在特定的调用地方,要对相应的异常进行指定捕获。
2.7.3 在展示给终端用户之前,先将特定的异常系想你转换成通用的信息
十一、后续
将原则变成实践
低层级(代码单元)原则要优于高层级(组件)原则
电子工业出版社
主要内容及体会记录如下:
一、编写短小的代码单元
1.原则
- 代码单元的长度应该限制在15行代码以内
- 因此首先不要编写超过15行代码的单元,或者将单元分解成多个更短的单元,直到每个单元都不超过15行代码
- 该原则能提高可维护性的原因在于,短小的代码单元易于理解、测试及重用
2.
代码单元是独立维护和执行的最小代码集合。在Java中,代码单元指的是方法或者构造函数。代码单元总是作为一个整体执行。
代码单元是可以被重用及测试的最小单元。
3.目的
3.1 短小的代码单元易于测试
单一原则,一个代码单元只做一件事情。相比较一个大段的代码单元,更容易测试。
3.2 短小的代码单元易于分析
条例清晰明了。不必添加多余的注释在各行代码上进行说明或备注。
3.3 短小的代码单元易于重用
代码短小,逻辑就足够简单,重复调用的可能性就很高。
4.使用
4.1 添加新的功能时
可以单独建立一个方法单元,将实现写在单元内,而将方法调用的位置放到指定的位置;
而不是直接在调用的方法内写功能。
好处:
如果有一天被告知此功能暂时不需要上线,避免注释掉大段的代码;只需要把调用该方法的位置注释掉即可。
单一原则,不互相干涉。如果几个方法都对同一个对象进行修改,就会出现各种问题;避免同名变量
4.2 代码书写完毕后的重构
4.2.1 提取方法
将几行实现一个功能的代码,抽取出来,作为一个独立的代码单元;
4.2.2 在方法内部使用对象
当方法的参数数量多于4个时,将参数抽象成一个对象
既减少传入参数的数量(避免类型、位置对应不上的问题)也减少方法内部的处理
二、编写简单的代码单元
1.原则
- 限制每个代码单元分支点的数量不超过4个
- 将复杂的代码单元拆分成多个简单的代码单元,避免多个复杂的单元在一起
- 分支点越少,代码单元越容易被修改和测试
2.代码测试
功能覆盖率,代码编写完毕后要进行初步的测试,测试的原则就是要覆盖每个分支
减少分支数量,就是在一定程度上减少测试成本及时间
3.使用
3.1 分支包括
- if else
- switch case default
- a > b ? a : b
- && ||
- for while
- try catch finally
3.2 减少else
使用return ,
比如:判断某集合不空,就进行某种处理。
if(CollectionUtils.isEmpty(list))
return ;
空了就不处理,也就不需要else
能够减少好多代码且条例清晰
3.3 抽象对象
比如:多个分支,多个参数处理、switch 的 各个case 的处理逻辑基本相同且参数很多
将参数抽象成对象,然后对此对象进行处理
三、不写重复代码
1.原则
- 不要复制代码
- 编写可重用的、通用的代码、并且或者调用已有的代码
- 如果复制代码,就需要在多个地方修改Bug
2.
复制代码似乎能够快速完成目标,既然代码都有了,何必要再写一遍?复制会导致代码重复出现,而冲农夫代码就是问题的根本所在。
3.目的
3.1 重复代码更加难以分析
3.2 代码重复更加难以修改
4.使用
提取方法,在代码开发过程中或开发完毕后,尝试将各个功能拆分,找出共同点
四、保持代码单元的接口简单
1.原则
- 限制每个代码单元的参数不能超过4个
- 将多个参数提取成对象
- 较少的参数可以让代码单元更容易理解和重用
2.目的
2.1 短接口更易于理解和重用
2.2 短接口的方法更易于修改
五、分离模块之间的关注点
1.原则
- 避免形成大型模块,以便能达到模块之间的松耦合
- 将不同职责分给不同的模块,并且隐藏接口内部的实现细节
- 该原则能提高可维护性的原因在于,相比起紧耦合的代码库来说,对松耦合代码库的修改更容易监督和执行
2.针对于如何提高系统中单个代码单元的可维护性
3.目的
3.1 小型、松耦合的模块允许开发人员独立进行工作
3.2 小型、松耦合的模块降低了浏览代码库的难度
3.3 小型、松耦合的模块避免了让新人感到手足无措
4.使用
4.1 根据不同关注点拆分类
举例:
一个用户接口
第一次开发:用户信息的增加、删除、修改、查询
第二次开发:用户信息通知接口
第三次开发:用户锁定接口
完全放在一个接口文件中时,代码量过大,且不易维护
建议:按照不同的关注点,拆分成多个接口
4.2 隐藏接口背后的特定实现
举例:
普通相机类:基本功能
高级相机类:如果为了兼容高级相机提供的特定功能,而扩充普通相机方法中的功能,就会显得代码过多
建议:抽象出接口,普通相机与高级相机分别实现接口,高级相机中添加特定的方法
调用接口时,利用多态的原理,保证调用的对象不同
六、架构组件松耦合
1.原则
- 顶层组件之间应该做到松耦合
- 尽可能减少当前模块中需要暴露给(例如,被调用)其他组件中模块的相关代码
- 独立的组件可以单独进行维护
2.举例
层与层之间设计中给的单向依赖
用户接口 --》 服务层 --》 业务逻辑层 --》 数据抽象层 --》 数据库层
如果相互之间调用混乱了,对于后续维护就不方便
如 用户接口直接调用 数据抽象层的接口
3.目的
3.1 低组件以来允许独立的维护
3.2 低组件依赖可以分离维护职责
4.使用
4.1 限制作为组件接口的模块的大小
4.2 在更高的抽象层次上来定义组件接口,这是为了限制跨越组件边界的请求类型,避免请求了解太多的实现细节
4.3 避免使用透传调用,即接收传入调用,又同时代理给其他组件。那么此时接口要同时为两个不同的组件进行服务,违反单一职责原则。
七、保持架构组件之间的平衡
1.原则
- 平衡代码中顶层组件的数量和体积
- 保持源代码中组件的数量接近于9,并且这些组件的体积基本一致
- 该原则能提高可维护性的原因在于,平衡的组件可以帮助定位代码,并且允许独立对组件进行维护
2.目的
2.1 好的组件平衡能让查找和分析代码变得更加容易
2.2 好的组件平衡能隔离维护所带来的影响
2.3 好的组件平衡能够分离维护职责
3.使用
3.1 顶层系统组件个数在理想状态下应为9个,通常来说位于 6 到 12 个 之间
3.2 各个组件的代码量应该大致相同
八、保持小规模代码库
1.原则
- 保持代码库规模尽可能小
- 控制代码库增长,并主动减少代码库的体积
- 拥有小型的代码、项目和团队是成功的一个因素
2.解释
代码库时存储在一个仓库中的所有源代码的集合,可以独力地进行编译和部署,并且由一个团队进行维护。
3.目的
3.1 以大型代码库为目标的项目更容易失败
3.2 大型代码库更加难以维护
3.3 大型系统会出现更密集的缺陷
4.使用
4.1 功能层面的方法
4.1.1 控制需求蔓延
项目开发过程中,需求不断增加,不断的迭代,有些需求看上去高大上,其实,实际应用中的效果不是很好,或者说反而会拖累当前的系统,那这样的需求的上线价值与以后的系统维护成本之间要做好权衡。
4.1.2 功能标准化
将功能实现代码标准化;底层实现的方式基本相同,可重用性的代码会增加
4.2 技术层面的方法
4.2.1 不要复制粘贴代码
复制粘贴,就说明代码可单独抽出一个方法;同时如果出现问题,同样的错误不用修改很多次;
4.2.2 重构已有代码
增加可维护性,减小代码体积
4.2.3 使用第三方库和框架
九、自动化开发部署和测试
1.原则
- 对你的代码进行自动化测试
- 通过使用测试框架来编写自动化测试
- 自动化测试让开发过程可预测并且能够降低风险
2.目的
2.1 自动化测试让测试可以重复
2.2 自动化测试会让开发更有效率
2.3 自动化测试让代码行为可预测
2.4 测试是对被测试代码的一种说明文档
2.5 编写测试能让你编写更好的代码
测试驱动开发,若一个方法内部实现了多个功能,那么测试用例就要考虑多种分支可能,所以为了方便测试,就会考虑如果将方法按照功能点进性拆分
3.使用
3.1 Junit 测试入门
3.2 编写良好单元测试的基本原则
3.2.1 正常情况和特殊情况都要进行测试
3.2.2 像维护非测试代码一样维护测试代码
3.2.3 编写独立的测试,他们的输出应该只反映被测试主体的行为
3.3 测试覆盖率来确定是否有足够的测试用例
覆盖率:指的是单元测试中执行的代码行数占代码库总行数的百分比。
通常80%以上。
十、编写简洁的代码
1.原则
- 编写简洁的代码
- 不应该在完成开发工作后留下代码坏味道
- 简洁的代码是可维护的代码
2.使用
2.1 不要编写单元级别的代码坏味道
忌:过长的代码单元;复杂的代码单元;长接口的代码单元
2.2 不要编写不好的注释
取消代码行内的注释;
代码远离注释;
不要为了注释而去写注释,每行代码上都有一行注释;而是必要的位置才去写注释
2.3 不要注释代码
版本控制上会留有一份旧的代码,所以不要留有注释的代码;
注释的代码对于一个新人来说就是一个具有挑战性的问题:
为什么前面的那个哥们把它注释掉了,是一个坑还是一个雷?我要不要试着修复它,万一出问题了怎么办?要不把它留着,留给后面的接班人?最好还是不动了,免得惹祸上身。
2.4 不要保留费期代码
方法中无法到达的代码
无用的私有方法
注释中的代码:避免在注释中使用代码
2.5 不要使用过长的标识符名称
避免:
使用表示多个职责的:generateConsoleAnnotataionScriptAndStylesheet
包含过多技术术语:GlobalProjectNamingStrategyConfigure
2.6 不要使用魔术常量
代码中不要出现数字,所有的数字均可用常量代替
2.7 不要使用为正确的处理的异常
2.7.1 捕获一切异常。
2.7.2 捕获特定异常。
在特定的调用地方,要对相应的异常进行指定捕获。
2.7.3 在展示给终端用户之前,先将特定的异常系想你转换成通用的信息
十一、后续
将原则变成实践
低层级(代码单元)原则要优于高层级(组件)原则