软件开发丨关于软件重构的灵魂四问

在软件工程学中重构就是在不改变软件现有功能的基础上,经过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提升软件的扩展性和维护性。前端

摘要程序员

在本文中,您会了解到以下的内容:编程

先添加新功能仍是先进行重构?segmentfault

重构到底有什么价值?后端

如何评判这些价值?设计模式

重构的时机是什么?浏览器

如何进行重构?安全

1. 先添加新功能仍是先进行重构?

问题:

官方资料,重构分析1.0版中。架构

有两顶帽子,一个是添加新功能,一个是重构框架

添加新功能时,你不该该修改既有代码,只管添加新功能,重构时你就不能再添加功能,只管改进程序结构。

一次只作一件事情。

这两个是否有矛盾,以哪一个为准?前面有些可信材料版本不一,有的还要互相打架,是否能够统一一下?

回复:

关于添加新功能和重构是否矛盾的问题,是先添加新功能仍是先进行重构?

咱们要作的是观察这两个事情哪一个更容易一些,咱们要作更容易的那一个。

就是你不能一会儿同时作这两件事情。由于同时作两件事情,会致使你工做的复杂度提高,容易出错。

通常而言,重构会改变程序的设计结构改动相对来讲比较大。可是由于没有功能方面的添加,因此对应的测试案例咱们不须要进行修改,那对咱们来讲,只要可以使得现有的重构修改可以知足咱们的业务测试案例就能够了。

添加新功能意味着咱们要添加对应的测试案例,以保证咱们新的功能是可测的。这部分的修改通常会依托现有的程序结构,改动起来相对比较少,而且修改容易鉴别。

在绝大多数正常状况下,咱们通常是先添加功能,提交完成之后,再新的修改需求中对代码进行重构。

从大的方向上来讲是分两步走的,这两个任务不能混为一谈。

一次只作一件事情,一次提交只包含一个任务,这是为了不在工做中人为的增长复杂度,这个复杂度包含代码修改,审查,测试等各个方面。

避免复杂度的上升,是咱们在软件开发过程当中时刻要谨记的一个原则

俗话说,一口吃不成胖子,心急吃不了热豆腐。作事情要一步一个脚印,稳扎稳打,步步为营。

2. 重构的价值和评判效果

问题:

哪一种类型的代码重构是高价值的?

1. 在网上跑了这么多年也没啥问题,为何要动他?

2. 重构先后功能又没啥变化,当前收益是啥?

3. 如果提升可维护性,可扩展性的话,怎么评判效果呢?

回复:

这是关于重构价值和评判结果的问题。

这几个问题问的都很好。

咱们来看第1个问题,就是"在网上跑了这么多年也没啥问题,为何要动"的问题?

这里的关键点就在于到底有没有问题。是否是说在客户那边客户看不到问题,就算是没问题。

固然不是的,在咱们软件开发当中,在交付给客户之后,客户那边看到的是黑盒,他不知道咱们内部的逻辑存在多少的漏洞。

若是咱们的内部逻辑存在不少的漏洞。假设偶然某一天,某个客户发现了一个漏洞,它能够经过这一个漏洞进入到咱们的系统内部,这样进入咱们的内部,会发生什么样的情况,咱们能够本身想象。

在公司的内部发言中专门提到了UK对咱们产品的一个评价,外层是铜墙铁壁,内层是很脆弱的,客户或者黑客一旦进入到咱们的内部之后,他就能够随心所欲了,从这一点上来讲,咱们必定要对咱们现有的代码进行重构,以免这样的问题。

咱们再来看第2个问题。重构先后功能又没啥变化,当前收益是什么?

重构最大的收益是解决以下的问题:

代码太多重复问题,单个函数体或者文件或者攻城过大的问题,模块之间耦合度过高的问题等等。

以上问题归根结底就是一个问题,就是复杂度太高的问题。

如今来谈一谈复杂度的问题,软件开发中的复杂度固然是越低越好。通常谈到复杂度,咱们可能想到了各类逻辑上的复杂度,设计上的复杂度,实际上在软件过程当中复杂度涉及到方方面面,咱们来看一下,具体有哪些方面咱们须要注意复杂度的问题。

