基于Table方法降低代码圈复杂度

描述:

在项目开发过程中,经常要求圈复杂度不能超过10,有时候写着写着圈复杂度就很大,我在项目代码中见过函数圈复杂度大于100的函数,由于历史的原因,代码越积越多,没人出去重构,导致后面很难懂和维护,所以在编码初期就应该在心中有个要求,就是圈复杂度不能超过10,如果超过10,肯定是代码逻辑写的过于复杂,要回过头来

想想怎么去分解功能,让流程简单易懂。

本文主要通过一些例子来介绍基于Table方式降低圈复杂度的过程。

例子1:一个简单的游戏控制函数

你可能会遇到如下类似的代码:

if(strcmpi(command, "north") == 0) {
    if(cur_location->north)
        GoToLocation(cur_location->north);
    else
        Print("Cannot go there");
}
else if(strcmpi(command, "east") == 0) {
    if(cur_location->east)
        GoToLocation(cur_location->east);
    else
        Print("Cannot go there");
}
else if(strcmpi(command, "south") == 0) {
    if(cur_location->south)
        GoToLocation(cur_location->south);
    else
        Print("Cannot go there");
}
else if(strcmpi(command, "west") == 0) {
    if(cur_location->west)
        GoToLocation(cur_location->west);
    else
        Print("Cannot go there");
}

从上面看到该函数的圈复杂度达到了13,包含了很多分支,不容易理解和维护,后续在往里面添加新的特性也容易出错,可以采用如下的方式进行改善。

修改后:

enum SIDE {SIDE_NORTH = 0, SIDE_EAST, SIDE_SOUTH, SIDE_WEST};
struct COMMAND {
   const char * name;
   SIDE side;
};
static const COMMAND commands[] = {
   {"north", SIDE_NORTH},
   {"east", SIDE_EAST},
   {"south", SIDE_SOUTH},
   {"west", SIDE_WEST},
};
for(int i = 0; i < NUM_OF(commands); i++)
    if(strcmpi(commands[i].name, command) == 0) {
        SIDE d = commands[i].side;
        if(cur_location->sides[d])
            GoToLocation(cur_location->sides[d]);
        else
            Print("Cannot go there");
    }

上面整改让函数的圈复杂度为5,变得非常清楚和易维护。

例子2:计算租一个CD价钱的函数

double result = 0;
switch(movieType) {
   case Movie.REGULAR:
     result += 2;
     if(daysRented > 2)
        result += (daysRented - 2) * 1.5;
     break;

   case Movie.NEW_RELEASE:
     result += daysRented * 3;
     break;

   case Movie.CHILDRENS:
     result += 1.5;
     if(daysRented > 3)
        result += (daysRented - 3) * 1.5;
     break;
}

修改后的版本:

enum MovieType {Regular = 0, NewRelease = 1, Childrens = 2};

                             // Regular   NewRelease   Childrens
const double initialCharge[] = {2,             0,        1.5};
const double initialDays[] =   {2,             0,          3};
const double multiplier[] =    {1.5,           3,        1.5};

double price = initialCharge[movie_type];
if(daysRented > initialDays[movie_type])
    price += (daysRented - initialDays[movie_type]) * multiplier[movie_type];

其实也可以采用继承的方式来解决上面的许多switch case分支,用一个类来介绍 regular 价格, 另外一个类 new releases价格, 一个类计算 children‘s movie价格。

例子3:判断字符和数字

有时需要判断一个字符是否为数字或者大小写,我们经常会使用如下的方法:

int isalnum(int ch) {
    return 'a' <= ch && ch <= 'z' ||
           'A' <= ch && ch <= 'Z' ||
           '0' <= ch && ch <= '9';
}

但是在C的运行库中判断是否为数字和 字母采用了如下的方式,如ctype.h中

static const unsigned char properties[] = {
      0,  0,  0,  0,  0,  0,  0,  0,  0, 16, 16, 16, 16, 16,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
     16,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,
    204,204,204,204,204,204,204,204,204,204,160,160,160,160,160,160,
    160,202,202,202,202,202,202,138,138,138,138,138,138,138,138,138,
    138,138,138,138,138,138,138,138,138,138,138,160,160,160,160,160,
    160,201,201,201,201,201,201,137,137,137,137,137,137,137,137,137,
    137,137,137,137,137,137,137,137,137,137,137,160,160,160,160,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
};
#define islower(ch)  (properties[ch] & 0x01)
#define isupper(ch)  (properties[ch] & 0x02)
#define isdigit(ch)  (properties[ch] & 0x04)
#define isalnum(ch)  (properties[ch] & 0x08)
#define isspace(ch)  (properties[ch] & 0x10)
#define ispunct(ch)  (properties[ch] & 0x20)
#define isxdigit(ch) (properties[ch] & 0x40)
#define isgraph(ch)  (properties[ch] & 0x80)

如果需要存储更少的信息,可以采用位数组,但是需要更多的操作去检索该值,如下:

inline int isalnum(int ch) {
    static const unsigned int alnum[] = {
        0x0, 0x3ff0000, 0x7fffffe, 0x7fffffe, 0x0, 0x0, 0x0, 0x0,
    };
    return (alnum[ch >> 5]) & (1 << (ch & 31));
}

从例子3中可以看出,善于利用数组可以有效减少程序的复杂度,有时候还能提高执行效率,不过在开发过程中还是要以可维护性和可理解性为主,除非是在关键路径上需要考虑性能指标。

时间: 2024-10-18 01:18:23

