好的重构方法才能摆脱“屎山”

大家好,我是Z哥。

最近在整理一些项目,所以相关的文章写的多了些。之前的相关文章有《聊聊单元测试》,感兴趣的话可以点击文末链接去阅读。

这次整理项目的时候,做了比较多的codereview和重构。好久没做这么高强度了重构了,所以对重构这件事有了新的思考和理解。

突然发现叫我们程序员“码农”还挺形象的,因为写代码和种田很像,想有个好收成,就要好好管理代码,让它们井井有条。

吴军老师在《文明之光》里讲到一个「垄耕种植法」,它由中国人发明,后发扬到全球,影响了全世界的粮食生产。据说欧洲人民以前是把种子随意地撒在地里,任其自由生长,结果收成很低,如果种下20斤,大概只能收获60斤左右粮食。而中国早在先秦时期亩产最少都在240斤以上,最新的数据是今年11月初袁隆平的杂交水稻,早晚稻加起来达到3000斤,这都得益于「垄耕种植法」。

所以,当你看到那些被随意“播种”的糟糕代码,是改,还是不改?改吧,花时间;不改吧,就像上面的欧洲人民。

其实很多人对「重构」的理解还有些误区。「重构」仅仅是所谓的优化代码吗?并不是。

Martin Fowler大神在他的《重构》一书中对「重构」的定义就非常准确。

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 

重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

《重构》Martin Fowler

所以,重构不仅仅是修改代码,是对软件结构的调整,修改代码只是其中的一个手段而已。

那么具体应该怎么做呢?在这之前,需要考虑清楚以下几个问题。

/01  什么时候重构?/

很多理想主义者认为的理想情况自然是随时发现坏代码就重构。但是这里存在两个问题:

  • 有很多客观因素导致了就算发现了坏代码,也不一定有充足的时间来修改。

  • 不同的人代码水平不一,一个人看起来没毛病的代码,可能在其他人眼里却是需要重构的代码。

所以我们需要几个更加客观的外部标准,Z哥建议你可以从以下三个方面来观察,如果发现了类似的现象,说明它在给你发出需要重构的信号。

  • 增加新功能的时候。此时,你发现既有的代码无法让你轻松添加新特性,需要花30%以上的时间做一些重复性的编码。

  • 修bug的时候。此时,你发现排查逻辑非常困难,需要花费半小时甚至是数小时才能搞清楚代码在处理什么。

  • 当从优秀的程序员口中说出觉得某段的代码写的太shit。(普通人的shit可能是到了无可救药的地步了,优秀的人觉得shit大概率还有救,否则他估计就离职不干了~)

/02  怎么重构?/

为了保证重构的质量,在你重构的过程中,一定要关注以下4个关键点:

  • 始终工作。这其实也是《重构》中提到的理念,“「不改变软件可观察行为」的前提下”。如果你的重构需要大刀阔斧的进行,会导致很长一段时间软件无法运行起来,那么这就不是一个好的重构方式,因为在很长一段时间里你都不知道它会不会走向万劫不复的“深渊”。(有没有遇到过越重构越糟糕的经历?欢迎分享你的吐槽)

  • 持续集成:很多人会将重构单独作为一个版本去做,其实这样会有一个新的问题,就是后续合版本是个痛苦的事情,而且容易出错。所以,应该就在平时的版本里去做重构,让每一次重构都可以跟着CI/CD持续进行。

  • 随时中止:不管你的重构代码写到什么阶段,只要编译不报错就可以随时中止,立马切换到功能开发。只有达到这个标准,你的Leader才不会对重构又爱又恨。

  • 过程可逆:假如我的重构出现了重大缺陷,它是可以快速回退的,而不需要从之前的版本当中去捞代码。毕竟,谁都无法100%保证每一次的重构都是perfect的。

可以回想一下,你之前做过的重构是否都符合了以上的这些要求?反正Z哥最近做的重构是不符合的,所以感觉很累很痛苦~

具体的重构工作其实说起来很简单,因为一段代码无非就是「输入参数」、「输出参数」、「方法体」3个东西,重构也自然以这几个地方展开。

