代码中的软件工程——基于menu项目
一、前言
此篇博客完成了以VS Code + GCC工具集为主要环境,编译调试了课程项目案例。
在实践的过程中借助了孟宁老师提供的源代码资料,结合代码分析其中的软件工程方法、规范以及软件工程思想。
最后,基于menu项目的实现来理解和解决了编码过程中遇到的问题,且认真体会了老师所讲的简约而不简单的代码规范和代码风格,为未来写出明了精炼的代码打下了坚实的基础。
二、环境配置
1.VS Code C/C++插件安装
在vscode中的扩展中心提供了相关的插件工具,我们进行下载和安装,为后续工作进行准备。

2.安装C/C++编译器
这里采用孟宁老师推荐的Mingw-w64/GCC作为编译器进行后期的编译。相关配置按文档的建议设置如下:

安装好后注意添加环境变量,之后我们可以打开CMD,执行gcc -v来检测是否安装成功。如下图所示,出现了版本号,说明安装编译器成功。

之后,打开老师提供的项目文件夹se_code,选择hello.c,运行后选择好运行环境,然后对生成的 .vscode 目录中的配置文件c_cpp_properties.json,launch.json,tasks.json分别进行如下配置:
c_cpp_properties.json文件配置

launch.json文件配置

tasks.json文件配置

配置完成后运行hello.c,成功运行,至此,配置完成。

三、软件工程思想
在整个menu项目中,通过学习相关内容后,我认为如下几点给我留下了深刻印象:模块化软件设计,可重用软件设计以及可重入函数与线程安全。
1.模块化软件设计
在设计较复杂的程序时,一般采用自顶向下的方法,将问题划分为几个部分,各个部分再进行细化,直到分解为较好解决问题为止。模块化设计,简单地说就是程序的编写不是一开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系逐步求精的结果是得到一系列以功能块为单位的算法描述。以功能块为单位进行程序设计,实现其求解算法的方法称为模块化。模块化的目的是为了降低程序复杂度,使程序设计、调试和维护等操作简单化。
1.1模块化评判指标
软件设计中的模块化程度便成为了软件设计有多好的一个重要指标,一般我们使用耦合度(Coupling)和内聚度(Cohesion)来衡量软件模块化的程度。
耦合度(Coupling):耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。一般在软件设计中我们追求松散耦合。

内聚度(Cohesion):内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。

1.2模块化软件设计在menu项目中的体现:
如下图所示,对比lab3.2和lab3.3可以发现,最初的所有功能都被揉合在了一个menu.c文件中,这样会导致后期如果需要进行更改和增减功能十分的麻烦,不利于大型项目的合作和持续开发;进行了模块化设计之后,设计的模块与实现的源代码文件之间有映射对应关系,因此我们需要将数据结构和它的操作独立放到单独的源代码文件中,这时就需要设计合适的接口,以便于模块之间互相调用,在menu项目中,是将一系列功能通过调用linklist.h文件中的接口予以实现,显著提高了代码的适应能力和稳健性。
模块化的设计进步&在linklist.c中进行的模块化功能实现

在menu.c中的调用

2.可重用软件设计
尽管已经做了初步的模块化设计,但是分离出来的数据结构和它的操作还有很多菜单业务上的痕迹,我们要求这一个软件模块只做一件事,也就是功能内聚,那就要让它做好链表数据结构和对链表的操作,不应该涉及菜单业务功能上的东西;同样我们希望这一个软件模块与其他软件模块之间松散耦合,就需要定义简洁、清晰、明确的接口。
接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。换句话说,接口具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。
在menu项目中的可重用软件设计
如图所示,以lab5.1为例,在linktable.h中定义了大量接口:

其中,以GetNextLinkTableNode为例,在linktable.c中进行实现:

最后在menu.c中进行调用:

3.可重入函数与线程安全
首先我们需要了解一下什么是线程安全。如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
在此基础上,我们进一步了解可重入函数的相关要求。可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。
可重入函数不为连续的调用持有静态数据; 不返回指向静态数据的指针; 所有数据都由函数的调用者提供; 使用局部变量,或者通过制作全局数据的局部变量拷贝来保护全局数据; 使用静态数据或全局变量时做周密的并行时序分析,通过临界区互斥避免临界区冲突; 绝不调用任何不可重入函数。
在menu项目中的可重入函数与线程安全的体现
可以看到,在linktable.c文件中通过定义pthread_mutex_t mutex来提供临界区的互斥功能,为后续的项目安全做好准备。

在使用CreateLinkTable创建LinkTable时,可以注意到,对其进行了加锁和解锁的初始化操作。

在其他函数,例如下图所示的增加节点的操作过程中,分别使用了pthread_mutex_lock和pthread_mutex_unlock进行加锁和解锁的操作。这样做的目的就是确保在同一时间只有一个线程访问相关代码,避免线程冲突,实现线程安全。

四、心得体会
通过这次的menu程序项目实践,对老师所讲的三大主要内容:模块化软件设计,可重用软件设计以及可重入函数与线程安全有了更全面和细致的认识。在未来的项目开发过程中,为实现更高效和安全的开发体验,应该对这三个方面进行更细致和深入的应用和研究。
另一方面,在平时的学习过程中,也要养成良好的代码规范和代码风格。在从规范整洁到逻辑清晰,再到写出一行优雅的代码,做一位能享受编程的优秀码农。
本文项目源代码:项目源代码
本文参考资料链接:参考资料
浙公网安备 33010602011771号