第一是命名规则。先举个例子,我定一个变量叫word。有的人喜欢把它写成wd。这个就增长了这个变量定义的复杂度,你从wd很难明白,这个变量是word的意思。

无论是变量的命名仍是函数的命名,咱们都但愿看到名字,咱们应该可以理解这个变量或者函数大致是关联到什么样子的事情。

因此谨慎的使用缩写是避免命名规则复杂度提升的重要前提。

第二是程序逻辑的复杂度。线性顺序执行的复杂度为1, 出现分支之后要乘以分支的个数。分支能够是条件判断也能够是循环。因此尽量的避免分支的出现是下降程序逻辑复杂度的重要手段。

若是程序分支不可避免,要尽量的把程序分支放到最高的逻辑层。这样作的目的是为了不在下层处理的时候出现发散式的分支。发散式的分支会急剧的增长程序的复杂度。

复杂度越高,程序越难维护,复杂度超过必定程度,人类程序员是没法处理的。

第三是架构设计的复杂度。架构设计涉及到模块设计和系统设计。要尽量的把一些公用的模块或者子系统抽取出来,好比安全相关的,日志相关的,工具相关的等等,这些公用的功能可能会被全部其余的业务模块或系统所调用。

在调用这些公用功能的时候,越简单越好,而且调用者不须要关心具体的内部实现,只须要知道如何使用就能够了。

这样作的目的是让程序员专一到业务代码的设计上来。

第四是系统部署的复杂度。系统部署包含几个不一样的阶段如开发阶段,测试阶段和生产阶段。无论是哪一个阶段,部署的步骤越少越不容易出错。有些系统自然的须要不少指令的配置,若是是这样的状况,须要编写一个批处理的文件来简化外部使用者的部署步骤,把多个步骤变成一步。

与部署相关联的还有集成部分。若是可以实现自动化或者从模板中建立那是很是好的状态。

第五是测试的复杂度。测试分白盒测试和黑盒测试。白盒测试的复杂度直接关联着代码层级的复杂度,代码层级的复杂度越高,固然白盒测试的复杂度也就越高。

白盒测试须要注意的一个重要问题是不要使白盒测试这部分的代码脱离实际业务代码的设计。也就是说白盒测试它的依附对象就是咱们实际的业务代码,从架构设计上说是一个附属层,不要试图在这里使用什么软件设计艺术或者所谓的编程艺术。

这种代码的风格就是简单直接,复杂度线性化。

黑盒测试的复杂度来自于业务需求分析。要有很是清晰的文档说明,须要对测试步骤和预期结果写的很是清楚。

第六是技术的复杂度。技术的发展趋势通常是愈加展越简单,功能越强大。那么在设计和开发的过程当中,要避免使用老旧的技术。关于技术框架的选择,要提早作好调研。前端选什么框架,要不要选择某些UI库,后端选什么框架,要不要选择某些程序库,原则上是为了简化咱们的学习过程,提升开发效率,加强整个项目的可维护性。须要具体问题具体分析。

第七是队伍结构的复杂度。队伍构成必定要短小精悍,人多不必定好办事。像亚马逊提倡的是两张披萨团队,意思是说整个团队两张pizza就能吃饱。大致估算就是10人左右的一个队伍。固然这只是一个参考指标。

整个队伍的目标必定要明确。全部的人都向着那个目标迈进,分工能够不一样,可是目标必定要一致。

目标+分工是队伍成功运做的关键。具体来讲就是把目标分红多个任务,每一个任务里又能够分红小任务,那全部的人都去作对应的任务,本身让本身忙起来,而不是别人让你忙起来。

咱们如今来看一下第3个问题,就是如何评判重构效果的问题。在上面的分析中,咱们已经了解了重构的目标和最大的收益,就是复杂度的下降。

那么对应的,就是代码的重复率大大下降了,单个函数体或者代码文件或者工程过大的问题不存在或者减小了,模块之间的耦合性下降了。

再进一步说,就是关于代码的可维护性和可扩展性上,咱们须要关注这么几点:

一是代码的可读性,咱们看到现有的代码就应该能够理解代码做者的意图是什么,这样咱们在修改bug的时候就更容易把握。好比函数,类或者组件的功能要单一化,命名要友好,要删除一些误导性的注释,对于一些没用的代码,要绝不客气的抛弃。

