代码中的软件工程
一、前言
首先感谢孟宁老师的教学指导,本篇博客是由根据孟宁老师上课内容,在以VS Code + GCC中为主要环境,搭建一个简单的命令行menu小程序开始,一步步根据软件工程的一般规律开发、完善,最终实现了一个成长起来的menu程序。
参考链接:https://github.com/mengning/menu
https://mp.weixin.qq.com/s/KJ4oU5ggccu1f5mMaEo1eg
https://gitee.com/mengning997/se/blob/master/ppt/menu_code.zip
https://github.com/mengning/menu
二、基于 VS Code 的 C/C++开发调试环境配置
Windows 环境下安装 Mingw-w64
要让 VSCode 能运行 C/C++ 代码,我们首先需要安装 C/C++ 扩展。但由于 C/C++ 扩展并不包括编译器或调试器,所以目前的VSCode是无法运行代码的。
1、在VSCode商店中下载C/C++官方插件。

2、下载安装C/C++编译器:MinGW。
下载地址:https://sourceforge.net/projects/mingw-w64/files/
下载如下图的文件

下载完成后,将其解压缩到一个不含中文路径名的目录下。然后找到文件中的bin文件夹所在目录,如“D:\soft\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin”,复制该路径名。
3、配置统环境变量。
将刚刚复制的路径名,添加到系统环境变量,完成环境配置。

在 cmd 窗口中输入“gcc -v”查看是否安装成功,若成功显示“gcc version”则表示配置成功。

4、配置launch.json和task.json文件
在VSCode中打开下载的menu代码所在文件夹,然后选择test.c文件,在VSCode左侧点击依次点击“运行和调试”->“C++(GDB/LLDB)”->“gcc.exe-生成和调试活动文件”。此时menu目录下生成了.vscode文件夹,该文件夹中生成了所需的launch.json和task.json。
将launch.json中的“preLaunchTask”的内容修改为“gcc”。

将task.json中的“label”的内容修改为“gcc”。

点击运行(快捷键ctrl+f5)后,代码运行成功。这样,VS Code 的 C/C++开发调试环境配置就完成了。

三、项目中的软件工程
1.模块化和接口
模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这个做法背后的基本原理是关注点的分离。关注点的分离在软件工程领域是最重要的原则。关注点的分离的思想背后的根源是由于人脑处理复杂问题时容易出错,把复杂问题分解成一个个简单问题,从而减少出错的情形。模块化软件设计的方法如果应用的比较好,最终每一个软件模块都将只有一个单一的功能目标,并相对独立于其他软件模块,使得每一个软件模块都容易理解容易开发。从而整个软件系统也更容易定位软件缺陷bug,因为每一个软件缺陷bug都局限在很少的一两个软件模块内。
耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。 一般在软件设计中我们追求松散耦合。

通过观察下面的文件,我么不难看出,通过模块化的设计让系统内的各个部分保持相对对了,以便每一个部分可以被独立地进行设计和开发。模块化设计的原理是关注点分离,相当于把复杂的问题分解成一个个简单的问题。在修改相关程序业务逻辑的时候,只需要修改对应的业务即可,即修改某一个文件的内容即可,不需要做全文件的修改。

通过上述的分析,我们不难发现模块化的作用:
- 避免命名冲突(减少命名空间污染)
- 灵活架构,焦点分离,方便模块间组合、分解
- 多人协作互不干扰
- 高复用性和可维护性
2.可重用接口
接口是双方共同遵守的一种协议规范,在软件系统内部通常是定义一组API函数约定模块之间的通信关系。接口的主要目的就是不需要重复造轮子,可以使得代码复用,减少代码量。降低类与类之间的依赖关系,方便功能扩展。
menu小程序中,孟老师便是将模块化设计与可重用接口很好的体现出来,我们将该程序分为了三部分:
- linktable
- menu
- 主函数
在 linktable.h内提供了对linktable数据结构进行创建,增加,删除,查找操作的接口;在 menu.h 内提供了对menu菜单进行配置与执行功能的接口。而程序员只需要在主函数内调用上面两个.h文件给出的接口,便可实现menu的所有功能。每个不同的模块内聚程度都很高,不同模块之间的耦合尽可能的降到了最低,不会因为单个模块出现问题,而导致其他模块出现问题。
在linktable.h文件中,我们定义了一系列的接口,如GetLinkTableHead函数,代码如下:
/*
* get LinkTableHead
*/
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
该函数提供了获取下一个链表的头节点的接口,使得调用者只需完成自身的功能设计,而无需顾及接口所需要实现的功能的细节,如下menu.c程序中的FindCmd函数对本接口进行了调用
/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd)
{
//对接口进行了调用,获取传入链表的头节点
tDataNode * pNode = (tDataNode*)GetLinkTableHead(head);
while(pNode != NULL)
{
if(!strcmp(pNode->cmd, cmd))
{
return pNode;
}
pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode);
}
return NULL;
}
3.线程安全
线程(thread)是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一般默认一个进程中只包含一个线程。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
在孟老师所给的menu代码中,线程安全就是通过加锁的方式来得以保证的。
struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;//确保线程安全的锁
};
pthread_mutex_lock(&(pLinkTable->mutex));
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1 ;
pthread_mutex_unlock(&(pLinkTable->mutex));
四、总结
本篇博客借助孟宁老师提供的menu项目,懂得了在完成一个工程项目的过程中,要注意模块化设计、可重用接口、线程安全等问题,使得软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发,同时要避免重复造轮子,更要懂得保持代码中的线程安全。让我在软件开发者过程中有了新的认知。

浙公网安备 33010602011771号