C语言代码评审小结

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 最近,我参加了几个C语言代码的评审会,发现了代码中一些共同存在的问题,特总结下来,供相关的开发人员参考。

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

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

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

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

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

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

例如,如下代码:

if (var1 > 0)
{
    return;
}
if (var2 > 0)
{
    return;
}
if (var3 > 0)
{
    return;
}
AI 代码解读

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

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

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

又如,如下代码:

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

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

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

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

在看到这段代码的时候,我们不知道代码中的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
}
AI 代码解读

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

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

strcpy(dest, src);
AI 代码解读

如上面的代码所示,当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);
AI 代码解读

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
}
AI 代码解读

在上面的代码中,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
        }
    }
}
AI 代码解读

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};
AI 代码解读

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

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};
AI 代码解读

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

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

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

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

    // 执行后续流程
}
AI 代码解读

可以看到,在函数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);

    // 执行后续流程
}
AI 代码解读

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

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
0
0
0
260
分享
相关文章
C语言之考勤模拟系统平台(千行代码)
C语言之考勤模拟系统平台(千行代码)
104 4
【C语言】揭秘GCC:从平凡到卓越的编译艺术,一场代码与效率的激情碰撞,探索那些不为人知的秘密武器,让你的程序瞬间提速百倍!
【8月更文挑战第20天】GCC,GNU Compiler Collection,是GNU项目中的开源编译器集合,支持C、C++等多种语言。作为C语言程序员的重要工具,GCC具备跨平台性、高度可配置性及丰富的优化选项等特点。通过简单示例,如编译“Hello, GCC!”程序 (`gcc -o hello hello.c`),展示了GCC的基础用法及不同优化级别(`-O0`, `-O1`, `-O3`)对性能的影响。GCC还支持生成调试信息(`-g`),便于使用GDB等工具进行调试。尽管有如Microsoft Visual C++、Clang等竞品,GCC仍因其灵活性和强大的功能被广泛采用。
222 1
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
C语言物联网开发之设备安全与代码可靠性隐患
物联网设备的C语言代码安全与可靠性至关重要。一是防范代码安全漏洞,包括缓冲区溢出和代码注入风险,通过使用安全函数和严格输入验证来预防。二是提高代码跨平台兼容性,利用`stdint.h`定义统一的数据类型,并通过硬件接口抽象与适配减少平台间的差异,确保程序稳定运行。
376 10
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
126 1
深入C语言指针,使代码更加灵活(二)
深入C语言指针,使代码更加灵活(二)
深入C语言指针,使代码更加灵活(一)
深入C语言指针,使代码更加灵活(一)
|
6月前
|
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
在C语言中,正确使用运算符能提升代码的可读性和效率
在C语言中,运算符的使用需要注意优先级、结合性、自增自减的形式、逻辑运算的短路特性、位运算的类型、条件运算的可读性、类型转换以及使用括号来明确运算顺序。掌握这些注意事项可以帮助编写出更安全和高效的代码。
129 4
数据结构基础详解(C语言):单链表_定义_初始化_插入_删除_查找_建立操作_纯c语言代码注释讲解
本文详细介绍了单链表的理论知识,涵盖单链表的定义、优点与缺点,并通过示例代码讲解了单链表的初始化、插入、删除、查找等核心操作。文中还具体分析了按位序插入、指定节点前后插入、按位序删除及按值查找等算法实现,并提供了尾插法和头插法建立单链表的方法,帮助读者深入理解单链表的基本原理与应用技巧。
1148 6