C语言代码评审小结

概述
在实际的软件开发项目中,代码评审是一个必不可少的流程。代码评审,也称之为代码复查,是指通过阅读开发人员所写的代码来检查源代码与编码规范的符合性以及代码质量的活动。总的说来,代码评审的好处有以下几点:
第一,发现程序问题,提高代码质量。
第二,理清代码逻辑,开阔编程思路。
第三,促进团队交流,提升开发技能。

代码评审的大体流程是这样的:
第一步,团队负责人(通常是开发经理)提前预定好会议室,并通知参与代码评审的人员,让他们做好准备。
第二步,在评审会上,讲解员大声阅读被评审的代码,评审人员就代码的问题给出自己的看法和意见,记录员再将这些意见记录下来。
第三步,评审结束之后,团队负责人将评审记录以邮件的形式发出来,并督促被评审代码的编写者按照评审意见对代码进行修改。

代码评审小结
最近,我参加了几个C语言代码的评审会,发现了代码中一些共同存在的问题,特总结下来,供相关的开发人员参考。

1.添加、修改、删除代码的时候缺少了注释说明。
在很多开发人员看来,注释是可有可无的东西,只要实现了程序的功能即可。实际上,这种想法是不对的。对于缺少必要注释的代码,在刚编写的时候,还能够知道每行代码的作用,但是当经过了多个版本的演进之后,不要说其他人,就算是作者本人,也未必知道当初自己为何要这么编写代码。当你接手缺少注释而读起来很吃力的代码的时候,有一种想把代码原作者拉过来打一顿的冲动。不管你又没有,反正我是有的。

因此,不管工作有多忙,在编写代码的同时,我们一定要在适当的地方添加适当的注释,这也是对一个合格的编程人员的基本要求。

2.在代码关键分支处缺少了日志信息,不便于分析和排查问题。
很多代码在“return”语句之前或else分支处没有日志信息,这使得跟踪代码流程变得比较困难,在程序出现故障的时候不便于分析和排查问题。

例如,如下代码:

if (var1 > 0)
{
    return;
}
if (var2 > 0)
{
    return;
}
if (var3 > 0)
{
    return;
}

当程序执行到这段代码而返回的时候,我们不知道到底是哪个变量大于了0,这导致我们不知从哪里入手排查问题。

正确的做法是在每个“return”语句之前,都添加详细的日志说明,如下语句所示:

if (var1 > 0)
{
    // 日志说明1
    return;
}
if (var2 > 0)
{
    // 日志说明2
    return;
}
if (var3 > 0)
{
    // 日志说明3
    return;
}

又如,如下代码:

if (var1 > 0)
{
    // 执行操作1
}
else
{
    return;
}
if (var2 > 0)
{
    // 执行操作2
}
else
{
    return;
}
if (var3 > 0)
{
    // 执行操作3
}
else
{
    return;
}

当程序执行到这段代码而返回的时候,我们不知道到底是在哪个else分支返回的。正确的做法同上,要在每个“return”语句之前,都添加详细的日志说明。

3.代码中出现了魔幻数字,不知道它们表达的意思。
例如,如下代码:

if (var1 == 100)
{
    // 执行操作1
}
else if (var1 == 200)
{
    // 执行操作2
}
else if (var1 == 300)
{
    // 执行操作3
}
else
{
    // 执行操作4
}

在看到这段代码的时候,我们不知道代码中的100、200、300表示什么意思,只是感觉头有点晕。这类我们不知道表达什么意思的数字就统称为魔幻数字。

为了消灭魔幻数字,正确的做法是将它们用宏替代,以提高代码的可阅读性和可修改性。将上面的代码修改之后如下所示:

#define   SMALL    100
#define   MEDIUM   200
#define   BIG      300

if (var1 == SMALL)
{
    // 执行操作1
}
else if (var1 == MEDIUM)
{
    // 执行操作2
}
else if (var1 == BIG)
{
    // 执行操作3
}
else
{
    // 执行操作4
}

4.在使用字符串相关操作函数strcpy、memcpy等的时候,没有判断拷贝长度。
在涉及到字符串相关操作的时候,大家一定注意防止操作不当而引起内存越界。如下代码没有判断拷贝长度,就容易引起了内存越界:

char src[100] = {0};
char dest[80] = {0};

strcpy(dest, src);

如上面的代码所示,当src中字符串的长度大于了80的时候,就会出现内存越界的情况,程序就会崩溃。

