当我们梳理自己或别人的代码时,很可能遇到如下情况:
当然不会如此夸张,但当程序员遇到类似的代码时大概率会抓狂,由此会想到代码重构,但当到最后一步时就没必要重构了,需要重写了。
何为重构?
代码重构(Code refactoring)重构就是在不改变软件系统外部行为的前提下,改善它的内部结构。
简单来说重构就是将原来混乱如麻的代码梳理清除并通过封装、解耦等一系列操作使其变得条理清晰起来,同时不影响原功能的运行。
何种情况下需要进行代码重构及解决方法?
1、变量方法、类等命名不规范时
一个较好的命名应做到见名知意,即通过命名就知道该变量(方法、类)是用来做什么的,而不是随意命名。常用的命名方式有三种:
1、小驼峰命名法(camei)
第一个单词以小写字母开始;第二个单词的首字母大写,例如:myFirstName、myLastName
2、大驼峰命名法(Upper Camel Case)
每一个单词的首字母都采用大写字母,如:MyFirstName、MyLastName,常用于类名的命名
3、匈牙利命名法
基本原则是:变量名=属性+类型+对象描述。
属性部分:
g_ 全局变量 c_ 常量 m_ c++类成员变量 s_ 静态变量
类型部分:
数组 a 指针 p 函数 fn 无效 v 句柄 h 长整型 l 布尔 b 浮点型(有时也指文件) f
双字 dw 字符串 sz 短整型 n 双精度浮点 d 计数 c(通常用cnt) 字符 ch(通常用c)
整型 i(通常用n) 字节 by 字 w 实型 r 无符号 u
描述部分:
最大 Max 最小 Min 初始化 Init 临时变量 T(或Temp) 源对象 Src 目的对象 Dest
举例
hwnd : h 是类型描述,表示句柄, wnd 是变量对象描述,表示窗口,所以 hwnd 表示窗口句柄;
pfnEatApple : pfn 是类型描述,表示指向函数的指针, EatApple 是变量对象描述,所以它表示指向 EatApple 函数的函数指针变量。
g_cch : g_ 是属性描述,表示全局变量,c 和 ch 分别是计数类型和字符类型,一起表示变量类型,这里忽略了对象描述,所以它表示一个对字符进行计数的全局变量。 上面就是HN命名法的一般规则。
2、重复代码
当一段代码重复多次出现在不同地方实现相同的代码逻辑时就应该考虑了代码重构了,可将重复的代码提取为 一个公共方法,删除源代码的同时并在原来的位置调用。
3、函数过长
有大佬曾讲过,但凡是一个函数如果超过50行以上的代码,就应该开始考虑进行重构它。
当函数过长时函数的复杂度会变高,导致影响代码的可读性,会造成维护时的困难程度增高。
故而应将过长的函数进行拆分,将相对独立的逻辑拆分出去,但不能将函数拆分的过于细碎,这反而会导致复杂度的增高。
4、函数参数过多
当封装或声明一个函数时可能会遇到需要传入参数过多的情况,这可能会导致未来在调用此函数时因为参数顺序不对产生一些问题,此时我们要做的是是将多个参数合并为一个对象,通过传递对象来减少函数需要传递的参数。
5、过多的循环语句
在许多业务场景中,循环语句都是不必要的,故尽量减少for循环的使用,可通过filter、map、forEach等方法来代替循环,当一个函数中循环嵌套过多时时间复杂度会显著增加,如若必须使用则可通过break或continue关键字中断循环,也可通过return关键字直接中断函数的调用减少循环次数。
6、过多的if...else(switch)分支
在一个系统或功能的开发过程中,刚开始自己写的代码很简洁,逻辑清晰,函数精简,没有一个 if-else,可随着代码逻辑不断完善和业务的瞬息万变:比如需要对入参进行类型和值进行判断;这里要判断下变量是否为空;不同类型是否要执行不同的流程。这时大概率就会选用if...else语句来进行逻辑判断,但随着时间延长,原来单一的if...else语句会变得分支越来越多, 复杂度越来越高,代码可读性越来越低,这就造成了屎山代码的堆积,后期维护就变得困难起来。所以就应该避免该情况的发生。
该如何避免此种情况呢?
在进行if...else语句的书写时要做到仅出现在主干代码之中,避免嵌套过深。解决的具体方案有:合并条件表达式、减少嵌套(分支数)、条件取反判断、移除或减少临时变量。
合并条件表达式
/*重构前*/
function test (num) {
if(num<0) return 0;
if(num>12) return 0;
if(num==9) return 0;
}
/*重构后*/
function test (num) {
if(num<0 || num>12 || num==9) return 0;
}
减少嵌套(分支数)
/*重构前*/
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12: {
return [
moment(`${year}-${month}-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-${month}-31 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
];
}
case 4:
case 6:
case 9:
case 11: {
return [
moment(`${year}-${month}-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-${month}-30 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
];
}
case 2: {
if ((year % 4 === 0 && year % 100 != 0) || year % 400 === 0)
return [
moment(`${year}-02-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-02-29 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
];
else {
return [
moment(`${year}-02-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-02-28 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
];
}
}
}
/*重构后*/
let monthTypeObj = {
big: [1, 3, 5, 7, 8, 10, 12],
normal: [4, 6, 9, 11],
};
let monthDateValueObj = {
big: [
moment(`${year}-${month}-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-${month}-31 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
],
normal: [
moment(`${year}-${month}-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-${month}-30 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
],
};
for (const monthTypeObjKey in monthTypeObj) {
if (monthTypeObj[monthTypeObjKey].includes(month)) {
return monthDateValueObj[monthTypeObjKey];
}
}
if ((year % 4 === 0 && year % 100 != 0) || year % 400 === 0)
return [
moment(`${year}-02-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-02-29 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
];
else {
return [
moment(`${year}-02-01 00:00:00`, 'yyyy-MM-DD 00:00:00'),
moment(`${year}-02-28 23:59:59`, 'yyyy-MM-DD 23:59:59')
.hour(23)
.minute(59)
.second(59),
];
}
7、减少三目运算符的使用
当分支逻辑简单,条理清晰时,三目运算符是可以清晰的完成需求的,担当条件逻辑复杂时就不要使用三目运算符了,这时使用三目运算符会使代码不好理解且难于维护,如下:
if ( !aup || !bup ) {
return a === doc ? -1 :
b === doc ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
0;
}
具体的代码重构方法还有很多,在此就不再赘余了,一切的代码重构都是基于项目和具体情况的,要按照具体的情景实行相应的代码重构,不符合情景时就不要进行代码重构了。