背景
最近项目新上线的版本中,出现了很多涉及下载相关
的bug;经过艰难的代码调试排查,可还是没有准确定位问题。主要还是由于代码年久,经过多代人的修修补补,到处都是“坏味道”的代码
。跟项目经理沟通过后,同意重构这部分的代码。下面就简单记录一下重构的过程。
项目现状
项目最早可追溯至2014~2015年,分为配置管理网站(Server),WPF桌面软件(Client)。桌面软件又分为:用户操作界面WPF,后台服务 Windows Services 程序。
在后端Services中,维护一个本地的小型Firebird数据库,为WPF提供数据和用户状态保持;实现了从远程服务器同步数据
到本地,访问远程接口
,下载管理
,数据上报
等功能。而我这次要做的就是,重写下载管理
。下面是关于旧代码的分析:
- 三种不同的下载触发机制:
软件启动触发
、用户触发
,数据同步到本地触发
; - 有八种不同的资源下载,分别在不同的表中管理;
- 有五套复制粘贴的代码:软件启动(两套)、用户触发(两套)、数据同步(一套);
- 大量无意义尝试,比如下载失败404后,短时间内没有必要再次尝试;
- 状态管理不统一,有的下载完成状态改为 ReadyForUse,有的下载完成状态改为 DownloadSuccess;
- 执行方法语义重叠,比如 DownloadPause 和 DownloadStop,DownloadFinished 和 DownloadSuccess;
- 大段注释代码,这些代码基本永远不会再使用的;
重构设计
鉴于项目是在运行阶段,一方面要考虑时间成本
,另一方面要保证原有的运行方式
。在当前软件的框架下,把重写的代码限定在下载模块内部,对外的接口不做改变。
重构代码的抽象和设计,也是依据当前项目的实际情况,尽可能的通用和易扩展。设计的UML类图如下:
下面的三个虚类,是下载管理流程的核心,所有的控制逻辑都包含在其中。
DownloadResourceAbstract
虚类 资源文件的基本信息:DatabaseID
和ResourceID
为了兼容不同数据库表的设计;DownloadPriority
预设优先级;DownloadImmediatelyPriority
用户触发的下载,优先级最高,立即执行;其它字段涵义可见字段名称;DownloadWorkItemAbstract
虚类 资源下载操作类,包含下载状态,开始,停止,MD5检查,状态更改的回调方法;与①
一对一关系;DownloadControlerAbstract
虚类 控制同时下载的数量,根据优先级控制下载的顺序;与②
一对多关系
下面的类,是具体下载方法的实现;当前的项目只有HTTP下载,类图中也只实现了HTTP的下载的管理功能:
- 可扩展下载方法设计:
FtpClient
、HttpClient
继承自DownloadClientAbstract
; - 关联 HTTP 下载,分别实现的子类
HttpDownloadWorkItem
和HttpDownloadControler
;
对不同类型的资源文件,组合不同的下载方式:
AutoHttpDownloadDealer
: 自动下载的资源 + HTTP 下载;UserOperHttpDownloadDealer
: 用户手动操作的资源 + HTTP 下载;- 由数据同步到本地的触发下载资源 + HTTP下载;(未在UML图中画出)
发现bug的真实原因
在重构测试阶段,意外发现了bug的原因:前段时间更换了文件服务器,而新的文件服务器,不支持断点续传,HTTP Status 返回的不是预期的Partial Content
(206),而是OK
(200);下载文件不存在时,HTTP Status 返回的也不是 404
,而返回自定义的 Response Text 。。。
由于这两个不合理的地方,导致软件的下载功能,出了好多莫名其妙的bug,再叠加到处复制 + 粘贴“坏味道”的代码
,修复起来异常繁琐,于是就有了重构这件事情。
个人感受
在当前的项目中,也做了几次小范围的重构,要么代码影响范围小,要么只是在原代码基础上套个框架。而这次是彻底干掉原代码,重新设计,重新写。下面是代码重写完成后 git merge 的 log:
Showing 1107 revision(s), from revision 6092a531 to revision f336c077 - 1 revision(s) selected, 166 file(s) selected; line: 5226(+) 5251(-) files: modified = 118 added = 28 deleted = 20 replaced = 0
从上面的log汇总信息,好像也不足以展示修改工作量。这些修改大约花费了我一周的时间,自愿工作日加班,自愿周末加班(有时候代码写上头了,还得强迫自己下班休息去)。开始的前三天的时间,边读旧代码,边搭建新的框架,尝试把旧代码融入到新设计中;后期转换思路,理解代码逻辑,把代码逻辑融入到新设计中
。
这样就可以放开手脚的干起来了,写代码的时候,感觉时间过的好快,如有神助般各种代码中的细节
,也会不假思索的写出来。在下阶段单元测试中,非常顺滑,基本没有发现什么bug,真的是酣畅淋漓。
最最让我寒心的是,好不容易重构测试完代码了,压抑着无比激动的心情
,希望组里的同事给个积极肯定的反馈,可连一个“卧槽”也没有,就行往常平淡的工作日。。。项目经理也是很淡然的说,要尽快拿给测试。
尾声
即使我的努力和心血没有得到,预期中的肯定,我也要把这个过程留存记录下来:把重构的代码剔除项目信息,再提取合并通用的部分,重新梳理成一个类库Github 地址;写一篇文章来记录一下。
其实,后面我细细回想了一下。如果最开始项目设计的下载功能是,把所有下载资源放在同一张数据库表中管理,是不是就那不就更简单了?如果最最早期的设计做好了,后期可以省掉多少功能扩展、修复bug、重构。