前言: 我们做程序员这一行的哈,说白了就是用机器能识别的语言跟机器打交道,但是经常会出现的一种情况是,我们所写的代码只有自己和机器能看得懂,其他人看懂有一定的困难性,甚至有时候过了一段时间连自己都看不懂了。。。那么此时你应该感叹自己当时的水平高超,还是该好好的反思一下了?

一,重构是什么

OK,闲言碎语不多讲,啥叫重构呢?随手打开百度,输入“重构”,点开百度百科:重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。但是这个解释缺少了一个前提,完整的说法应该是:在不改变软件可观察行为的前提下,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

当我第一次读到这句话时,感觉重构这件事情跟我这个满头秀发的初级程序员没有任何关系,还什么设计模式,还什么架构,好高深的东西。。但是随着发型趋于成熟和结合踩过的越来越多的坑,逐渐改变了这个想法。

我这里不是某个不合格的老师上课只会读PPT,当然我也没有资格去做老师。我所相信的是,任何东西,任何学科都能用最简单的普世理论讲的通,也都能在自然界或人类社会中找到某个大家所熟知的例子进行类比,尤其是自然学科。说白了,不管是给一个普通人还是行业顶尖人解释一个复杂的功能或概念,用生活中常见的例子加上人的惯性思维比照本宣科更容易让人理解!咱是个粗人,怎么理解重构这个概念呢?前提:不能改变软件原来有的功能,我做了点什么事呢:改了改代码,然后呢:机器跑这个软件轻松了或其他同事更容易理解这段代码了(此处立个Flag,或许下次再读这句话时会重构它)。

举个例子,当你穿的衣衫不整的就去参加亲戚或朋友的婚礼了,而且表情愤怒,言语不羁,认识你的人知道你是来参加婚礼,不知道的以为你是新娘的前男友去砸场子呢,容易被门口保安小哥按住。如何避免这种尴尬呢?很简单,重构一下啊:把头发梳成大人模样,穿上一身帅气西装,手里拿着红包,然后满脸微笑,见了人多打招呼。这样的话别人一看就知道你是来干啥滴。你还是你,该做的事情做得更好了,保安小哥也不会拦你了,说不定门口的礼仪小姐姐还会和你打招呼,而且逼格也提升了好几个level。设身处地的想一想,我们的仪表着装言谈举止不就是我们个人社交时的“对外接口”么。

OK,回到软件代码的重构,结合刚才那个例子,其实把一个很low函数名改成一个容易让人理解的名字,也属于重构。这样不管是自己还是其他人一看函数名字就大体知道这个函数是要干啥滴!

二,为什么要重构

关于为什么要重构,查阅相关资料或翻阅相关书籍大多都会提到以下几句话:1、持续偏纠和改进软件设计 。2、使代码更被其他人所理解。 3、帮助发现隐藏的代码缺陷 。4、从长远来看,有助于提高编程效率,增加项目进度。

其实将什么是重构时已稍微带入了,再次强调一下,为了持续改进我们的软件设计使得机器跑我们的软件更轻松,为了让同事更容易理解我们写的代码,为了出更少的Bug,为了更快的开发! 简单明了而且就连百度百科上也解释的很清楚,此处不多赘述。

三,怎么重构

哲学上有世界观和方法论,同样此时我们已经对重构有了一定的了解,那么我们通过什么方法进行重构呢?

OK,接下来要分不同的情况进行讨论了。

1,提炼函数

具体做法:①拆分过长的函数。②提炼在多个函数中使用的相同代码

这样做的好处是:①函数被复用的机会大。②如果所提炼的函数命名足够好,那么高层函数的功能更加一目了然

