重构之践
上个月个人有机会重构正在开发的一个系统代码,在完成后团队使用中还有效果的情况下,觉得有必要将总结一下。
简介
这个系统功能是一个工业设计软件,通过一些参数的配置,自动生成客户所需要的模型。采用C#开发,winform系统,该系统现为单机版,不存在服务端开发。其代码实现主要是模型生成,数据库参数读取都相对简单,现阶段代码量不是很大,配置界面大概有十个左右,业务操作代码估计有一万五左右吧。开发模式主要是敏捷开发,前期对系统进行简单设计,具体详细设计主要是由编码人员实现。
原因
- 由于该系统正在开发中,不易系统新增功能,甚至有些功能无法新增;
- 核心功能修改困难,只有开发自己可以修改;
- 该系统是公司计划的一个产品,产品负责人希望保证其质量;
- 团队技能提升方向。
处于以上原因,团队成员与团队负责人,产品负责人,以及请其他技术骨干人员,大家一起讨论觉得现阶段代码有必要重构。初步的计划是,不影响整体项目计划,我们原开发人员继续完成可以完成的任务,重构由团队负责人,与一C#技术资深人员(其他部门,十年左右开发)完成 。这里就有必要说说我们的这位团队负责人了,反正现在也不是我的主管了!):,他以前是做java的,自身对技术也比较感兴趣,现在刚刚做团队主管,经常把一些PMP中的原话搬出来,自己不负责开发,不做分析,不做设计,不写代码,但是在专案的过程中,经常对开发很感兴趣。
好了,还是言归正传,话说最后决定有这两位大神对系统进行重构,一位是相对我们这些全身投入的“猪”来说的“鸡”,一位是从其他部门调来的大神,每天支援我们专案半天左右。二位就开始了他们的重构之践,每天打电话请资深人员来我们办公室,二位在同一台电脑前,开始了结对重构,一个向另一个讲解专案需求,另一个向这个分析应该怎么设计,应该怎么实现。就这么一个知道需求,一个只懂技术的两个人一周过后,系统基本与原来一样。为什么会这样呢?最后还是我们团队负责人说,他每天要对另个人讲解需求,而且还包括部分行业知识,还不如他自己做呢,但他自己又对具体实现和C#不清楚,不明白不敢修改,而且还要不停的询问当时代码开发人员,效率效果都不好。
所以最终还是决定让我来重构,因为我对现阶段系统的实现都比较清楚,没有十分也有七分吧。我对当前的重构内容评估的时间七人天,最后给了我两周的时间,包括最后将现在开发的任务整合进重构后的系统代码中。
目标
由于是自己第一次正式的做重构,所以我当时给自己制定的目标是:
- 使该系统新增现功能方便;
- 处理当前系统难以修改的部分;
- 多向其他人员询问,这样是否可以易于编码?
步骤
这里就该主要的总结一下,自己在本次重构过程中究竟做了哪些了。这里有有必要说一下VS的一个快捷键 “CTRL +K,R ”,该快捷键帮助我找出一个属性,方法或者类在哪些地方被调用,对后面的重构省了很多功夫。而且有SVN版本管控使我很容易的找到上一个正确的版本,并比较修改了哪些代码。
步骤一:先找出系统中复杂度最高的几个类。
我的定义是修改最频繁,代码行数最多,其他类引用该类最多。初步划分下来主要有三部分,暂且定义为A、B、C吧。A为界面操作部分,B为A对应之业务操作类,C为另一主要业务部门,在编写代码中我主要负责的C中的部分实现。
步骤二:找到了需要重构的部分,接下来就要确定从哪里入手。
针对上面的ABC三部分,我第一感觉是将C排在了最后,因为当时主要是由我个人完成的,我对里面的代码质量还是有信心的(虽说最后仔细看代码结构还是不理想),而且每个人都不喜欢改自己的代码,认为修改即是对之前的否定,我个人心里也算有点这方面的意识吧,虽说一直在调整这种认识;第二觉得应该最先处理的是B部分,因为现有大部分新增功能都与B相关,而且认为B完成后也易于修改A部分。所以最终就从B部分开始啦!
步骤三:确定了具体从哪里入手后,就要彻底仔细分析该部分。
B部分复杂不是由于引用该类的地方多,直接引用该类只有A的主要一个界面,这里为什么说是直接引用呢?这就要提到A部分了,因为在A的主界面中B是作为一个公用属性,而且在主界面上new一个子界面时穿的参数是this——即主界面全部信息。好了还是说B吧,B复杂主要是由于修改频繁和代码行数较多,该类当时行数记得好像大概有近2000行吧,而且还在增加,修改频繁的主要是B里面的一个主要方法Mb,而且该方法基本上只有编写人员他一个搞的懂内部关系,其他人只能望文观止。
步骤四:具体做法。
我在重构的时候具体的方法大概有:由于没有测试代码,所以较小比较确定的修改只是保证其编译通过,就可以进行下一项了,较大的修改,就需要简单手动测试下。
- 去除一些无用的注释,例如当时开发人员注释掉的一些方法或段落,现在用不上的;
- 移除除没有被用到的属性,方法,类,以及一些空的cs文件。这里的类和cs文件可能是超前设计的产物;
- 只在该类中用到的属性,方法,降低起访问级别,改为private;
- 该类中修改最频繁的那个方法Mb,复制至新的类中,并取合适的类名称,连同其所有子方法,以及用到的属性字段。修改调用该方法的代码,使其使用新的类中的方法;
- 检查上面方法中的子方法与属性,原类中是否还有其他方法使用,若无直接删除,否则先暂时保留。分析有使用共同属性的方法与需要调整的方法Mb之间是否有联系;
- 去除掉与方法Mb相关的操作后,B类此时剩余代码大概就只有1000行左右。也就是每一个复杂的类里面总能找到一个与之对应的方法同样复杂;
- 接下来就是对方法Mb再一步进行重构,方法复杂主要是由于方法里面判断比较多,解决的方法就是用多态替换判断,将不同类型处理方式放在不同的类中,然后将这个方法改为一个工厂方法;
- 做完这些测试通过后,我觉得B部分就可以先放一放了,接下来就是A部分,因为AB关系较密切;
A的处理方式:
- 开与B中的基本相似,除去无用代码与注释
- 然后修改界面之间的传值问题,将this整个界面传递改为需要什么传什么
- 若子界面中的数据影响主界面,则用委托事件替代之前的直接调用主界面中的方法
- 建立每个子界面的业务操作类,从B中移除主要是数据读取
- 将每个界面上的数据新建一个对象,运用单例模式保存数据,减少之间的参数传递
- 子界面与子界面之间的关系都通过主界面间接触发
- 提取每个界面的公有接口,重置,保存,判断数据正确性,判断数据是否被修改
C的处理方式
C复杂主要是由于开始的时候只有一种处理方式CA,然后慢慢的又出现另一个处理方式CB。也就是之前只有一个一个基类CA,然后又加了一个基类CB,但是写CB这个类的人又是完全参照CA写的,造成二者间有许多重复的操作,而且为了模仿增加了一些不必要的操作。然后出现这种情况了,当时我们讨论尽然大部分一样那就在抽取一层CAB,是CA和CB分别继承CAB,好么,最后这样弄完以后就是全部代码在CAB类中大概有1000行左右,然后CA、CB有两三百行左右。
我的处理方式就是和其他人先讨论这两种处理方式,最后决定就使用CB这种方式,好,由于CA之前是我写的,CB是另外一人写的,但是我比较清楚。我就将CAB重写了一遍,去掉CA与CB,将类中的属性分离只另个类中,这个类的对象作为CAB的一个属性包含在内,方法里面的一些类型判断全部删除,留给子类自己处理,最后好像代码也只有三百多行似的。
现状
重构的任务当时也在两周之内完成,达到团队的预期把。距现在也有一个月时间了,在这一个月里代码中又新增了许多功能,工作中也发生了许多事。代码整体上还算整洁,每个类的代码基本上都还保持在500行左右,但是也有时常有需要改善的地方,我个人有个习惯,就是没事的时候看看自己别人写的代码,觉得需要调整的就调整了,觉得别人哪里写的不好的,也会找本人谈谈。我发现我们团队的主要问题是在完成功能时忽略代码质量,而且就算觉得部分代码放在这里或这样写不好,但是不知道应该放哪,这时在不知道的情况就只好暂且先这样,然后就是永远这样。个人觉得我们在面向对象中还是应该先做好类的封装,然后在谈继承多态,就像我之前介绍设计原则——单一职责,封装是最基础最实用,对封装性最直观的就是方法、类行数,以及类中的属性方法数量。
反思
其实写这篇博客的初衷,希望将自己重构的方法,以及对代码质量的认识分享出来的,但是写着写着就跑到记录上去了,唉又跑题啦!不过还好,这张的题目叫重构之践,多点实践,下次重构之见时再写个人的认识见解吧。