目录
一,函数重构方法
1,条件合并
最近在重构函数,要求圈复杂度不超过7。
遇到这么一种代码:
if (A && B && C) {
do {
sth1;
}
}
if (B && C) {
do {
sth2;
}
}
我改成了
if (B && C) {
if (A) {
do {
sth1;
}
}
do {
sth2;
}
}
圈复杂度直接降低2
2,变量排查
拆分函数的时候,对于拆出来得到的新函数,
要看里面的所有变量是什么类型的变量,是否有定义。
按照变量所写的位置,分为几种:
普通变量、数组下标变量、小数点或->后面的变量、宏里面的变量,
当然,宏里面的变量也分为普通变量、数组下标变量、小数点或->后面的变量,
对于小数点或->后面的变量可以不用管,对于普通变量、数组下标变量,需要注意是否有定义。
如果是全局变量或宏定义的常量,那就是有定义,如果没有定义,那就有2种处理方式:
要么传参,要么直接在新函数中定义。
传参是为了传值进来或者传值出去,而有的临时变量,既不会传入,也不会传出,就不用传参。
临时变量的生命周期拆分:
有的函数是这么写的:
int x;
x=fun1();
y=f(x);
x=fun2();
z=f(x);
如果要把这个代码拆成2个函数,对于x这个变量,其实是可以拆开的,也就是说,上述代码等价于
int x;
x=fun1();
y=f(x);
int xxx
xxx=fun2();
z=f(xxx);
这样,拆开成2个函数之后,就不用把第一个函数计算出来的x的值传到第二个函数。
总结:
(1)注意读宏
(2)小数点或->后面的变量可以不用管,全局变量或者宏定义的常量也不用管,宏里面的变量和其他变量要么传参,要么直接在新函数中定义。
(3)传参是为了传值进来或者传值出去,而有的临时变量,既不会传入,也不会传出,就不用传参。
(4)形如上述的代码中,临时变量的生命周期可拆分,避免冗余的参数传递。
3,圈复杂度计算
我司系统计算的圈复杂度,貌似是这个逻辑:
起步是1,然后if算1个,else算1个,如果是else if只算1个,
case算1个,default算1个,空case不算,空default算。
这个计算逻辑,和我想象中差别挺大的。
更新:
switch语句的圈复杂度计算方法应该是,有多少个break就算多少个。
4,超多case的switch语句
如果一个switch有case1,case2......case20,
首先确认下有没有哪个case修改了枚举值,原则上case语句不应该修改枚举值的,不过还是要以防万一。
再确认下是不是所有非空case都带了break,如果有case不带break,那么这个case执行之后还要执行default语句。
如果没有case修改枚举值,而且,所有非空case都带了break或者default语句是空的,那么所有case可独立判断执行,也就是说可以以任意组合方式拆开成若干个switch语句,任意顺序排序。
二,重构:改善既有代码的设计
1,重构的原则
何为重构:对软件内部结构的一种调整,在不改变软件可观察行为的前提下,提高可理解性,降低修改成本。
为何重构:重构改进软件的设计,重构使软件更容易理解,重构帮助找到BUG,重构提高编程速度。
何时重构:事不过三,三则重构。
2,代码坏味道
神秘命名、重复代码、过长函数、过长参数列表、全局数据、可变数据、发散式变化、霰弹式修改、依恋情结、数据泥团、基本类型偏执、重复switch、循环语句、冗赘的元素、夸夸其谈通用性、临时字段、过长的消息链、中间人、内幕交易、过大的类、异曲同工的类、纯数据类、被拒绝的遗赠、注释
3,构筑测试体系
先写测试用例,现有代码测试通过,然后再重构,小步快跑,每跑一步就可以测一下。
4,重构方法
- 常用手法:提炼函数、内联函数、提炼变量、内联变量、改变函数声明、封装变量、变量改名、引入参数对象、函数组合成类、函数组合成变换、拆分阶段。
- 封装:封装记录、封装集合、以对象取代类型、以查询取代临时变量、提炼类、内联类、隐藏委托关系、移除中间人、替换算法
- 搬移特性:搬移函数、搬移字段、搬移语句到函数、搬移语句到调用者、以函数调用取代内联代码、移动语句、拆分循环、以管道取代循环、移除死代码
- 重新组织数据:拆分变量、字段改名、以查询取代派生变量、将引用对象改为值对象、将值对象改为引用对象
- 简化条件逻辑:分解条件表达式、合并条件表达式、以卫语句取代嵌套条件表达式、以多态取代条件表达式(提炼函数)、引入特例、引入断言
- 重构API
- 处理继承关系
三,编程规范
1,调用函数时前面为什么要加(void)
调用函数时前面加(void),是为了显式指明,程序不处理函数返回值。
这是一种较好的编程规范,增加可读性之类的好处。