/01  输入参数/

对于输入参数的重构,主要关注在参数的个数上。那些优秀的开源项目里,你几乎看不到参数很多的方法。

因为过多的参数个数,不但不容易理解,而且你在写调用这个方法的代码的时候也会很头疼,时不时要数一下这是第几个参数,对应的参数说明是什么。

有一些工具推荐的默认参数最大长度是7个(如SonarQube)。如果你没有更好的定义和理解,那么不妨以“7”这个标准来执行。

/02  输出参数/

输出参数只有一个,能够出乱子的空间也很小,所以一般来说不需要怎么优化。

唯一值得提醒的两点是:

  1. 参数类型尽量用强类型。弱类型的返回值虽然让你的Function向后兼容性很好,但是也带来了很多无法在编译期间被发现的问题。

  2. 不返回不需要的参数。添加更多参数在最初肯定是为了“跑在业务前面”,但这份好心往往最终带来的是更多“意料之外的耦合”,导致后续的重构成本大增。

/03  方法体/

对于方法体的重构是花费时间最多的地方,具体的方式方法也很多。但是我建议你一定要坚持一个核心要点,我将它称为「NRD重构法」,这3个字母分别表示:New、Replace、Delete。也就是说,做重构的时候不要直接在原来的方法体里改,重新建一个新的方法,然后等单测跑通之后再替换掉老方法,最后再把老方法删除。

只要做到这点,要满足前面提到的4个关键点,就没那么困难了。

具体的重构内容自然是以减少复杂度为核心思路去做。衡量代码复杂度有一个概念叫「圈复杂度」(也叫「循环复杂度」),在1976年由Thomas J. McCabe, Sr. 提出。现在有不少工具有统计这个指标。

复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。

复杂度大的代码往往伴随着大量的if/switch/for/foreach/try...catch/while等等。每一次试用都会让「圈复杂度」+1,并且其中的条件判断越多,增加的越快。

所以,常见的重构方式大多以降低代码的圈复杂度为主。比如,

  • 将相同逻辑的代码抽离并封装到一处,可以避免在多个方法体里增加圈复杂度,只在一个方法体里增加。

  • 通过AOP技术,不但可以将重复的代码剔除出当前方法体,还可以将try...catch之类的代码剔除出去,以降低复杂度。

  • 通过一些语法糖或者框架,也可以降低复杂度。如,lambda表达式。

  • ……

还有很多小众的重构技巧这里就不赘述了,真是觉得大家都应该读一读《重构》这本书。

多说一句,不提倡刻意降低代码行数的方法,因为你的复杂度不下降,减少代码行数只是“掩耳盗铃”而已。

另外,重构有一个最佳伴侣,就是单元测试。你想象一个画面,当你重构之前通过率100%的单元测试在重构完成后跑一遍,发现了10%的失败。此时你的心情肯定是“真香,否则一堆bug等着我修”。

不过,如果你的代码「圈复杂度」越高,单元测试写起来越费劲。如何写好单元测试可以看我之前写的文章《聊聊单元测试》。

最后,怎么判断重构的效果好不好呢?自然是工作效率是否提高了。

  • 增加一个功能或者接口的时间是不是缩短了?

  • 测试那边回归测试的平均时间是不是缩短了?

  • ……

好了,就这么多。如果你还是觉得无从下手,不妨试试《重构》作者推荐的一种做法:

随机挑选一个目标,比如,“去掉一堆不必要的子类”。然后朝着目标前进,没把握就停下来。当你无法证明自己所做的修改能够保证原有程序的逻辑和语义时,立马停下来思考:当前做的重构是改善了?还是毫无成果需要撤销?

最后再次强力推荐《重构》这本书,里面有很多非常具体的代码重构方法,值得每一位程序员入手一本。

好了,总结一下。

这篇呢,Z哥和你分享了我对代码重构这件事的看法。要想提高你代码的“产出”,那么就得好好重视重构这件事。

