随便吐槽一下~
往期资料 == 菜单栏下有更多资料
前言(废话一堆)
我们公司的保密工作做得特别好,研发人员进入保密区(办公室)不能带手机、研发人员的电脑禁用USB功能、电脑上不了外网、有些办公室里有装摄像头……最近更是越来越严了,有个桌面管家的东西,可以监视着我们电脑上存放的东西,以及监视你在用电脑干嘛,就连我电脑几天不连内网、不关机它都要警告我,禁用我权限。
刚入职的时候,很不习惯,之前手机都是不离手的,入职后每一天中的大部分时间都不在手上。在这种高强度的保密机制下,我在想我们部门的项目代码应该很规范、很珍贵。在拿到项目代码之前,我认为它应该像开源项目那般规范、有条理、清晰、排版整齐、注释清晰、代码效率很高等。所以我第一次参与开发时很谨慎,代码尽量规范,对照着部门的代码规范来改了一遍又一遍。
当我拿到代码时,我沉默了,产品级的代码是这样子的吗?暂且不说功能的实现问题,光从排版及编程的角度看,这与我刚学编程时的水平差不多。下面摘取这份产品级项目代码的一部分我觉得不妥的地方进行吐槽
:
吐槽点
一、头文件重复包含问题
同一个头文件在不同的C文件里都可能会被包含,重复包含就会导致代码冗余,甚至可能会产生重复包含的错误。我们可以使用宏保护来解决这个问题,如下test.h中可以使用如下宏保护:
第一次包含头文件,会定义宏_TEST_H,并执行“头文件“的代码;第二次包含时因为之前已经有定义宏_TEST_H,不会重复执行”#ifndef _TEST_H“与”#endif“之间的代码。
但是,这份产品级代码的部分头文件里并没有使用这样的宏保护。
二、定义一个元素的数组
定义变量使我们编程的基础,定义一个数组是为了方便存储具有相同数据类型的数据。可是我在这份产品级代码里看到其定义只有一个元素的数组:int a[1];,只有一个元素为什么不直接定义一个整形变量。。
三、不使用宏定义给一些具有特殊含义的常量命名
这份产品级代码里有类似如下语句(而且很多):
*(ptr + 520) = 1314; // 语句(1)
information(0x10000, 520, 1314); // 语句(2)
这些在代码中穿插一些常量就算了,还不注释,让我去猜类似520这样的数字代表什么意思
。改为下面这样不好吗?
要是这么写的话我就可以很快的知道520、1314分别是我爱你、一生一世的意思呀。
四、几乎全用全局变量
随便在网上查看一些C语言编程规范就可以看到有这么一条:尽量不用或少用全局变量。包括我们部门的编程规范里也是有这么一条规则的。然而,这份产品级的项目代码百分之九十以上的变量都是全局变量。。所以整份工程定义的函数大多都是这样格式的:
void test(void)
{
}
定义的函数无参数,无返回值,看似简便,实则破坏了函数该有的封装性。
为什么说少用全局变量呢?
1、长期占用内存
全局变量生命周期长,程序运行期一直存在,始终占有那块存储区;
2、难以定位错误
全局变量是公共的,全部函数都可以访问,难以定位全局变量在哪里被修改,加大了调试的难度;
3、增加了代码的耦合性
使用全局变量的函数,需要关注全局变量的值,增加了理解的难度,增加了耦合性,也降低了代码的可读性;
4、破坏了函数的封装性
函数像一个黑匣子,一般是通过函数参数和返回值进行输入输出,函数内部实现相对独立。但函数中如果使用了全局变量,那么函数体内的语句就可以绕过函数参数和返回值进行存取,这种情况破坏了函数的独立性,使函数对全局变量产生依赖。同时,也降低了该函数的可移植性。
所以,如果不是万不得已,最好不要使用全局变量。
五、给数组赋值
在这份产品级的项目里,有如下被注释掉的代码:
int a[4] = {1,1,1,1};
a = 0;
我把代码的一些注释去掉之后,进行编译,发现报错,定位到这个地方,当时一时半会看不出哪里有错误,因为int a[4];是定义在其他.c文件里的。当看到 a 是被定义为一个数组是,才知道这是要给数组进行赋值。。怎么能这么给数组清零。。
给数组清零的方法:
1、挨个元素赋值
int a[4] = {1,1,1,1};
int i;
for (i = 0; i < 4; i++)
{
a[i] = 0;
}
2、使用memset函数
int a[4] = {1,1,1,1};
memset(a, 0, sizeof(a));
metset函数被包含在头文件string.h中,其原型为:
void *memset(void *s, int ch, size_t n);
将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。特别需要注意的是第三个参数,给数组清零时很容易误写为数组元素的个数。
六、对外接口管理
当代码量比较大时,常常使用模块化编程。在模块化编程中,每个模块都可以看做是一个黑盒,只需要了解模块提供的功能以及使用的方法,不需要关心具体实现该模块功能的策略和方法。就好像我们买了一部手机,只需要会用它所提供的各种功能即可,至于各种功能在底层是如何实现的,用户不需要关心。
在大型程序开发中,一个程序由不同的模块组成,可能不同的模块会由不同的人员负责。在编写某个模块的时候,很可能需要调用别人写好的模块的接口。这个时候关心的是:其他模块提供了什么样的接口,应该如何去调用,至于模块内部是如何实现的,对于调用者而言,无须过多关注。模块对外提供的只是接口,把不需要的细节尽可能对外部隐藏起来,这正是采用模块化程序设计所需要注意的地方。
一个最小的模块单元包含两个文件:一个是“.h”文件(又称为头文件);另一个是“.c”文件(又称为源文件)。
1、“.h”文件(又称为头文件)
该文件可以理解为一份接口描述文件,其文件内部一般不包含任何实质性的函数代码,可以把这个头文件理解成为一份说明书,其内容就是这个模块对外提供的接口函数、接口变量以及使用说明等。
2、“.c”文件(又称为源文件)
该文件主要功能是对“.h”文件中声明的外部函数进行具体的实现,对具体实现方式没有特殊规定,只要能实现其函数的功能即可。
而这份产品级的项目代码并没有使用模块化工程,其把大部分函数声明全都在一个公用的头文件中声明。
七、定义同名的全局变量与局部变量
这份代码中,在定义了全局变量i、j,又定义了局部变量i、j。这些循环变量不应是定义局部的吗?定义同名的全局变量与局部变量可能也没什么大问题,因为局部变量会屏蔽掉同名的全局变量。但是为了程序的严谨性,还是不要定义同名的全局变量与局部变量。
八、数组初始化问题
这份工程定义了很多数组,而且很多数组定义在一行,其初始化为0,却不略写,看着很冗长。如与以下类似的代码:
unsigned long test1_arr[4] = {0,0,0,0}, test2_arr[4] = {0,0,0,0}, test3_arr[4] = {0,0,0,0};
为什么不分行定义呢,为什么不略写呢?
unsigned long test1_arr[4] = {0};
unsigned long test2_arr[4] = {0};
unsigned long test3_arr[4] = {0};
这么写不是更简洁清晰吗。
九、不喜欢用for循环
这份工程很不喜欢用for循环,其中有如下类似代码:
就这样,宁可多写几百行代码,也不宁可使用一个for循环来简写代码。
总结
产品级的代码不应该是这个样子的呀,代码格式乱七八糟,仅有轻微强迫症的我表示看着很难受。代码框架完全没有规划,想到啥就写啥。这样的代码不出问题还好,一旦出问题就不好解决了。
吐槽结束,欢迎阅读!