读代码整洁之路及实施重构总结

简介: ### 前言 在开发代码的时候,经常觉得代码有一种坏味道,有一种迫切重构的想法,但是再一次的仔细阅读代码,却发现为了实现业务需求,代码就应该是现有这种模式的写法。之前也有在网上看一些代码重构的文章,但是直到看完《代码整洁之路》这本书,才算是有了一些明确代码重构的方法和思路。在这次对生产代码的一些重构实践中,对文章中提到并亲身体会过的部分小点,有了一些认识和体会,记录如下。参考博客中的第一篇,

前言

在开发代码的时候,经常觉得代码有一种坏味道,有一种迫切重构的想法,但是再一次的仔细阅读代码,却发现为了实现业务需求,代码就应该是现有这种模式的写法。之前也有在网上看一些代码重构的文章,但是直到看完《代码整洁之路》这本书,才算是有了一些明确代码重构的方法和思路。在这次对生产代码的一些重构实践中,对文章中提到并亲身体会过的部分小点,有了一些认识和体会,记录如下。
参考博客中的第一篇,重构十六字心法是下述小点的前提:旧的不变,新的创建,一步切换,旧的再见;

过长的函数及过长的函数参数列表

过长的函数参数列表导致的函数一定是会过长的,因为会存在参数的校验和参数的进一步加工处理,这些是可以将参数进行封装到数据结构以后提前进行处理的,同时也能大大减少函数调用长度,让阅读者更加清楚的聚焦函数内部逻辑处理。
过长的函数处理体是很难让人一下理清处理逻辑,它需要开发者一行一行的阅读完整个函数才能有大概的了解,它代表着函数内部的代码抽象层级不统一,这个时候需要我们对其进行分治,将一些聚合在一起的逻辑封装为一个函数,这样子我们一个大函数里面只有几个子函数调用,通过子函数名我们就能知道这个函数的大概处理逻辑。

过长的判断条件

当我们在if 判断条件中,使用 || &&连接的判断语句超过两个,我觉得就可以被认定为过长的判断条件。这种写法在代码阅读的时候,要求阅读者能够记住判断条件中使用的变量代表中的意义,有一定的记忆成本。另外这种过长的判断条件,还是有很大一部分的重复使用的情况,此时将它封装为一个单独函数,并起名一个有意义的名字,方便后续的阅读和维护。

变量的有效命名

变量的有效命名和函数的有效命名是一样的,一个可以望文生义的名字可以节省维护代码很大一部分时间,书中对此点进行了反复的强调。一个有意义的名字不是立即取出来的,当我们发现有更适合的命名时,一定要毫不犹豫的更改掉。

函数中的输出参数

类似于如下代码,函数在处理的时候,直接从params获取参数处理后并将结果put到Map中去。

void process(Map<String, Object> params);

在我们的生产代码中有很多这种的函数处理,当我去阅读的时候,给我带来很大的认知困扰。这种函数会某个代码片段中被调用一下,并没有返回结果,这个行为是非常容易迷惑人的,因为参数被自然的看做是函数的输入。如果该函数没有返回结果,对于这种的代码,我认为是可以随意调整的,但是随后又会存在一个函数依赖此函数的处理结果,而这些函数的命名或者参数中并没有体现出依赖关系,很容易出现严重的BUG。
我们应该极力避免输出参数的使用,即使是简单的将输入参数作为结果返回也比返回Void好很多。

函数名和函数实际行为的匹配,避免副作用

正如变量的有效命名所说,函数名必须有意义,但是函数行为也必须和函数名匹配,不能导致其它的副作用。check、is等这些开头的函数不应该对对象的状态进行更改。

垃圾的代码上增加注释

很多时候,我会在晦涩难懂的代码上增加注释以增强自己的理解,但往往下一次阅读的时候还是没有办法一目了然,仍然需要一行行从头阅读才能大概了解其意思。更加困难的一个事情,如果这个代码实现具备一定的业务背景,在交接的时候并没有提及的话,后来者很难阅读出其意图,更加别提去维护了。
如果代码实现的不好,不要增加注释了,重构它吧。

循规蹈矩的JavaDoc注释

如果是仅仅为了避免ide的提示,给函数增加上了循规蹈矩的Java doc注释,那还是干掉它吧。多余的注释只会分散开发者的注意力,另外不正确注释比不好的代码实现更加容易让人误解。没有注释的情况下,开发者使用的时候会查看实现,但是有注释的情况下,很可能会以注释内容为准,从而导致错误的行为。

统一变量命名概念

标识同一个概念意义的变量,命名一定要尽量统一。比如说分页的页码标识,pageNo、currentPage、pageNumber等混用情况。如果是其它更加复杂带有业务属性的变量,同一个概念使用不同的变量标识会带来更加严重的认知困难。

尽量避免返回null值