在重构代码的「输入参数」、「输出参数」、「方法体」的时候需要持续保持以下4个关键点:

  • 始终工作

  • 持续集成

  • 随时中止

  • 过程可逆

这才能使得你的重构工作平稳的进行,而不会是一场赌博。

并且,重构方法体的时候要以降低「圈复杂度」为目的,而不是代码行数。如果条件允许,尽量多写一些单元测试来保障重构的稳定性。

希望对你有所启发。

重构可以使软件更容易地被修改和被理解,这个意义甚至大于所谓的“优化和改进”。Kent Beck大神曾也经说过:首先让代码架构易于改变,然后再进行简单的改进。

如果你想摆脱代码越改越痛苦的困境,那么赶紧行动起来吧。

推荐阅读:

  • 做架构也得讲武德

  • 聊聊单元测试

原创不易,如果你觉得这篇文章还不错,就「在看」或者「分享」一下吧。鼓励我的创作 :)

如果你有关于软件架构、分布式系统、产品、运营的困惑

可以试试点击「阅读原文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306373.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 Asp.Net Core 中使用 worker services

ASP.Net Core 3.0 Preview 3 新增了一个 worker services,那什么是 worker services 呢?它如同 windows服务 或者 Linux守护进程 一样的后台服务,在 Visual Studio 中提供了一个专门创建 worker services 的模板,如下图&#xff…

react全局状态管理_rxv: 在React中用Vue3的reactivity包实现状态管理。

前言React的状态管理是一个缤纷繁杂的大世界,光我知道的就不下数十种,其中有最出名immutable阵营的redux,有mutable阵营的mobx,react-easy-state,在hooks诞生后还有极简主义的unstated-next,有蚂蚁金服的大…

VS Code 变身约会利器!以码会友,轻松找到心仪的TA!

在韩老师的《Visual Studio Code 权威指南》一书中,我向大家推荐了许多好用的插件,其中也不乏许多摸鱼插件,刷知乎、炒股票、看电影、听音乐、追番、看小说,一应俱全。前不久,韩老师还给大家推荐过一款“小霸王”插件&…

机器学习——常用算法的总结

机器学习常用算法总结 机器学习——常用算法的总结学习方式一、监督式学习:1、分类2、回归补充——线性回归与逻辑回归二、非监督式学习:三、半监督式学习:四、强化学习:算法类似性一、回归算法:二、基于实例的算法三、…

机器学习——决策树学习

机器学习——决策树学习一、什么是决策树二、决策树的学习过程特征选择:决策树生成:剪枝:三、决策树的一个具体的实例导入数据python strip() 函数和 split() 函数的详解及实例数据处理提取到训练集中的标签给数据的每一列添加上标签&#xf…

微软:Excel公式是世界上使用最广泛的编程语言

喜欢就关注我们吧!文|一君微软近日推出了一项 Excel 公式构建的新功能 LAMBDA,正则测试阶段。LAMBDA 允许使用 Excel 自身的公式语言自定义功能,而过去,Excel 中需要通过 JS 等语言编写自定义函数。同时,LAMBDA 还可以…

机器学习——文件的读取

机器学习——文件的读取(一).txt文件的读取(二)excel文件读取操作(一).txt文件的读取 txt的链接 链接:https://pan.baidu.com/s/1fIAUdCDTpR7TiqLHZtx1yg 提取码:0929 python strip() 函数和 split() 函数的详解及实例 一直以来都分不清楚strip和split…

linux select读取节点数据失败_MySQL中覆盖索引查询和select*查询执行结果案例分析...

索引优化建议在MySQL中要尽可能使用覆盖索引进行检索,只访问索引的查询(索引列和查询列一致),减少select * 可提高查询效率覆盖索引(Covering Index)理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可…

使用 Azure WAF 羞辱黑客的智商

点击上方蓝字关注“汪宇杰博客”导语还记得之前给大家介绍过的《使用 Azure Web 应用防火墙拦截黑客攻击》吗?今天我又带来了一个有趣的 Azure WAF 小技巧,可以让你爽一把。好奇的黑客今天 Azure Application Insights 上发现了一段集中时间的404错误&am…

