c语言项目重构java,C语言switch/case圈复杂度优化重构

软件重构是改善代码可读性、可扩展性、可维护性等目的的常见技术手段。圈复杂度做为一项软件质量度量指标,能从必定程度上反映这些内部质量需求(固然并非所有),因此圈复杂度每每被不少项目采用做为软件质量的度量指标之一。html

C语言开发的项目中,switch/case代码块是一个很容易形成圈复杂度超标的语言特性,因此本文主要介绍降低低switch/case圈复杂度的重构方法(以下图)。switch圈复杂度优化重构可分为两部分:程序块的重构和case的重构。程序块重构是对代码的局部优化,而case重构是对代码的总体设计,所涉及的重构手段也各不相同。数组

a98328b87f4c48d3b44670f231eaa59a.gif

程序块重构

程序块重构指的是每一个case内的代码段重构。Martin Fowler 的《重构——改善既有代码的设计》(电子版)书中总结了80多种重构方法。书中针对每种技术都给出了示例说明,另外这里、这里还提供了其余语言的示例和进一步介绍。由于存在大量示例,因此本文针对这些方法再也不给出示例,有兴趣的同窗能够经过上面几种途径了解学习。不过这些技术中有些是改善代码的可读性,有些是改善代码的可扩展性,并非每项技术都能有效减低圈复杂度。其中能够下降圈复杂度的方法有以下几种:数据结构

提炼函数(Extract Method)。你有一段代码能够被组织在一块儿并独立出来。将这段代码放进一个独立函数中,并将函数名称解释该函数的用途。

分解条件表达式(Decompose Conditional)。你有一个复杂的条件(if-then-else)语句。从if、then、else三分段落中分别提炼出独立函数。

合并条件表达式(Consolidate Conditional Expression)。你有一系列条件测试,都获得相同结果。将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。

合并重复的条件片断(Consolidate Duplicate Conditional Fragments)。在条件表达式的每一个分支上有着相同的一段代码。将这段重复的代码搬移到条件表达式以外。

移除控制标记(Remove Control Flag)。在一系列布尔表达式中,某个变量带有“控制标记”的做用。以break语句或return语句取代控制标记。

这些重构方法除了下降圈复杂度外,还有以下好处:函数

知足单一职责设计原则,提升代码可读性。

去除重复冗余代码。你能够删除大量相同的条件语句。

知足“Tell, Dont Ask”原则,告诉对象须要作什么,而不是怎么作。

case重构

对于一个switch有几十个case的状况,其圈复杂度每每上百,程序块重构显然已不能解决其本质复杂度。若是要下降其圈复杂度,必然须要对代码进行从新设计。学习

C语言的switch/case语言特性本质是描述一种查表逻辑,其中表结构和表的控制(即查表)都经过软件来表达。表经过代码来描述,这显然不是一种最佳的实现方式。咱们须要作的就是,避免控制中的复杂性,将精力集中在数据的组织上,以反映所模拟世界的真实结构,并将数据与控制进行分离。测试

表的设计由两部分组成:对象(表项)的抽象和表的构建。对象如何抽象,对象粒度如何划分,对象间的关系如何设计?这些问题涉及抽象思惟能力的训练,并且也与具体业务逻辑强相关,不是本文重点。读者可阅读《计算机程序的构造和解释》来进一步了解软件抽象等相关技术细节。优化

表的构建方法是本文的重点,其可分为编译期构建、连接期构建和运行时构建。3种方法各有所长和不足,可根据自身须要进行选择。.net

编译期表构建

问题背景

boot启动支持3种启动方式,每种启动方式的用户菜单流程也不尽相同。启动菜单支持输入检查、存储、菜单回退等功能。原有设计中函数设计臃肿,菜单项经过switch/case来进行选择处理,有十几个函数圈复杂度超过40,最大的圈复杂度为147,代码维护困难。设计

重构方法

boot启动用户菜单本质是一个优先状态机,每一个菜单项是其中一个状态。抽象菜单项对象T_PROMT,其包含提示打印、输入检查、存储、状态跳转等成员。构建T_PROMT aPromtArray[]菜单表描述全部菜单项对象,经过MenuFsm实现状态机的控制:经过对象T_PROMT的jumpto接口实现状态的跳转,经过check接口实现输入检查,经过setvalue接口实现存储,经过parent实现菜单回退到上级菜单(由于上级菜单是动态变化的,没法静态初始化,因此在jumpto中进行动态赋值)。示例代码以下:htm

typedef struct prompt

{

WORD32 type;

CHAR *name;/*env name*/

CHAR *prompt;/*prompt info to user*/

WORD32 (*check)(CHAR *src);/*check func for user's input*/

struct prompt* (*jumpto)(struct prompt*, WORD32);

struct prompt *parent;

VOID (*setvalue)(CHAR *name);

}T_PROMT;

static T_PROMT aPromtArray[] =

{

/* env name prompt string check func jump func parent set func */

{TYPE_NORMAL, ENV_LOCAL_IP, "Local IP:", CheckIpAddr, LocalIpJump ,NULL, SetCltIpAddr },

{TYPE_NORMAL, ENV_SERVER_IP, "Server IP:", CheckIpAddr, ServeripJump ,NULL, SetSerIpAddr },

/* 共 22 个表项,如下略 */

};

static SWORD32 MenuFsm(struct prompt *menu)