二是设计模式的可参考性。设计模式的好处就是提供一种能够追寻的代码扩展轨迹,新的功能能够遵循这种轨迹模板进行添加,从而得到复杂度线性增加的效果。

三是白盒测试的完善性。尽管咱们有很是强大的测试团队,对于黑盒测试方面有不少的经验和心得,可是如今咱们有不少项目缺少白盒测试案例,这使得开发者在进行重构的时候,面临很是尴尬的境地。没有充分的白盒测试案例,重构工做会举步维艰,有一种瞎子摸象的感受。

如今就说一下白盒测试这一部分。测试的框架应该在项目开始阶段或者重构开始前搭起来。等部分代码成型的时候,逐步的添加必要的测试案例。测试案例的选取能够按照环形复杂度的计算方法来肯定,也能够根据集成测试对应的用户需求来肯定。

与代码相关的测试,通常有单元测试,集成测试和系统级的测试。

单元测试,通常被认为很是繁琐。单元测试的繁琐主要体如今测试案例的选取上, 若是使用全覆盖方式来选取测试案例的话,会产生大量的测试代码,之后维护起来也是一个负担。若是采用环形复杂度来选取测试案例的话,会产生适量的测试代码,可是环形复杂度的计算也是一个很大的时间开销。

集成测试跟客户的实际业务需求相关。在这个过程当中须要理清接口的输入与输出,以及运行路径,而后据此来设计测试案例,写出测试案例代码。

开发人员通常不会拒绝写集成测试。由于她带来的好处是实实在在的,会极大的提升你的开发效率和调试效率。尤为是对于无界面的程序接口尤其重要。

系统级测试是大系统中子系统之间的集成测试。这个主要包含两个方面:

一个方面是有界面的自动化测试,经过这样的测试架构来模拟人类用户的使用过程,同时增长一些随机性的行为,试图可以找出系统的一些漏洞。

另外一种是无界面的测试,体如今多个服务系统之间的调用上或者相似浏览器自动化框架的使用上。

一套完整的测试系统,能够帮助工程师提升开发效率,减小之后系统维护和重构的成本。

从测试的紧迫性上来讲,集成测试最为必要,系统间的测试有时候使用手工测试经过一些测试工具来代替。单元测试能够有很广阔的讨论空间,这部分要具体问题具体分析。

3. 重构的时机

问题:

关于重构时机的说法,正确的是?

添加功能时,重构可以使得将来新增特性时更快捷、更流畅

在修复错误时,应该聚焦问题自己,不建议重构,能够避免引入新的问题

专家Review时重构,可以传递经验,改善设计,避免或减小代码持续腐化

回复:

关于重构的时机问题,如今咱们有三个选项,咱们就分别分析一下这三个选项。

第1个选项是说在添加功能的时候进行重构。这个选项的主要问题就是一个提交包含了多个任务。这属于人为的增长工做的复杂度。第1个缺点是会增长工做的难度,使得原本能够用工做量1解决的问题,变成了工做量2和3。第2个缺点是增长了代码审查的难度。原本你的提交中描述的是添加功能,结果发现里面的代码修改大部分与此描述无关。

因此第1个选项排除。

第2个选项是说在修复错误的时候应该聚焦问题自己,不建议重构,以免引入新的问题。

聚焦是点睛之笔。咱们在作任何事情的时候,都不要忘记初心,集中精力攻克问题,不要分心。

因此第2个选项是正确的。

第3个选项是说专家在审查代码的时候再重构。这里面的最关键问题是专家可能并不了解代码的业务需求和应用场景。他们可以看到代码存在很差的味道,但在不了解业务场景的状况下,让专家进行重构会带来很大的风险。

因此第3个选项也不正确。

4. 如何进行重构?

问题:

如何正确的进行重构?

回复:

下面咱们来看看如何进行重构。

简单的代码重构咱们都比较熟悉,好比说你经过工具就能够作一些整理,如变量重命名,函数抽取,类建立等等。

如今比较头疼的一个话题就是对老产品的重构,一些老产品涉及到上千万行,上亿行的代码。

关于老产品整改的问题。若是只是缝缝补补的话,可能起不到化繁为简的目的。其实作相似这种工做的话,有一个比较可行的方案。就是把现有的产品当作一个成型系统也就是现有运行的产品,不要作大的改动,顶多就是修改bug。