基于Table方法降低代码圈复杂度的相关文章

前端代码质量-圈复杂度原理和实践

写程序时时刻记着,这个将来要维护你写的程序的人是一个有严重倾向,并且知道你住在哪里的精神变态者. 导读你们是否也有过下面的想法? 重构一个项目还不如新开发一个项目...这代码是谁写的,我真想...你们的项目中是否也存在下面的问题? 单个项目也越来越庞大,团队成员代码风格不一致,无法对整体的代码质量做全面的掌控没有一个准确的标准去衡量代码结构复杂的程度,无法量化一个项目的代码质量重构代码后无法立即量化重构后代码质量是否提升针对上面的问题,本文的主角 圈复杂度 重磅登场,本文将从圈复杂度原理出发,介

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

软件重构是改善代码可读性.可扩展性.可维护性等目的的常见技术手段.圈复杂度作为一项软件质量度量指标,能从一定程度上反映这些内部质量需求(当然并不是全部),所以圈复杂度往往被很多项目采用作为软件质量的度量指标之一. C语言开发的项目中,switch/case代码块是一个很容易造成圈复杂度超标的语言特性,所以本文主要介绍下降低switch代码段的重构手段(如下图).switch圈复杂度优化重构可分为两部分:程序块的重构和case的重构.程序块重构是对代码的局部优化,而case重构是对代码的整体设计,

圈复杂度(Cyclomatic Complexity)

圈复杂度(Cyclomatic Complexity)是很常用的一种度量软件代码复杂程度的标准.这里所指的"代码复杂程度"并非软件内在业务逻辑的复杂程度,而是指代码的实现方式的 复杂程度.说起来有点绕是么?打个比方就是嘴笨的人可以把简单的事情说得很复杂.虽然"内在业务逻辑"与"实现方式"有紧密的联系,有很多研究统计指出, 圈复杂度高的代码中错误的可能性更大,维护的成本更高. 它的计算方法很简单,计算公式为:V(G)=e-n+2.其中,e表示控制流

【软件测试】圈复杂度

圈复杂度:在软件测试的概念里,它用来衡量一个模块判定结构的复杂程度,数量上表现为线性无关的路径条数,即 合理的预防错误所需测试的最少路径条数. 圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系. 圈复杂度的原理:(其实就是分支的个数) 圈复杂度为1,意味着代码只有一条路径. 对于有一条分支的代码,它的圈复杂度为2. 从1开始,一直往下经过程序. 一旦遇到以下关键字,或者其它同类的词,就 +1. 如 if.while.repeat.for.and

让代码重构渐行渐远系列(2)——降低代码重复度

降低代码重复度:我所谓的代码重复指的不仅仅是多行一模一样的代码,同时也包括一些重复复制或是没必要的一些代码,如以下下代码: 1 public void 重复代码示例(int? 参数一) 2 { 3 if (参数一 != null) 4 { 5 //处理代码.... 6 } 7 else 8 { 9 参数一 = null; 10 //处理代码..... 11 } 12 13 //处理代码..... 14 15 } 可能有的朋友在第一眼看到这段代码时会说,这个没什么问题啊 ,哪有什么重复呀?真的就没

圈复杂度和McCabe

[书名]:软件架构--Python语言实现 [主题]:圈复杂度 [摘要]:圈复杂度(Cyclomatic Complexity)是衡量计算机程序复杂程度的一种措施.它根据程序从开始到结束的线性独立路径的数量计算得来的.在 Python 中可以使用 mccabe 包测量程序的圈复杂度. 1 圈复杂度 对于没有任何分支的代码,它的圈复杂度为 1 ,意味着代码只有一条路径.例如下面的函数: 1 2 3 def (a, b): return a + b 对于有一条分支的代码,它的圈复杂度为 2 ,比如下

圈复杂度

一.现象 1. 代码设计不规范. 当项目规模达到一定的程度,比如达到十万行的代码量.那么项目肯定存在有些类特别大,方法特别多.特别长. 以上因素会导致什么后果呢? 一个类没有做到单一指责,后期对这个类改动会导致其他功能出现Bug. 代码阅读性较差,维护困难. 2. 没有一个准确的标准去衡量代码结构复杂的程度. 各个公司都会有自己的规范,但是开发中很少人能够去完全遵循规范.而且没有一个明确的标准去衡量代码的复杂程度,而且人工去检测代码的复杂程度是很繁琐的.因此我们急需一个标准去检测代码结构复杂的程

基于word分词提供的文本相似度算法来实现通用的网页相似度检测

实现代码:基于word分词提供的文本相似度算法来实现通用的网页相似度检测 运行结果: 检查的博文数:128 1.检查博文:192本软件著作用词分析(五)用词最复杂99级,相似度分值:Simple=0.968589 Cosine=0.955598 EditDistance=0.916884 EuclideanDistance=0.00825 ManhattanDistance=0.001209 Jaccard=0.859838 JaroDistance=0.824469 JaroWinklerDi

基于lanzcos方法的特征值分解算法原理及C++实现(一):lanzcos方法原理以及简单实现

假设m阶实对称阵X的低秩特征值分解为 , 其中U是列正交矩阵,即 ,每一列为一个特征向量,S是对角阵,对角线上每个元素为特征值.r为分解的秩 lanzcos方法的大致思路 1) 对X进行正交变换得到一个三对角阵T:,其中 \alpha_0 & \beta_1 & 0 & 0 & \dots \\ \beta_1 & \alpha_1 & \beta_2 & 0 & \dots \\ 0 & \beta_2 &\alpha_3