点击上方“中兴开发者社区”,关注我们

每天读一篇一线开发者原创好文

▍作者简介

作者陈彬是一名Linux驱动开发工程师,对Linux内核、软件设计和敏捷有较浓厚的兴趣和长期实践经验。自从项目建立起代码质量规范和监控工具后,如何重构高圈复杂度的代码,特别是switch/case的圈复杂度,是同事间比较普遍的一个问题,这正是写本篇文章的起因。希望这篇文章对大家能有所启发。

C语言switch/case圈复杂度优化重构

软件重构是改善代码可读性、可扩展性、可维护性等目的的常见技术手段。圈复杂度作为一项软件质量度量指标,能从一定程度上反映这些内部质量需 求(当然并不是全部),所以圈复杂度往往被很多项目采用作为软件质量的度量指标之一。

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

▍程序块重构

程序块重构指的是每个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, Don’t Ask”原则,告诉对象需要做什么,而不是怎么做。

▍case重构

对于一个switch有几十个case的情况,其圈复杂度往往上百,程序块重构显然已不能解决其本质复杂度。

如果要降低其圈复杂度,必然需要对代码进行 重新设计。

C语言的switch/case语言特性本质是描述一种查表逻辑,其中表结构和表的控制(即查表)都通过软件来表达。

表通过代码来描述,这显然不是一种最 佳的实现方式。我们需要做的就是,避免控制中的复杂性,将精力集中在数据的组织上,以反映所模拟世界的真实结构,并将数据与控制进行分离。

表的设计由两部分组成:对象(表项)的抽象和表的构建。

对象如何抽象,对象粒度如何划分,对象间的关系如何设计?这些问题涉及抽象思维能力的 训练,而且也与具体业务逻辑强相关,不是本文重点。

读者可阅读《 计算机程序的构造和解释 》来进一步了解软件抽象等相关技术细节。

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

▍编译期表构建

问题背景

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

重构方法

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

▍运行时表构建

问题背景

内核模块通过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控制码的处理接口。

示例代码

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

▍ 链接期表构建

问题背景

编译期表构建和运行时表构建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,不需要修改任何公共代码或数据。

示例代码

c语言圈复杂度switch,干货|C语言switch\/case圈复杂度优化重构相关推荐

  1. c语言圈复杂度switch,C语言switch/case圈复杂度优化重构

    软件重构是改善代码可读性.可扩展性.可维护性等目的的常见技术手段.圈复杂度作为一项软件质量度量指标,能从一定程度上反映这些内部质量需求(当然并不是全部),所以圈复杂度往往被很多项目采用作为软件质量的度 ...

  2. R语言随机森林模型:计算随机森林模型的特征重要度(feature importance)并可视化特征重要度、使用少数重要特征拟合随机森林模型(比较所有特征模型和重要特征模型在测试集上的表现差异)

    R语言随机森林模型:计算随机森林模型的特征重要度(feature importance)并可视化特征重要度.使用少数重要特征拟合随机森林模型(比较所有特征模型和重要特征模型在测试集上的表现差异) 目录

  3. R语言使用apriori算法进行关联规则挖掘实战:关联规则概念、频繁项集、支持度(support)、置信度(confidence)、提升度(lift)、apriori算法

    R语言使用apriori算法进行关联规则挖掘实战:关联规则概念.频繁项集.支持度(support).置信度(confidence).提升度(lift).apriori算法 目录

  4. c语言 switch案例,C语言程序设计 2. switch语句的运用 案例 switch条件语句.docx

    C语言程序设计补充案例 C语言程序设计 案例 switch条件语句 一.案例描述 考核知识点 名称:switch条件语句 练习目标 了解switch语句的执行流程 掌握switch语句的使用方法及使用 ...

  5. c++宏定义常量为什么使用移位_干货 | C语言系列3——常量,运算符,常用数学函数.........

    本期总结 1.符号常量和const常量: 2.运算符: 3.输入与输出: 4.常用数学函数. 往期回顾 干货 | C语言系列1--编程软件与做题平台 干货 | C语言系列2--程序结构与变量 符号常量 ...

  6. c语言出圈游戏课设报告,c语言作业 出圈游戏

    #include//头函数 #include #include #define SIZE 100 struct SeqList{ int num; char name[10]; }; int menu ...

  7. 思考的梯子 | 黄金圈法则What-How-Why(超干货)

    「思考的梯子」系列,第二篇,强烈安利你认真看完,我也花了点时间做PPT配图,因为「黄金圈法则」我几乎每个需求都在使用,是我认为每个从事策划工作的职场人应该学会的思考方式! 「黄金圈」有啥用? 相信你遇 ...

  8. c语言怎样统计数组的长度,C语言指针难吗?纸老虎而已,纯干货讲解

    原标题:C语言指针难吗?纸老虎而已,纯干货讲解 指针对于C来说太重要.然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识.所以本文尽可能的通过一篇文 ...

  9. switch if c语言效率,switch与ifelse的效率问题

    switch...case与if...else的根本区别在于,switch...case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的.从而,swi ...

最新文章

  1. FEMS综述: 如何从微生物网络中的“毛线球”理出头绪(3万字长文带你系统学习网络)...
  2. jupyter notebok 调用本目录下的其他py文件
  3. datatable数据类型方法
  4. jupyter notebook代码导出_Jupyter Notebook导出包含中文的pdf_亲测有效
  5. 注意Entity中的Uptate
  6. 降低关系型数据库的逻辑复杂
  7. php兴趣爱好复选框如何取值,php checkbox 取值详细说明
  8. 深度学习基础(二)—— 从多层感知机(MLP)到卷积神经网络(CNN)
  9. 华三防火墙配置端口地址转换_H3C防火墙配置命令
  10. 关于ObjectProxy
  11. 高斯消元法求解线性方程组
  12. 北京精雕现状_北京精雕科技有限公司怎么样?
  13. html平行四边形标签,平行四边形
  14. ueditor 前后端分离+图片上传
  15. 软件需求分析(第九章)
  16. android 蓝牙sco开发
  17. 搭建网站的步骤有哪些?
  18. ubuntu12.04将tgz文件解压到指定目录
  19. 阿里云Linix上 python脚本 发邮件发送不出去问题
  20. GitLab 搭建 群组Runner

热门文章

  1. PART1 WebPack
  2. 最长有效括号的长度问题
  3. Xcode抓包ios
  4. 设计模式 - 抽象文档
  5. lua中自定义加载lua的path
  6. Postgresql数据库javaweb教程详细完整版(一)
  7. 大溃败!阿里最大股东轰然倒下!!
  8. 什么是肉鸡服务器,黑客说的肉鸡正确解释
  9. flume源码分析2--配置文件的加载
  10. OpenNI(开放自然交互)是一个多语言,跨平台的框架