比如以下函数(代码是C/C++,但是 C#, Java的同学都可以看得懂):


int nArrays[5] = {1,2,3,2,1};
void PrintResult()
{int nPrintNum ;// Print split line;printf("****************************");printf("********             *******");printf("****************************");// Algorithm;for (int i = 0;i<5;i++){nPrintNum += nArrays[i] * 5;}// Result:printf("Result: %d" , nPrintNum);
}

其实这个函数已经很简单了,我这里举一个简单的例子简单说明一下(代码纯手敲,并没有做过运行)

分析一下哈,首先这个函数名是叫"PrintResult",个人感觉里面最好不要包含算法,所以将算法应该提取出来,当然上面Pring splist line也可以提取出来:

void PrintSplitLine()
{printf("****************************");printf("********             *******");printf("****************************");
}int CalcResult()
{int nPrintNum = 0;for (int i = 0;i<5;i++){nPrintNum += nArrays[i] * 5;}return nPrintNum;
}void PrintResult()
{PrintSplitLine();int Result = CalcResult();printf("Result: %d" , Result );
}

这样的话PrintResult()这个函数甚至连注释都不用加大家就都能看懂,而且如果其他地方需要打印分割线或者使用这个算法时候,这两个函数都可以复用了。

2,以查询取代临时变量

比如上一段代码定义了一个为Result申请了一个临时变量,当然也无可厚非,提倡的做法是直接查询就可以了:

void PrintResult()
{PrintSplitLine();printf("Result: %d" , CalcResult());
}

3.引入解释性变量

大家看下下面的代码,虽然加了注释,但是看起来依然不爽,揍啥嘞界是!

double GetPrice()
{//price is base price - quantity discount + shippingreturn m_nQuantity * m_nItemPrice - max(0,m_nQuantity-500) * n_ItemPrice * 0.05 + min(m_nQuantity * m_ItemPrice * 01, 100.0 );
}

这个表达式太复杂,我们应该将表达式拆分,拆分的结果应该放到临时变量里,以变量名称来解释表达式的用途:

double GetPrice()
{double dBasePrice = m_nQuantity * m_nItemPrice;double dQuantityDiscount = max(0, m_nQuantity - 500) *  m_nItemPrice * 0.05;double dShipping = min( dBasePrice * 0.1, 100.0);return dBasePrice - dQuantityDiscount + dShipping;
}

4,分解临时变量

临时变量通常用于保存临时运算结果,这种临时变量应该只被赋值一次。如果它被赋值超过一次意味着它承担了一个以上的责任,当然这也无可厚非,但是这使得其他人会产生疑惑,这个值到底是干嘛用的?

比如下面这个函数,本来就已经够复杂的了,但是大家会发现dAcc赋值了两次,而这两次是属于不同的加速度,都用这个变量表示容易让人产生误解。

double GetDistanceTravelled(int nTime)
{double nResult = 0;// 加速度double dAcc = m_dPrimaryForce / m_dMass;int nPrimaryTime = min(nTime, m_dDelay);nResult = 0.5 * dAcc * nPrimaryTime * nPrimaryTime;int nSecondaryTime = nTime - m_dDelay;if (nSecondaryTime > 0){double nPrimaryVel = dAcc * m_dDelay;dAcc = (m_dPrimaryForce + m_dSecondaryForce) / m_dMass;nResult += nPrimaryVel * nSecondaryTime + 0.5 * dAcc * nSecondaryTime * nSecondaryTime;}return nResult;
}

不同含义的变量应该用两个不同的临时变量:

double GetDistanceTravelled(int nTime)
{double nResult = 0;// 加速度const double dPrimaryAcc = m_dPrimaryForce / m_dMass;int nPrimaryTime = min(nTime, m_dDelay);nResult = 0.5 * dAcc * nPrimaryTime * nPrimaryTime;int nSecondaryTime = nTime - m_dDelay;if (nSecondaryTime > 0){double nPrimaryVel = dPrimaryAcc * m_dDelay;const double dSecondaryAcc = (m_dPrimaryForce + m_dSecondaryForce) / m_dMass;nResult += nPrimaryVel * nSecondaryTime + 0.5 * dSecondaryAcc * nSecondaryTime * nSecondaryTime;}return nResult;
}

5,移除对参数的赋值

首先观察下面的函数的某个片段:

void Method( Object obj)
{int nResult = obj.GetValue();       // 1Object anotherObject = new Object();// 2obj = anotherObject;                // 3// ...
} 

解释一下,首先obj是一个参数,在函数内改变它的值是一个不好的习惯,其次obj是当做一个对象传进来的,也没办法改变它的值。如果在Method函数里面只是想获取某些obj里面的值,下面这种写法是提倡的:

void Method( const Object& obj)
{int nResult = obj.GetValue(); Object anotherObject = new Object();// ...
} 

首先我不改变传入的值所以加了个const,其次传引用只需要传递四个字节。这里需要注意的是Object类中的GetValue()应该也加上const:GetValue() const{ ... },因为const对象不能引用非const成员函数。另外函数加上了const,在函数内就不能改变成员变量了。

再举个简单的例子:

int Discount (int nInputVal, int nQuantity, int nYearToData)
{if (nInputVal > 50) nInputVal -= 2;if (nQuantity > 100) nInputVal -= 1;if (nYearToData > 10000) nInputVal -= 4;return nInputVal;
}

这种直接这种传入值,转了一圈又当做返回值出去了。。。不要修改传入值,否则容易让人混淆。提倡做法:

int Discount (int nInputVal, int nQuantity, int nYearToData)
{int nResult = nInputVal;if (nInputVal > 50) nResult -= 2;if (nQuantity > 100) nResult -= 1;if (nYearToData > 10000) nResult -= 4;return nResult;
}

6,以函数对象取代函数

如果有一个大型函数,局部变量过多,可以将这个函数放到一个单独的对象中,如此一来,局部变量就变成了对象内的字段。然后可以在同一个对象中将这个大型的函数分解成多个小函数。另外,局部变量过多时,首先考虑“以查询取代临时变量”,有时候你会发现根本无法拆解一个需要拆解的函数,在这种情况下考虑使用函数对象。

class Order
{// ...double Price(){double primaryBasePrice;double secondaryBasePrice;double tertiaryBasePrice;//long computation;// ...}
}

可参考以下类图:

7,替换算法

把某个算法替换为更清晰的算法:

下面这段代码是Java的代码,懂得C++或C#的童鞋应该能直接看懂。两段代码要实现的功能一致,至于你认为哪一种好用,就用哪一种呗(个人比较推崇第二段实现方式)。

String foundPerson( String[] people ){for ( int i=0; i < people.length; i++ )  {   if ( people[i].equals("Don") ) {return "Don";}      if ( people[i].equals("John") ) {return "John";}if ( people[i].equals("Kent") ) {return "Kent";}}return "";
}
String foundPerson( String[] people ){List candidates = Arrays.asList(new String[]{"Don","John","Kent"});for ( int i=0; i < people.length; i++ )    {if ( candidates.contains(people[i) ) {return people[i];}       }return "";
}

8,内联函数

在C++中,以inline修饰的函数叫做内联函数,编译时C++编译器会调用内联函数的地方展开,没有函数压栈开销,内联函数提升程序运行的效率。

内联函数用法大家自行百度就可以了,此处不再啰嗦。

OK,暂时先给大家分享到这里。欢迎批评指正。

重构是什么,为什么要重构,怎么重构相关推荐

  1. m基于贝叶斯理论的超分辨率重构算法matlab仿真,对比Tikhonov重构算法

    目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 超分辨率(Super-Resolution)通过硬件或软件的方法提高原有图像的分辨率,通过一系列低分 ...

  2. 重构学习(三):代码重构的原则

    1 前言 最近因为一些乱七八糟的事情耽搁,所以有一段时间没看<重构:改善既有代码的设计>这本书,可以先看上两篇笔记重温一下: 从零开始了解重构(一): 从零开始了解重构(二). 本文接上两 ...

  3. python之父考虑重构python解释器_Python之父考虑重构Python解释器

    同时,父重构杜卡迪也将会从哈雷那里获得更多的资金支持来维持品牌发展. 凡本网注明来源非中国证券报·中证网的作品,考虑均转载自其它媒体,考虑转载目的在于更好服务读者.传递信息之需,并不代表本网赞同其观点 ...

  4. 相空间重构 matlab 程序源,matlab求相空间重构延迟时间和嵌入维数

    关联积分计算 function C_I=correlation_integral(X,M,r) %该函数用来计算关联积分 %C_I:关联积分的返回值 %X:重构的相空间矢量,是一个m*M的矩阵 %M: ...

  5. 可重构优化c语言,第八章 可重构计算处理器技术_魏少军.pdf

    中国科学 信息科学 年 第 卷 第 期 评 述 中国科学院学部 科学与技术前沿论坛 微纳电子专刊 可重构计算处理器技术 魏少军 刘雷波 尹首一 清华大学微电子所, 北京 100084 * 通信作者. ...

  6. 抖音iOS最复杂功能的重构之路--播放器交互区重构实践

    背景介绍 本文以抖音中最为复杂的功能,也是最重要的功能之一的交互区为例,和大家分享一下此次重构过程中的思考和方法,主要侧重在架构.结构方面. 交互区简介 交互区是指播放页面中可以操作的区域,简单理解就 ...

  7. 【配电网重构】基于粒子群求解配电网重构模型matlab源码

    一.故障信息的数学表示 在上图中K表示断路器,每一个断路器上均有一个FTU装置,可以反馈断路器开关是否过流,用表示上传的故障信息,反映的是各分段开关处是否流过故障电流有故障电流为1,否则为0).即: ...

  8. WCF服务重构实录(上)

    项目需求 之前的项目中采用了WCF,绑定模式选择的是netTcpBinding,宿主选择了控制台方式,主要考虑两方面优点: 方便管理宿主的生命周期 提升服务性能 但是在实际的开发过程中产生了许多问题, ...

  9. 求求你别再写上千行的类了,试试这些牛逼的重构技巧吧

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:juejin.cn/post/6844904038383747086 答应我,别再写上千行的类了好吗 最近在对已有项目进行扩展 ...

  10. IDEA 真牛逼,900 行又臭又长的类重构,几分钟搞定?真香!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:juejin.im/post/5e11dd736fb9a04 ...

最新文章

  1. Java 对象的理解
  2. Ubuntu 安装 IPython、jupyter notebook
  3. 可以通过无线充电的软脑植入物来控制大脑中的脑细胞
  4. arduino定时器函数如何使用_【Arduino101教程】定时器的使用
  5. 【Android 高性能音频】Oboe 开发流程 ( 包含头 Oboe 头文件 | 创建音频流 | 设置音频流 | 音频流回调类 AudioStreamCallback )
  6. 【评论】为什么我的工资最低!
  7. c# Parallel.For 并行编程 执行顺序测试
  8. python视频人脸检测_Python学习案例之视频人脸检测识别
  9. 安装Linux后的遗留问题
  10. ansible 修改文件变量_基于ansible的批量配置生成
  11. openstack中resize更改虚拟机内存、cpu大小后虚拟机状态显示VERIFY_RESIZE
  12. vue-flowy前端流程图绘制工具
  13. 利用 Caddy 轻松实现反向代理/镜像(支持自签SSL证书)
  14. android webview 弹出浏览器,android – 防止WebView打开浏览器
  15. c语言编程分数判定,用C语言编程平均分数
  16. 耳机煲机软件测试工资,耳机煲机有必要吗_耳机煲机正确方法
  17. 【精华帖】PS拼接图片最简单教程
  18. could和may区别请求_英语教学:外教这样区分can,may,could
  19. MFC实现基本图形绘制、变换、自由曲线绘制、图形裁剪和填充
  20. 在windows电脑定时运行python脚本

热门文章

  1. noi.ac 邀请赛1 By cellur925
  2. 青年教师大讲堂 计算机,青年教师大讲堂 | 第二期海洋与科学技术学院祝捍皓博士开课啦!...
  3. java吃货联盟的实训报告_作业:吃货联盟
  4. ReactiveX 简介
  5. 对话 | 港科大教授权龙:为什么三维重建才是计算机视觉的灵魂?
  6. 百度商家口碑将停止运营并下线
  7. 生成带大写英文字母和数字的验证码(手机或邮箱)
  8. 计算机信息检索课程小结,计算机信息检索课程教案.doc
  9. 游戏SDK(三)架构设计之代码实现1
  10. 清华姚班毕业生开发新特效编程语言,99 行代码实现《冰雪奇缘》,网友:大神碉堡!创世的快乐...