`

重构之我见

阅读更多
      所谓重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

      定义很明确,清楚,但是怎么证明重构真的改善了代码的质量,性能?怎么证明重构提高了软件的扩展性合并可维护性呢? 换句话说,怎么证明我们花在重构上的时间真的产生了价值?

      在软件代码的开发中,坏味道是公认的不好的代码,而设计模式,Clean Code是公认的好的代码,因此,如果能够把有坏味道的代码重构为使用模式的代码,或者符合Clean Code标准的代码, 那么就可以认为重构改善了代码的质量,提高了代码的可扩展性和可维护性,这个从不好的代码到好的代码就是重构产生的价值。
   
       从上面的论述中,可以看出,任何重构一定是基于代码坏味道的,因此,重构的基础是对代码坏味道的识别能力,任何不是针对代码坏味道的重构都是耍流氓。

       在做重构时,通常会涉及到3种类型的重构:
一是某个类内部的修改,包括重命名,抽取方法等等,这类重构很简单,有编程经验的程序员们基本上都会;
二是把多个类的相同代码,往上提,从而整合出一个类层次结构,以方便代码复用,提高扩展性等等。
三是把多个类中功能相似的代码提取出来,归总到一个类中。

接下来就通过一个例子来看一下这整个的重构过程:有一个停车场ParkingLot,,这是第一阶段的需求,对于ParkingLot的实现如下:
public class ParkingLot {
    private Map<ParkingTicket, Car> carports = new HashMap<ParkingTicket, Car>();
    private int capacity;
    private int spaces;

    public ParkingLot(int capacity) {
        this.capacity = capacity;
        this.spaces = capacity;
    }

    public ParkingTicket park(Car car) {
        if(spaces == 0){
            return null;
        }
        ParkingTicket ticket = new ParkingTicket();
        carports.put(ticket, car);
        spaces--;
        return ticket;
    }

    public boolean isFull() {
        return spaces == 0;
    }

    public Car unpark(ParkingTicket ticket) {
        return carports.get(ticket);
    }
}
首先,对于park()方法的逻辑有点多,进行第一步重构,在park()方法的整个逻辑应该是获取一个停车位,然后把车停进去。因此可以把park方法按如下修改:
      public ParkingTicket park(Car car) {
        return isFull() ? null : parkCarIntoCarport(car);
    }

    public boolean isFull() {
        return spaces == 0;
    }

    private ParkingTicket parkCarIntoCarport(Car car) {
        ParkingTicket ticket = new ParkingTicket();
        carports.put(ticket, car);
        spaces--;
        return ticket;
    }
,接下来,第二阶段的需求,有一个泊车小弟Parker,管理一批停车场ParkingLot,每次有车来,他都按照顺序一个停车场停满再停下一个停车场。实现这个Parker的代码如下:
public class Parker {
    private List<ParkingLot> parkingLotList = new ArrayList<ParkingLot>();

    public void addParkingLot(ParkingLot parkingLot) {
        parkingLotList.add(parkingLot);
    }

    public ParkingTicket park(Car car) {
        for (ParkingLot parkingLot : parkingLotList) {
            if (!parkingLot.isFull()) {
                return parkingLot.park(car);
            }
        }
        return null;
    }

    public Car unpark(ParkingTicket ticket) {
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.unpark(ticket) != null) {
                return parkingLot.unpark(ticket);
            }
        }
        return null;
    }
}

第三阶段需求,有另外一个泊车小弟VacancyParker, 它也管理一批停车场,但是他放车的顺序是按照停车场空置率,哪个停车场空置率高就放哪个停车场。于是VacancyParker的代码如下:
public class VacancyParker {
    private List<ParkingLot> parkingLotList = new ArrayList<ParkingLot>();

    public void addParkingLot(ParkingLot parkingLot) {
        parkingLotList.add(parkingLot);
    }

    public ParkingTicket park(Car car) {
        ParkingLot choosedLot = null;
        double vacancyRate = 0;
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.vacancyRate() > vacancyRate) {
                vacancyRate = parkingLot.vacancyRate();
                choosedLot = parkingLot;
            }
        }

        return choosedLot.park(car);
    }

    public Car unpark(ParkingTicket ticket) {
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.unpark(ticket) != null) {
                return parkingLot.unpark(ticket);
            }
        }
        return null;
    }
}
,在这个阶段,我们把VacancyParker的park()方法整理一下,如下:
    public ParkingTicket park(Car car) {
        ParkingLot choosedLot = choosePark();
        return choosedLot == null ? null : choosedLot.park(car);
    }

    private ParkingLot choosePark() {
        ParkingLot choosedLot = null;
        double vacancyRate = 0;
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.vacancyRate() > vacancyRate) {
                vacancyRate = parkingLot.vacancyRate();
                choosedLot = parkingLot;
            }
        }
        return choosedLot;
    }
整理之后,就能发现一个很明显的代码异味,就是Parker和VacancyParker的代码重复特别多,除了在选择ParkingLot的逻辑上之外,其他的代码都是一样的,因此我们可以把代码重构为如下的类层次结构:

模式就出来了。
      第四阶段需求,有一个ParkerManager,它负责管理几个Parker,为了了解他管理的整个停车场的状态,需要获取其管理的所有停车场的状态报告,按如下格式:
parkerManager:
    Parker1:
        ParkingLot1:10/21 (注:共有21个车位,˙其中10个有车)
        ParkingLot2:8/21
    Parker2:
        ParkingLot1:10/21
        ParkingLot2:8/21
    ..........
实现这个需求时,第一阶段的代码如下:
public class ParkerManager {
    private List<Parker> parkers = new ArrayList<Parker>();

    public void addParker(Parker parker) {
        parkers.add(parker);
    }

    public String report() {
        StringBuilder stringBuilder = new StringBuilder().append("manager:\n");
        for (Parker parker : parkers) {
            stringBuilder.append(parker.report());
        }
        return stringBuilder.toString();
    }
}

Parker中的关于report()方法的代码如下:
public String report() {
        StringBuilder stringBuilder = new StringBuilder().append(TWO_SPACE_INDENT).append("parker:\n");
        for (ParkingLot parkingLot : parkingLotList) {
            stringBuilder.append(parkingLot.info());
        }
        return stringBuilder.toString();
    }

ParkingLot中关于report的方法如下:
public String info() {
        StringBuilder stringBuilder = new StringBuilder()
                                            .append(FOUR_SPACE_INDENT)
                                            .append("parkinglot:")
                                            .append(capacity - spaces + "/" + capacity)
                                            .append("\n");
        return stringBuilder.toString();
    }
在完成这个代码之后,有一个不易被发现的Bad Smell,当我们需要修改report的方式的时候,我们需要遍历整个代码结构树上的类,并做出修改。这种情况下,有一个通用的设计模式可以解决这类问题,这就是Visitor模式。即为类层次结构中全部,或部分成员添加一个report方法,该report方法接受一个Report类型的参数(根据需求,可使用不同的实现类),然后,把该成员的report功能委托给Report去做。经过提取参数,pull members up等重构手法对代码拾掇以后,类的层次结构变为:


如果,还有新的需求,我们可以继续通过如上的方法,明确需求-》查找合适的设计模式 -》重构出这个设计模式,保持一个干净的代码环境。

总结:
1、简单设计,以最快的速度实现当前需求
2、TDD保证重构信心
3、模式是重构出来
4、保持对代码异味的高度敏感性

本文中的示例代码地址:https://github.com/xianlinbox/ChessDemo/tree/master/src/main/java/parkinglot

  • 大小: 17.3 KB
  • 大小: 44 KB
分享到:
评论

相关推荐

    三维重构 、基于切片的三维重构 、三维重构的缺陷检测 ,matlab GUI界面的形式

    三维重构 可以检测有效的系统缺陷,进行缺陷的定位,缺陷体积的测量等,如何进行三维重构(3D reconstuction)的输入是各种三维以下的数据,比如纯二维的RGB图像(序列)、带有深度信息的RGBD数据等,出来的是三维...

    重构----改善既有代码的设计(完整中文扫描版PDF)

    《设计模式》的作者Erich Gamma为本书作序,足见其在软件工程中的重要地位。 书中介绍了超过70种行之有效的重构方法。这些方法是多年来软件开发者在实际工作中逐渐总结出来的经验,目的是为了在不改变系统外部行为...

    EANN实验解析与重构1

    测试阶段,模型在未见过的数据上评估其泛化能力。 重构过程主要是为了优化实验流程,提高代码的可读性、可维护性和执行效率。这可能包括模块化代码、优化数据处理步骤、调整模型结构以适应更大规模的数据或改进训练...

    “深度学习”理念下的名著教学课堂重构——以《智取生辰纲》教学为例.pdf

    其次,教学设计应紧密围绕文本,通过精心设计的各个环节,如“导入—创设情境”、“引读—理解文意,理情节”、“细品—看英雄,观天地”、“深悟—知天地,见众生”和“精练—读经典、写人物”,逐步培养学生的速读...

    基于Radon变换的图像重构(2017年数模国赛)

    CT系统在不破坏样品的情况下,利用样品对射线能量的吸收特性对生物组织 与工程材料的样品进行断层成像.即通过发射光源的起点然后中间经过...射,然后投影在探测器上面,故在探测器上可以见物质的几个图像与能量的变化.

    基于YOLOV5,裁剪不用的代码,重构图像检测接口

    由官方代码重构而得,本仓库只有推理代码, yolov5的官方代码见:https://github.com/ultralytics/yolov5.git 本API提供检测类型80种,对应索引和类型见下图 img 二、环境说明 见requirements.txt 三、参数说明...

    实践中的重构

    重构,早就不再是“奢侈品”,而是“日用品”。纵然如此,在自己的工作过程中,还是听到很多关于重构的误解。...目前为止,我还没有见过一个程序员,包括我自己在内,写代码是一遍就写得非常整洁,无需重构的

    基于C/C++实现将光激励红外结合深度相机,联合机械臂自动扫查扫查系统,实现自动扫查与三维重构+源码+项目文档(毕业优秀项目)

    基于C/C++实现将光激励红外结合深度相机,联合机械臂自动扫查扫查系统,实现自动扫查与三维重构+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,...

    永磁同步电机SVPWM过调制电压重构MTPA弱磁矢量控制仿真 模型 (1)内置式永磁同步电机,搭建基于反馈电压环的弱磁控制MAT

    永磁同步电机SVPWM过调制电压重构MTPA弱磁矢量控制仿真 模型 (1)内置式永磁同步电机,搭建基于反馈电压环的弱磁控制MATLAB仿真模型,同时结合...(6)仿真波形见截图。 仿真学习,biye设计好资料,2015B版本以上。

    基于动态神经网络的喷雾耙供气温度数据重构.pdf

    这意味着即使面对未见过的异常情况,模型也能有效地预测和重构数据,这对于实际操作中传感器故障的应对和数据恢复具有重要意义。 总的来说,这项研究展示了深度学习和神经网络在解决复杂热力系统数据问题上的潜力,...

    基于Yolov5-Deepsort-Fastreid源码,重构了视频行人MOT和行人ReID特征提取代码、接口

    本仓库代码由官方代码重构而得, 官方代码见:https://github.com/zengwb-lx/Yolov5-Deepsort-Fastreid.git 二、环境说明 相关依赖库见:requirements.txt 三、API参数说明 mot.src.deep_reid.DeepReid 行人多...

    公考行测空间重构类题目解题技巧.doc

    再分析 A、C 只有前面的一个面不同,根据展开图的关系,当右侧面和上面确定时,前面一定也随之确定,故答案选 C。 例 3:此题答案选 B。解析:此题的两个特征面—涂黑的面,是对面的关系,那只要在其展开图判断该两...

    现代通信技术之我见.pdf

    在本文件《现代通信技术之我见》中,虽然没有直接提供详细内容,但从标题和描述中可以看出,文档可能探讨了现代通信技术的各个方面,包括但不限于IP技术、网络架构以及未来发展趋势等。 标题和描述中提及的IP技术,...

    保险业信息化建设之我见.pptx

    《保险业信息化建设之我见》 在当前的数字化时代,保险行业的信息化建设已经成为企业竞争力的关键因素。本文将深入探讨保险行业的现状、发展趋势以及信息化建设的重要性。 保险行业现状与发展趋势: 1. 行业现状:...

    【配电网重构】基于matlab yalmip求解含sop+二阶锥配电网重构问题【含Matlab源码 2264期】.zip

    0积分下载,代码运行效果图见压缩包 1、完整代码,可直接运行 ,包运行 2、海神之光擅长领域:路径规划、优化求解、神经网络预测、图像处理、语音处理等多种领域Matlab仿真 3、版本:2014a或2019b

    互联网C端研究报告:流量焦虑与估值重构

    各路大小巨头围绕资讯...而随着互联网 C 端人口红利的消失,移动用户增长见顶,移动互联网步入存量市场,C 端用户变现迎来天花板。增长放缓的同时成本进一步推高,同时随着渠道的下沉红利殆尽,C 端流量总量遭遇天花板

    Spring框架的简单实现

    【SSH进阶之路】一步步重构容器实现Spring框架——从一个简单的容器开始(八) 【SSH进阶之路】一步步重构容器实现Spring框架——解决容器对组件的“侵入式”管理的两种方案--服务定位器和IoC容器(九) 【SSH进阶之路】...

    企业改革之我见.docx

    它打破了传统的以人定岗、以职位定岗的方式,通过科学的流程设计,重构组织架构,以适应企业未来的发展需求,提升管理水平。这一过程需要长期坚持,与业务流程优化相结合,通过管理诊断找到问题,如同医生对症下药,...

    TripServiceKata:.NET 版本的 Sandro Mancuso 重构卡塔

    测试和重构遗留代码Sandro Mancuso 重构 kata 的 .NET 版本(见链接)。代码 git clone https://github.com/orient-man/TripServiceKata.gitcd TripServiceKatagit checkout demo-start使用 git 导航重构步骤有用的 ...

    java代码-练习方法重构

    遵循“见名知意”的原则,使得其他开发者看到方法名就能理解其作用。 3. **内联方法(Inline Method)**:如果一个方法非常短且只在一个地方被调用,可以考虑将其内容直接替换掉调用的地方,减少调用层次,提高执行...

Global site tag (gtag.js) - Google Analytics