代码重构是成为一个优秀程序员的必经之路,在看多了糟心的代码后我们才能知道怎么写出不糟心的代码。同时重构代码锻炼了我们的耐心,细心,和勇气。又能加快项目的后期迭代速度,和代码的可维护性。简直是一件有百利而无一害的事情。
要说害处,还真有一件,那就是会产生bug(不过我们就是写bug的啊),尤其是对之前的业务代码一无所知的情况下。
1. 为什么要重构代码:
每个人在开发一个新项目时肯定都会思考如何设计,然后再进行编码。但是随着时间的流逝,功能的迭代。不同的人都会对之前的代码进行修改。于是最开始完美的设计结构逐渐衰弱,代码质量也慢慢沉沦,编码工作也从严谨的工程堕落为胡侃乱劈的随性行为。到了这种地步,就是时候来一次重构了。
套用一句名言:优秀的代码总是相似的,糟糕的代码却有各种各样的糟糕。主要体现在:
- 代码逻辑结构混乱:这其中包含的异常情况太多了,是代码重构中最糟糕的吧,勉强维持运行,但是没人敢改动,一动必出bug。
- 单文件或者单个函数代码行数过多:如果一个文件中代码上千行,在这个文件中极有可能还存在着其他糟糕的情况,增加功能,修改bug恨不得换一个高两米的显示器。不然鼠标滚轮的寿命就要减少好多年。
- 模块之间耦合性过高:这种情况下,全局变量一般会被较多使用。导致各模块互相牵制,改动困难。
2. 重构前:梳理代码
知己知彼,方能百战百胜(可能用在此处不恰当)。代码重构并不是说都能成功,没有良好的准备,很可能导致重构的最终失败。
想要顺利的对项目进行重构,第一步肯定是要了解这个项目。首先可以从代码的总体架构上着手,从全局把控原有的代码的糟心情况,初步评估代码的优化难度,以及我们重构要完成的目标。当然要结合项目的进度情况作出合理的排期。必经产品们关心的是上线时间,而不是代码质量。然后就可以深入阅读代码了。这是一个恶心的过程,耐心就是在这个时候锻炼的。不要走马观花式的读。最好做一下笔记。对原有代码逻辑做一下梳理。形成文档或者是脑图。这为下一步的重构打下坚实的基础,避免产生阻塞性的bug。这个过程可能不是读一遍代码就可以完成的,要多读几遍哦。这真的是一个很重要的过程,不骗你们。最后,为了避免个人的理解误差,要找几个人共同验证你梳理后的内容。拿着你梳理完的文档,对一些重要业务逻辑咨询产品,测试,后端等相关人员,请他们确认你的重构逻辑是没有问题的。这样你才能安心下手,以免后顾之忧。
在重构之前另一个重要的工作就是准备一份测试用例。如果能找到项目的需求文档,对我们的重构能起到事半功倍的效果。如果没有,那我们最好结合页面功能,自己写一份测试用例,为我们重构后的结果评估提供标准。测试工作在重构过程中的作用是如何强调都不为过的。因为即使我们的准备很充分,可以避免引入绝大多数bug,但是我们毕竟是专职写bug的程序员。不能因为重构,而让原先使用起来正常的功能挂掉。这就会给那些反对重构的人一个津津乐道的把柄。
3. 重构中:胆大心细
好的开始是成功的一半。有了前面一步完美的准备,我们就可以开动了。
针对不同情况的源代码,重构的策略也会不同。但是时刻不要忘记我们的初心-重构代码。
在自主可控的情况下,不要对之前的代码结构畏首畏尾。这也不敢动,那也不敢动,我们的重构有什么意义呢。所以重构是需要一定勇气的,不能因为过多的顾虑而放弃改动。
对改动过的代码、功能,不能想当然,认为一定完美,没有bug。重构的最基本要求就是:小步前进、频繁测试。保证重构后的代码不引入新的bug。
下面介绍几个在重构中经常使用到的几个技巧:
- 重新组织函数:
函数可以说是程序的细胞。一个程序肯定是有很多函数组合而成。函数的混乱必然导致程序难以维护和迭代。重构的大部分时间都是在对函数进行整理。
首先是拆分过长的函数,每个人修改这种函数都会无比痛苦。函数中包含各种数据信息,这些信息又被错综复杂的功能逻辑掩盖,不易鉴别。秉着一个函数只做一件事的原则,可以将这种函数拆分成多个独立的、命名良好的小函数。
另一个问题就是函数中的变量,这包括参数、局部变量、全局变量。函数要想保持独立性和良好的复用性,就不应该直接依赖全局变量,而是应该以参数的形式传入。既然是全局变量,肯定不是一个函数使用,这会导致各函数之间紧密耦合,维护困难。重构过程中要尽量减少全局变量。局部变量是函数重构的一个困难源泉,它会驱使你无意识中写出很长的函数,而且使得代码很难被提炼。所以应该尽可能的把局部变量替换为一个查询函数。参数传入函数后就会变成局部变量,所以要减少参数的数量,只传必要的参数。如果参数过多,可以使用对象结构替换。
// 只传必要的参数
function column(width,height,square){
console.log("矩形的宽度为"+width);
console.log("矩形的高度为"+height);
console.log("矩形的面积为"+square);
}
// square可以由width和height计算得到
function column(width,height){
var square = width*height;
console.log("矩形的宽度为"+width);
console.log("矩形的高度为"+height);
console.log("矩形的面积为"+square);
}
- 简化条件表达式
复杂的条件逻辑是最常见的导致程序难以阅读和维护原因之一。每一个条件分支都需要编码做不同的事,随着分支的增加,很快函数也会变得庞大起来。大型函数本身就难以阅读,而条件分支更是加大了阅读难度。
你可以将每个分支中执行的代码分解为多个独立的函数,根据每个函数的作用,合理命名。然后将原函数中的代码该外函数调用。这样不仅可以更清楚的表达自己的意图,而且可以突出条件逻辑,更清楚的表明每个分支的作用和原因。
避免出现嵌套条件表达式。在可能的情况下,替换条件表达式。
// 使用命令模式,替换条件表达式
function(flag){
if(flag==="left"){
move("right");
}else if(flag==="right"){
move("left");
}else if(flag==="top"){
move("bottom");
}else if(flag==="bottom"){
move("top");
}
}
// 替换条件表达式
function moveTo(flag){
command.flag;
}
var command = (function(){
var left = function(){
move("right");
}
var right = function(){
move("left");
}
var top = function(){
move("bottom");
}
var bottom = function(){
move("top");
}
return {
left,right,top,bottom
}
})();
function getPayAmount() {
let result;
if(isDead) {
result = deadAmount();
} else {
if(isSeparated) {
result = separatedAmount();
} else {
if(isRetired) {
result = retiredAmount();
} else {
result = normalPayAmount();
}
}
}
return result;
}
//提前return,简化分支嵌套逻辑
function getPayAmount(){
if(isDead) return deadAmount();
if(isSeparated) return separatedAmount();
if(isRetired) return retiredAmount();
return normalPayAmount();
}
技巧,示例展示的有限,有兴趣进一步了解的同学可以阅读《重构--改善既有代码的设计》。书中详细介绍了重构代码的各种技巧以及示例。
重构更重要的是一种思想,是对代码易阅读,易维护的一种追求。他应该像水和空气一样融入我们的编码过程中,随时随地的重构。关于重构技巧,就是我们编码过程的养成的良好习惯和设计模式的灵活运用。当我们知道哪些编码是有问题的,我们必定回去寻找改变这个问题的方法。就是在这种不断发现问题,解决问题的过程中,提升自我,实现我们的优雅编码的理想。