正确的做法是在执行拷贝操作之前,先判断字符串的长度,同时将strcpy函数替换为strncpy。修改之后的代码如下所示:

char src[100] = {0};
char dest[80] = {0};
int  len      = 0;

if (strlen(src) >= sizeof(dest)-1)
{
    len = sizeof(dest)-1;
}
else
{
    len = strlen(src);
}
strncpy(dest, src, len);

5.if…else语句的判断条件不明确,用多个变量作为多个else if的判断条件。
例如,如下代码所示:

#define   SMALL    100
#define   MEDIUM   200
#define   BIG      300

if (var1 == SMALL)
{
    // 执行操作1
}
else if (var1 == MEDIUM)
{
    // 执行操作2
}
else if (var2 == BIG)
{
    // 执行操作3
}
else if (var3 == BIG)
{
    // 执行操作4
}
else
{
    // 执行操作5
}

在上面的代码中,if…else语句的判断条件中使用了三个不同的变量var1、var2和var3,这导致了代码阅读起来不方便,而且后续对代码的修改也容易出现错误。

正确的做法是同一级的if…else语句的判断条件中要使用相同的变量,修改之后的代码如下所示:

#define   SMALL    100
#define   MEDIUM   200
#define   BIG      300

if (var1 == SMALL)
{
    // 执行操作1
}
else if (var1 == MEDIUM)
{
    // 执行操作2
}
else
{
    if (var2 == BIG)
    {
        // 执行操作3
    }
    else
    {
        if (var3 == BIG)
        {
            // 执行操作4
        }
        else
        {
            // 执行操作5
        }
    }
}

6.定义变量的时候排版不工整。
在编写几乎每一个函数的时候,我们都会定义一些变量,包括整型变量、字符型变量、指针变量、结构体变量等。如果把这些变量放在一起而不注意排版,那么看起来会非常的乱。如下代码所示:

char str1[50] = {0};
int i = 0;
int max = 0;
char str2[500] = {0};
char *pStr = NULL;
TestStruct_T tTestStruct = {0};
int j = 0;
char str3[1000] = {0};

从上面的代码可以看出,将不同的变量混合放在一起,代码看起来很凌乱,严重影响了程序的可阅读性。为此,我们要将同种类型的变量放在一起,利用缩进来让它们对齐,并利用空行来分隔不同类型的变量。修改之后的代码如下所示:

int i   = 0;
int j   = 0;
int max = 0;

char str1[50]   = {0};
char str2[500]  = {0};
char str3[1000] = {0};

char *pStr = NULL;

TestStruct_T tTestStruct = {0};

这样修改之后的代码阅读起来,层次感更好,可阅读性更强。

7.没有判断函数输入参数(尤其是指针变量)的合法性。
在定义函数的过程中,我们很容易忘记对它的输入参数的合法性进行校验,这样也就增大了程序出问题的风险。如下代码所示:

void TestFun(char *pMsg)
{
    char buf[1000] = {0};

    memcpy(buf, pMsg, sizeof(buf)-1);

    // 执行后续流程
}

可以看到,在函数TestFun中,我们直接将pMsg拷贝到buf中,而没有判断其是否为空。如果pMsg为空,那么这个拷贝就会出现问题,程序就会崩溃。

正确的做法是在使用输入指针pMsg之前,先对其合法性进行校验,也就是判断它是否为空。如果为空,那么直接返回,不进行后续处理。修改之后的代码如下:

void TestFun(char *pMsg)
{
    char buf[1000] = {0};

    if (pMsg == NULL)
    {
        printf(“TestFun: pMsgis NULL!\n”);
        return;
    }
    memcpy(buf, pMsg, sizeof(buf)-1);

    // 执行后续流程
}

总结
不管是开发什么样的软件产品,只要需要写代码,那么就离不开代码评审。通过代码评审,我们不仅可以很快发现代码中存在的问题,而且可以发现自己思维的缺陷及在技术上的欠缺,这对提升开发人员本身的素质也是很有好处的。

在代码评审的时候,大家要本着实事求是的态度,不要因为怕伤了和气而不好意思将问题讲出来,更不要由代码问题而转向对开发人员的人身攻击(例如说某人写的代码太烂了)。我们始终要记住大家的共同目标:将产品做好。

上一篇:D1net阅闻:Google收购自然语言处理创企Api.ai


下一篇:使用GitHub Pages服务进行域名URL转发