{

SWORD32 dwRet = BSP_OK;

WORD32 dwIndex;

while(menu != NULL) {

if (menu == GetPrompt(ENV_NULL)) {

dwRet = MODE_MENU_BACK;

break;

}

dwIndex= PrintPromptAndGetUserInput(menu);

if (dwIndex != NORMAL_MENU_BACK ) {

menu = menu->jumpto(menu, dwIndex);

} else {

menu = menu->parent;

}

}

return dwRet;

}

static struct prompt* GetPrompt(char *name)

{

WORD32 i = 0;

struct prompt *pt = NULL;

WORD32 dwSize = sizeof(aPromtArray)/sizeof(aPromtArray[0]);

for (i = 0; i < dwSize; i++) {

if (strcmp(name, aPromtArray[i].name) == 0) {

pt = &aPromtArray[i];

break;

}

}

return pt;

}

运行时表构建

问题背景

内核模块经过ioctl对外部提供接口,而此模块ioctl控制码有84个,原ioctl函数经过switch/case完成ioctl的分发和处理,此实现方案致使函数代码长度达767行,圈复杂度达124,难以维护,不知足项目软件质量要求(函数圈复杂度在12如下)。

重构方法

抽象ioctl接口对象ctrl_operations并实例化;经过bsp_iocmds_init构建字典(哈希表),实现ioctl控制码到ioctl接口的映射;在board_dev_init模块初始化中完成哈希表的初始化;在boardctrl_do_ioctl中经过哈希查表接口bsp_dict_get获取ioctl控制码的处理接口。

示例代码

struct ctrl_operations {

SWORD32 (*board_init)(struct board *bd);

SWORD32 (*board_exit)(struct board *bd);

/* 共 92 个表项,如下略 */

};

struct ctrl_operations ioctl_ops = {

.inherits = &extern_ops,

.epld_op = bsp_epld_op,

.epldrw = bsp_epld_rw,

/* 共 84 个字段,如下略 */

};

void bsp_iocmds_init(struct board *bd, pt_bsp_dict pdict)

{

bsp_dict_add(pdict, BSP_IOCMD_ROV_WR, bd->ops->rov_wr);

bsp_dict_add(pdict, BSP_IOCMD_TCAM_INFO, bd->ops->tcam_info);

/* 共 84 个key,如下略 */

}

static SWORD32 __init board_dev_init(void)

{

struct board *bd = get_board();

/* 删除无关代码 */

bd->iocmds = bsp_dict_new(DICT_HINT, bsp_cmp, bsp_hash);

bsp_iocmds_init(bd, bd->iocmds);

return BSP_OK;

}

WORD32 boardctrl_do_ioctl(unsigned int cmd, void *pParam)

{

WORD32 dwIoNum = _IOC_NR(cmd);

struct board *bd = get_board();

WORD32 dwRet = BSP_E_BRDCTRL_NOTSUPPORT;

PT_OPS_FUNC ops;

ops = bsp_dict_get(bd->iocmds, dwIoNum);

if(likely(ops)) {

dwRet = ops(bd, pParam);

}

return dwRet;

}

固然除了使用哈希表,也可使用链表等数据结构来组织数据。

连接期表构建

问题背景

编译期表构建和运行时表构建2种方法,能优化设计,下降圈复杂度,但有一件事情没有作完美:新增一个表项时,必须修改公共的静态表(编译期表构建,如须要修改aPromtArray)或注册函数(运行时表构建,如须要修改bsp_iocmds_init),没法作到彻底知足“开发封闭原则”。

连接期表构建方法则能够解决这个问题。

重构方法

经过gcc的section属性,把全部(ioctl控制码,接口)数据对(即元组)定义在同一个section数据段中。在连接阶段,连接器会构建初始化此section数据段,话句话说,链接器帮助咱们完成了这个对象数组的初始化和构建。而后利用gcc导出的__start_ctrl_op_section和__stop_ctrl_op_section符号,boardctrl_do_ioctl便可完成对section数据表的查表操做。

此项技术在u-boot、Linux kernel中大量使用。当添加一个新表项时,只须要添加一句ctrl_op_init,不须要修改任何公共代码或数据。

示例代码:

typedef void (*ctrl_op)(struct board *bd);

#define _init __attribute__((section("ctrl_op_section")))

#define ctrl_op_init(num, func) ctrl_op __no_##func _init = (ctrl_op)num; \

ctrl_op __fn_##func _init = func

extern ctrl_op __start_ctrl_op_section;

extern ctrl_op __stop_ctrl_op_section;

ctrl_op_init(BSP_IOCMD_ROV_WR, bsp_rov_wr);

ctrl_op_init(BSP_IOCMD_TCAM_INFO, bsp_tcam_info);

/* 共 84 个ctrl_op_init,如下略 */

WORD32 boardctrl_do_ioctl(unsigned int cmd, void *pParam)

{

WORD32 dwIoNum = _IOC_NR(cmd);

struct board *bd = get_board();

WORD32 dwRet = BSP_E_BRDCTRL_NOTSUPPORT;

ctrl_op * ptr = &__start_ctrl_op_section;

do {

if((WORD32)*ptr == dwIoNum) {

ptr++;

if(likely(ptr))

return (*ptr)(bd, pParam);

}

ptr += 2;

} while (ptr < &__stop_ctrl_op_section);

return dwRet;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值