机器学习之乳腺癌问题(SVM)

机器学习之乳腺癌问题SVM题目所需的代码及数据利用SVM建模SVM调参题目所需的代码及数据 链接:https://pan.baidu.com/s/1bS7Ku_PUfcimiVkmLz9Fzw 提取码:0929 利用SVM建模 import matplotlib.pyplot as plt import pandas as pd import numpy as npfro…

如何将日志记录到 Windows事件日志 中

每当出现一些未捕获异常时,操作系统都会将异常信息写入到 Windows 事件日志 中,可以通过 Windows 事件查看器 查看,如下图:这篇文章将会讨论如何使用编程的方式将日志记录到 Windows 事件日志 中。安装 EventLog 要想在 .NET Core…

【Java】springboot

文章目录 Spingboot1、起步依赖2、构建springboot工程jar包3、springboot配置文件4、多环境配置5、maven和boot多环境兼容问题6、配置文件分类7、springboot整合mybatis Spingboot springboot用来简化spring的初始搭建以及开发过程。 比方说,创建一个springmvc程序…

数据科学与python语言——Matplotlib数据可视化基础

Matplotlib数据可视化基础一.读取数据与数据处理阶段1.提取指定行中的数据2.得到>指定数值的数据3.得到指定值得数据4.整体的数据处理:二.画图函数1.plt.subplots()2.plt.subplots_adjust()3.设置x轴y轴的刻度和标签4.使用中文标题在作图时三.画折线图(plot)四.画…

2021年,Azure云遇到. NET5,注定开启高光时刻,微软的心,真大!

云开发诞生的市场背景云开发是一个已经存在了很多年的概念,但在过去未能真正成为主流。然而,由于云和软件即服务的宏观趋势的结合,以及技术的进步,如容器技术 Docker 和 Kubernetes,云开发现在有机会最终成为基于云的应…

.net5+nacos+ocelot 配置中心和服务发现实现

相关文章:手动造轮子——为Ocelot集成Nacos注册中心出处:https://www.cnblogs.com/buruainiaaaa/p/14121176.html作者:唐 最近一段时间 因公司业务需要,需要使用.net5做一套微服务的接口,使用nacos 做注册中心和配置中…

数据科学与python语言——Pandas统计分析基础(时间转换+聚合)

Pandas统计分析基础(时间转换聚合)实验要求一实验二要求全部代码实验要求一 #M表的时间戳类型转为datetime data_Mete[TIMESTAMP]pd.to_datetime(data_Mete[TIMESTAMP],format%Y%m%d%H%M%S)data_VI[Date]pd.to_datetime(data_VI[Date],format%Y/%m/%d) p…

容器的那点事

当我们的后端服务器不够用的时候,我们可以通过容器技术,可以快速的把这些服务器全部虚拟出来, 当然这个虚拟跟虚拟机是不一样的,比虚拟机的方式快多了,早期阿里的淘宝平台如果整个坏掉了,重新搭建部署起来需…

lqb——修改数组

思路 **常规思路用哈希表的思想,设置bool数组标识是否被占用过,但是发生矛盾时将会造成查找需要遍历整个数组,比如,1,2,3……100000已连续占用,此时再插入1,将会一直遍历这100000个数,极端情况下,插入100000个1,将是n平方的复杂度。 如何快速查找到插入位置,这就引…

bp神经网络训练_数据分析模型6——神经网络基础(人工智能的底层模型)

未经许可请勿转载更多数据分析内容参看这里今天我们来学习人工智能的底层模型——神经网络(NEURAL NETWORKS),现在比较热门的一个模型是深度学习,深度学习的基础也是神经网络,要学好深度学习,神经网络不了解…

四种最令人讨厌的编程语言:Java、Javascript、C++和Perl

喜欢就关注我们吧!TIOBE 12 月榜单已于日前公布,在最新的排行榜中,C 语言仍高居榜首,其次分别是 Java、Python 和 C。在编程语言这一领域中,许多编程语言都会随着时间的推移而经历人气的激增,以及历史迭代之…