重构,系统改善之道
tryqlzw
8年前
<p>我常常喜欢把一个系统比喻成一辆车,你需要经常对它做维护和保养,才能保证它的良好运作。如果不这么做,虽然看着能开,但某一天一个严重的问题就会导致极其危险的后果。而持续重构就是我们给系统做的保养,这对于保证系统的稳定运行非常关键。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/7b6cb1edccec218b45e66df7730deabb.png"></p> <p>我曾主导过不少系统重构工作,从中也得出了一些我所认为的最佳实践,希望也能给其他程序员朋友们一些参考。</p> <h2><strong>从构建工具开始</strong></h2> <p>在接手去重构一个新的系统时,我常常会发现他们的构建脚本写得有多糟糕,有的系统甚至根本没有使用构建工具。更可气的是,负责系统的开发人员往往并不把它当回事,这就带来以下一些问题:</p> <ul> <li> <p><strong>自动化程度低下</strong> :很多本该由构建工具自动完成的工作,如:编译,测试,部署,都需要人工干预完成,或者由于构建脚本写得不好,本来可以增量编译和部署的,变为每次需要全量编译和部署,修改一行代码只用了一分钟,而等待构建却用了多达5分钟时间。</p> </li> <li> <p><strong>缺少有效的包依赖与版本管控</strong> :构建工具可以帮助我们进行有效的依赖包与版本管理。缺少了它,则很可能造成因不同开发人员引入的第三方包版本不一致所导致的系统问题。</p> </li> <li><strong>缺少自动化测试覆盖</strong> :构建工具能够帮助我们在每次版本构建时,执行自动化测试,这对版本的交付是一个非常有效的质量保证。</li> <li> <p><strong>不利于团队构建</strong> :开发团队总是具有一定流动性的,我们经常需要新人加入团队参与系统的开发。缺乏良好的构建工具,往往迫使每个新人都需要耗费大量的时间去搭建开发环境,这对团队的构建也是非常不利的。</p> </li> </ul> <p>因此,对于我来说,系统重构的第一步便是引入构建工具或重写构建脚本:</p> <ul> <li> <p><strong>引入构建工具</strong> :对于后端Java程序来说,我最常使用的便是Groovy,目前也开始尝试使用Gradle。而对于前端来说Grunt或更新的Gulp都是不错的选择。</p> </li> <li> <p><strong>第三方包依赖与版本管理</strong> :通过定义全局属性文件的方式,定义如系统的版本号,名称等信息,并在构建脚本中明确定义引入第三方包的依赖关系与使用的版本。使得整个开发团队都能使用统一且标准的开发环境。</p> </li> <li> <p><strong>实现自动化</strong> :通过定义不同的target(注:Groovy中的一个任务称为target),来实现不同的目标。比如:增量编译与本地部署,自动化测试,打全量版本包等等。总之,将一切需要手工完成的任务,利用工具去帮你完成。</p> </li> <li> <p><strong>写入开发手册</strong> :将构建工具的使用,不同target针对的不同应用场景写入开发手册,能够有效地减少新人的学习成本,并使整个团队开发效率得到提升。</p> </li> </ul> <h2><strong>让自动化测试成为重构的保障</strong></h2> <p>我们重构的目的往往是去解决系统的某些痛点,这些痛点也往往是系统的核心功能,因此,在你直接动代码之前,需要详细分析修改可能造成的关联影响。下面是我在做关键功能重构时所采用的步骤:</p> <ul> <li> <p>详细Review该功能的需求</p> </li> <li> <p>针对需求,完善自动化单元测试案例</p> </li> <li> <p>将这部分单元测试的执行引入到每次自动化构建中</p> </li> </ul> <p>在大部分我重构过的项目中,起初自动化单元测试都是缺失的。由于对核心功能的重构,往往涉及到大量代码的反复修改,因此,通过引入单元测试,可以非常有效地避免因重构造成的关联影响。而通过重构完善自动化测试,在我看来也是一个很好的重构实践,它将为我们未来的持续重构打下良好的基础。</p> <h2><strong>代码级的持续重构</strong></h2> <p>虽然我们一开始总是能够确保代码的质量,但不可否认,我们的代码会随着时间的推移变得越来越糟。这其中可能包括:</p> <ul> <li> <p>重复的代码,它们可能存在于同一个类或不同类中</p> </li> <li> <p>不一致或没有标识性的对象、变量或方法命名</p> </li> <li> <p>过长的代码段</p> </li> <li> <p>让人费解的布尔表达式</p> </li> <li> <p>过于复杂的逻辑判断</p> </li> <li> <p>对象错误地暴露其内部状态</p> </li> <li> <p>遭废弃但没有删除的类或方法</p> </li> </ul> <p>对于上面这些代码中的坏味道,你应该一有机会就尝试去重构它。但记住,你不应该操之过急,想着一下在把所有问题都一起解决。你只需要先识别出这些问题,然后分步骤地逐步去解决,而每次重构你需要充分识别可能造成的关联影响。如果你已经为你的代码写了单元测试,那你的重构将会有很好的测试保证。如果没有,你也可以尽可能找人帮你review修改的代码,因为不同的人来看你所修改的代码总是能发现不同的东西。</p> <p>另外,我们现在使用的IDE也能为我们提供很多帮助,比如找出要重构的方法在哪些地方被调用到,或者要重构的类的层级关系等等。它还能帮助我们自动地去重命名一个变量、方法甚至是类。</p> <h2><strong>基于微服务的重构</strong></h2> <p>最后,让我们从架构的角度来看看系统的重构。说到架构,时下最流行的一定是“微服务”架构了。在我看来,微服务并非是一个全新的架构方法论,而是对SOA——面向服务架构的一次升级。它的出现源于云计算、容器技术、DevOps等技术以及全新运维理念的不断成熟。</p> <p>最近,我正在主导一个遗留系统基于微服务架构的升级工作。在技术层面,虽然业界已经出现了多个支持微服务的架构,我们选择的是Spring Boot,主要是因为它的背后是Spring团队强有力的技术支持与维护。我们的重构也很简单:</p> <ul> <li> <p>服务识别</p> </li> <li> <p>UI与服务的剥离</p> </li> <li> <p>构建服务</p> </li> </ul> <p>由于采用微服务架构,我们并不会在原有系统上进行重构,而是创建一个新的基于SpringBoot的项目,将原有系统的功能,逐步拆分成一个个服务,添加到新的项目中,然后利用一些开关设置,将原有功能切换到新的基于微服务的系统中,这是与之前系统重构一个很大的区别。</p> <p>使用微服务框架,可以使开发变得更加简明,然而,它的难点恰恰在于服务的发现与定义。你系统中的哪些服务应该被独立出来,形成对外的服务,服务的粒度又应该是怎样的呢?我在做相关架构时,其实参考了领域驱动设计的思想,先识别出系统所包含的那些领域模型,然后按照领域的划分与不同的粒度来规划系统的服务。</p> <p>重构虽然无法直接创造业务价值,但却能显著提高系统的可维护性,所以你在重构上所投入的每一分钟,都将转变为未来节省更多的维护时间,这也是为什么我们需要持续重构的原因。</p> <p> </p> <p>来自:http://www.jianshu.com/p/f5a3204d408b</p> <p> </p>