查看原文
其他

随便吐槽一下~

正念君 嵌入式大杂烩 2021-01-31

点击上方「嵌入式大杂烩」,选择「置顶公众号」第一时间查看编程笔记!

往期资料  == 菜单栏下有更多资料

资源分享 | 嵌入式相关

资源分享 | 编程语言类

资源分享 | Linux相关资料

资源分享 | 数据结构与算法




前言(废话一堆

我们公司的保密工作做得特别好,研发人员进入保密区(办公室)不能带手机、研发人员的电脑禁用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循环来简写代码。


总结

产品级的代码不应该是这个样子的呀,代码格式乱七八糟,仅有轻微强迫症的我表示看着很难受。代码框架完全没有规划,想到啥就写啥。这样的代码不出问题还好,一旦出问题就不好解决了。

吐槽结束,欢迎阅读!

ps:资料链接失效怎么办?


温馨提示:若链接失效,请联系小编。小编微信及QQ二维码如下,欢迎添加


      
  


【往期精彩笔记推荐】

学习使用带参宏,提高编程基础(一)

学习使用带参宏,提高编程基础(二)

#define的高级用法

一位大牛的单片机笔记

显示板开发

省电子设计竞赛一等奖作品分享(一)

省电子设计竞赛一等奖作品分享(二)

【C语言笔记】你知道C语言编译的过程吗?

【C语言笔记】操作位的技巧



如果觉得对你有用的话,请帮忙点个赞哟,如果你觉得对你的朋友、同学也有用的话,欢迎转发给你的朋友。


Today's Feelings

点击左下角的【阅读全文】,获取本公众号往期所有笔记推送,欢迎一起交流,共同进步

你点的每个好看,我都认真当成了喜欢

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存