Java中的NullPointException是我们在编程希望极力避免掉的,因此我们在调用某个可能返回null的函数时,我们都需要增加判断条件,当返回值不为null才进行进一步的处理,所有函数调用初都不可避免的带上了这个判断条件处理,影响代码的阅读。
从另外一个角度来说,当函数提供者实现时,发现有调用方获取一个不存在的值,返回null也是一种可以理解的行为,在我的实现中就经常出现该类代码。之前我一直没有思考过不返回null,直接抛异常的处理方式,但这次去回看某些函数实现,正确的行为就应该是当结果为null时,抛出业务异常。
这一重构点我目前觉得大部分场景都难以避免,但是在后续实现业务逻辑的时候,遇上返回null的场景,一定要思考下抛出异常的处理逻辑,这种实现能够确保函数调用方一定能够拿到有值的结果,从而避免的重复、无味的非空判断。

火车头事件,代码的封装

当A依赖B,B依赖C时,如果A实现的一个逻辑处理,实际依赖于C的处理,我们有时会让A使用B拿到C以后,然后A直接调用C进行处理,这种行为被作者称为火车头事件。A直接调用C的行为,打破了B的封装,让A直接感受到了B的内部逻辑。
正确的行为应该是B封装C的行为,A只会感知到B,A调用B后,由B再来调用C。这个场景在我的代码中经常出现,当A、B、C都是自己编写的情况下,这个打破封装的行为自己是完全意识不到的,需要我们回过头来审视自己的代码,重新思考代码的正确行为。

重构时的单测

程序员都对自己的代码非常自信,特别是大部分的业务逻辑都是简单的增删改查的情况下,我自己单测是写得比较少的。但是错误往往出现于细微之处,特别在大部分正确的情况下,细小处的错误行为是很难寻找的。书中作者在讲述重构样例时,花了很大一番功夫来描述单测。如何让单测能够覆盖函数的所有行为是对程序员的一个考验,写好单测是重构的先决条件,不然往往会让自己事倍功半。
一定要有单测,TDD!
一定要有单测,TDD!
一定要有单测,TDD!

避免重复

Do not repeat youself! 这个原则被大部分的重构指引提及,而现代的ide能够智能识别到重复代码,另外当你发现你自己写了似曾相似的处理代码,那么你也在重复自己了,这种时候就应该提醒自己回头审视自己的代码了,一定是我们的处理逻辑存在问题才会导致这种的重复。当发现重复时,多回头看看!


参考博客

https://www.zhihu.com/question/19574943
https://martinfowler.com/bliki/BranchByAbstraction.html
http://insights.thoughtworkers.org/principles-of-refactoring/
https://www.infoq.cn/article/clean-code-refactor
http://insights.thoughtworkers.org/service-split-and-architecture-evolution/

目录
相关文章
|
测试技术 数据库 安全
带你读《C++代码整洁之道:C++17 可持续软件开发模式实践》之二:构建安全体系
如果想用C++语言编写出易维护的、扩展性良好的以及生命力强的软件,那么,对于所有的软件开发人员、软件设计人员、对现代C++代码感兴趣或想降低开发成本的项目领导者来说,本书都是必需品。如果你想自学编写整洁的C++代码,那么本书也是你需要的。本书旨在通过一些示例帮助各个技术层次的开发人员编写出易懂的、灵活的、可维护的和高效的C++代码。即使你是一名资深的开发工程师,在本书中也可以找到有价值的知识点。
|
7月前
|
存储 NoSQL 关系型数据库
重构之道:揭秘大规模系统重构的经验与挑战
重构之道:揭秘大规模系统重构的经验与挑战
164 2
|
11月前
|
设计模式 程序员 开发者
程序员在开发中必经之路:重构代码
众所周知,程序员在开发过程中接手前人代码,或者接手公司外购项目的代码等情况的时候,都有想要重构代码的冲动,与其这样说,不如说程序员只要是接手不是自己亲自写的代码都想重构!俗话说得好:一百个程序员脑中有一百个编程思维,不同程序员就算是开发相同功能的程序,一定会有不同的实现方式,而且代码格式和实现方式也肯定是不一样的,这样就给程序的代码重构留下了伏笔。
140 1
|
设计模式
重构·改善既有代码的设计.04之重构手法(下)完结
重构改善既有代码的设计完结篇,汇总了全部的重构手法。看看哪些手法对你的项目能有所帮助…
7357 2
重构·改善既有代码的设计.04之重构手法(下)完结
|
敏捷开发 Devops 测试技术
深聊测开领域之:一文搞懂什么是敏捷测试,如何做敏捷测试,建议先收藏再学习。
深聊测开领域之:一文搞懂什么是敏捷测试,如何做敏捷测试,建议先收藏再学习。
477 0
深聊测开领域之:一文搞懂什么是敏捷测试,如何做敏捷测试,建议先收藏再学习。
|
SQL 前端开发 安全
【测开方法论】如何简单的对测试平台进行底层重构 ?
【测开方法论】如何简单的对测试平台进行底层重构 ?
|
存储 人工智能 物联网
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路(四)
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路(四)
|
机器学习/深度学习 传感器 边缘计算
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路(三)
《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路
|
存储 传感器 人工智能
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路(二)
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 	6G 总体设计思路(二)
|
传感器 人工智能 边缘计算
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路(一)
《6G需求与愿景》第三章6G 设计思路与愿景3.1 6G 总体设计思路
带你读《6G需求与愿景》第三章6G 设计思路与愿景3.1 	6G 总体设计思路(一)

热门文章

最新文章