而后以这些成型的系统为基准,去写新的系统。至关于参照一个大的白盒就写一个小的白盒,这样新的小的白盒质量上确定比大的白盒性能上要有优点。

这样子循序渐进去作的话,就会比较靠谱。

有朋友会说上面的作法是重写,字面意义上没错的。

实际上不矛盾。区别就是重构的方式应该从下往上仍是从上往下。好比说咱们如今大部分的重构都理解为从下往上来作。也就是感受这个文件里头有坏代码的味道,而后就改这个文件,这样作是没有问题的。

好比如今有些教练遇到的问题,就是发现上下文不是很清晰,这个代码为何要这么写?为何一个文件有1万行或者3万行,这个前因后果不是很清楚。

这个时候可能就须要从整个子模块来进行一个自上而下的分析。梳理出这个子模块的功能需求是怎样的,须要有多少个公共接口?内部公共接口的实现方式是否是应该像目前这样的?

一个文件可以写成1万行或者3万行,确定是有必定历史缘由的,绝大程度是因为全局把握的编程能力不够形成的。

像这种状况,若是从这个文件自己去作重构的话,难度很是之大,可是若是从上往下,从模块的整个设计角度来作重构的话,可能就容易一些。

对于这样的庞然大物,最好的办法就是分而治之。首先要肯定系统的功能逻辑点,针对这些逻辑点,要编排好对应的检测点,也就是说等咱们完成了重构之后,咱们得确保咱们的重构是没有问题的,这些检测点就是作这个的,咱们能够理解成集成类的测试。

这些集成类的测试必定要确保能够在当前未重构以前的系统上正常运行。

有了这个设施之后,咱们就能够开展咱们的重构工做。重构的方法有不少,好比采用比较好的工具,函数和变量的命名改变,调用方式的改变等等。这些是在现有代码的基础上进行的重构。这里咱们重点说一下重写的方式来实现重构。所谓重写呢,就是另外开辟一套代码底座。甚至能够选用不一样的编程语言。

这种状况下重构首先要重用已有的业务逻辑,实现针对业务逻辑集成测试100%的经过率。

具体无论采用哪一种方式都要一个模块一个模块的进行推动。验证完成一个是一个,千万不能急于求成,试图一次性的把某些问题搞定。若是出现不少次失败,有可能会消磨掉你的自信心。因此必定要一点一点的往前推动,始终是在进步当中。采用了这种方式之后,无论当前的系统有多么的庞大,你只要坚持作下去,就必定可以把重构工做完全完成。

这个时候须要作的具体步骤能够参考以下:

1. 根据功能需求定义公共接口。

2. 根据公共接口写出测试案例代码。

3. 这个时候能够按照测试驱动开发的理念去填充代码。

4. 代码能够从现有的代码中抽取出来。

5. 在抽取的过程当中进行整理重构。

这样,这个子模块完成之后,就能够尝试去替代现有的子模块,看看能不能在整个系统中安全的运行。

对于整个系统来讲,咱们又能够分红不少个子模块。而后又能够对各个子模块各个击破,最终完成对整个系统的重构。

若是一开始对整个系统进行重构的话,也是能够从自上而下的角度来看的。

好比说开始的时候先把全部的子模块当作一些占位符,假定他们已经完成他们的接口了。那对于整个系统来讲,它自己就是一个子模块,属于提纲挈领的那个模块。

这个过程,从字面意义上能够理解成重写,实际上,它也是一个重构的过程,由于咱们确定会重用这个系统自己的一些现有代码和现有的逻辑。

上面咱们是假定系统在已经完成的状况下进行的重构,其实重构能够贯穿于软件开发的始终。软件开发的首要目标是实现业务逻辑,可以解决客户的问题。这个目标实现之后,咱们就要追求代码的干净度,复杂度可以降到最小,当前的技术可以用到最早进。

因此只要有机会,咱们都应该对代码和设计进行重构。

结语

本文针对收到的几个关于重构方面的问题做了回答,侧重点各不同,但愿可以给存在相同困惑的朋友们有所启示

点击关注,第一时间了解华为云新鲜技术~

相关文章
相关标签/搜索