从menu项目中感受软工的思想艺术
作者:互联网
前言
笔者虽然已经使用了VS Code很长时间,并且也使用这款编辑器编写了很多的C/C++程序,但一直是当作编辑器在使用,后来在网上查过如何将VS Code+GCC工具集作为开发环境,但没有系统的学习和整理,现在结合课程学习的内容实际操作和整理一下这部分内容,以及阅读了源码之后,对menu程序中体现的软件工程思想进行简要分析。
参考资料:https://gitee.com/mengning997/se/blob/master/README.md#代码中的软件工程
实验环境:Windows7,VS Code,GCC
一、用VS code完成编译和调试环境配置
众所周知,VS Code作为一款强大的编辑器本身是不具备编译功能的,通常是通过安装各种编译器以及插件来实现编译功能。因此在这里,我们也要下载编译器,安装好了MinGW之后,我们需要配置环境变量才可使用,gcc环境变量配置流程很简单,向PATH下添加MinGW文件夹下的bin文件夹路径即可,安装好了MinGW并配置了环境变量之后,我们可以通过在cmd命令行运行。
gcc -v //测试是否安装成功
-
安装成功的界面如下所示:
-
为了让vscode支持C/C++,需要为其安装c/c++扩展:在扩展中搜索c++并安装。
-
想在VS Code上成功的运行调试程序,我们还需进行配置文件的修改,需要更改.vscode下的launch.json与c_cpp_properties.json文件,这里我们可以直接在menu-master文件夹下创建一个名为.vscode的文件夹,然后在.vscode文件夹下分别创建launch.json和tasks.json文件,接着将下面的配置文件分别复制进去就行。
-
launch.json
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/${fileBasenameNoExtension}.out", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "preLaunchTask": "build", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }
-
tasks.json
{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "build", "type": "shell", "command": "g++", "args": ["-g", "${file}", "-std=c++11", "-o", "${fileBasenameNoExtension}.out"] } ] }
- 然后运行HelloWorld.cpp,可以看到能成功运行,说明配置成功了
二、Menu小程序的简要分析
模块化设计
模块化的基本原理
模块化是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这个做法背后的基本原理是关注点的分离,关注点的分离在软件工程领域是最重要的原则,我们习惯上称为模块化,翻译成我们中文的表述其实就是“分而治之”的方法。
模块化的优点
模块化软件设计的方法如果应用的比较好,最终每一个软件模块都将只有一个单一的功能目标,并相对独立于其他软件模块,使得每一个软件模块都容易理解容易开发。从而整个软件系统也更容易定位软件缺陷bug,因为每一个软件缺陷bug都局限在很少的一两个软件模块内。而且整个系统的变更和维护也更容易,因为一个软件模块内的变更只影响很少的几个软件模块。
模块化程度的衡量
一般我们使用耦合度(Coupling)和内聚度(Cohesion)来衡量软件模块化的程度。
耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。 一般在软件设计中我们追求松散耦合。
内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。
项目中的模块化设计
在本项目中,我们把将数据结构和它的操作与菜单业务之间进行分离处理就是一个典型的模块化代码,我们把与 菜单业务处理相关的代码放在menu.c文件中,把与数据结构及其操作相关的代码放在linklist.h文件中在,并在linklist.c文件中实现所需函数。
在linklist.h文件中, 定义了链表结点的结构与操作函数
typedef struct DataNode
{
char* cmd;
char* desc;
int (*handler)();
struct DataNode *next;
} tDataNode;
/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tDataNode * head, char * cmd);
/* show all cmd in listlist */
int ShowAllCmd(tDataNode * head);
在linklist.c文件中, 定义了链表的结构并实现了之前定义的操作函数
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"
tDataNode* FindCmd(tDataNode * head, char * cmd)
{
if(head == NULL || cmd == NULL)
{
return NULL;
}
tDataNode *p = head;
while(p != NULL)
{
if(!strcmp(p->cmd, cmd))
{
return p;
}
p = p->next;
}
return NULL;
}
int ShowAllCmd(tDataNode * head)
{
printf("Menu List:\n");
tDataNode *p = head;
while(p != NULL)
{
printf("%s - %s\n", p->cmd, p->desc);
p = p->next;
}
return 0;
}
在这一部分代码中没有设计任何项目的业务逻辑,只是定义了链表这种数据结构和操作。如果要用到链表操作,只需要导入linktable.h,就可以将数据结构应用到业务逻辑中,无需关注数据结构的细节。
可重用接口
消费者重用和生产者重用
消费者重用是指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。
在重用已有的软件模块代码时一般会重点考虑如下四个关键因素:
- 该软件模块是否能满足项目所要求的功能;
- 采用该软件模块代码是否比从头构建一个需要更少的工作量,包括构建软件模块和集成软件模块等相关的工作;
- 该软件模块是否有完善的文档说明;
- 该软件模块是否有完整的测试及修订记录。
我们清楚了消费者重用时考虑的因素,那么生产者在进行可重用软件设计时需要重点考虑的因素也就清楚了。
接口的相关概念
接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式
在面向过程的编程中,接口一般定义了数据结构及操作这些数据结构的函数;而在面向对象的编程中,接口是对象对外开放的一组属性和方法的集合。
接口包含五个基本要素:
- 接口的目的;
- 接口使用所需满足的前置条件或假定条件;
- 使用接口的双方遵守的协议规范;
- 接口使用之后的效果,一般称为后置条件;
- 接口所隐含的质量属性。
项目中的可重用接口设计
在本项目中,我们用到了链表这种数据结构,而在一些比较大的项目中,我们可能会多次用到链表。因此,我们可以编写一个有关链表的通用模块,方便以后的重用。于是我们编写了前面的linktable.c和linktable.h文件。
linktable.h文件中定义了一系列的接口,比如GetNextLinkTableNode函数:
/*
* get next LinkTableNode
*/
tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
该函数的功能是获取下一个链表节点,调用该函数时需要传入链表和当前节点,函数返回下一个结点。
调用者使用这个接口,只需要关注传入的参数和结果,无需关心细节。
menu.c中对该接口的调用:
/* 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;
}
线程安全
线程
线程是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一般默认一个进程中只包含一个线程。
操作系统中的线程概念也被延伸到CPU硬件上,多线程CPU就是在一个CPU上支持同时运行多个指令流,而多核CPU就是在一块芯片上集成了多个CPU核,比如4核8线程CPU芯片就是在集成了4个CPU核,每个CPU核上支持2个线程。
可重入函数
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。
线程安全
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
函数的可重入性与线程安全之间的关系
- 可重入的函数不一定是线程安全的,可能是线程安全的也可能不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题;
- 不可重入的函数一定不是线程安全的。
项目中的线程安全设计
项目中的线程安全设计体现在链表的定义中,如果多个线程对同一个链表执行操作则存在线程安全问题,所以项目在定义链表结构时,添加了用于互斥操作的线程锁。
/*
* LinkTable Type
*/
struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;
};
我们将链表作为一种临界资源,对其进行互斥访问,操作链表前要为其上锁,操作结束后要为其解锁。
/*
* Delete a LinkTable
*/
int DeleteLinkTable(tLinkTable *pLinkTable)
{
if(pLinkTable == NULL)
{
return FAILURE;
}
while(pLinkTable->pHead != NULL)
{
tLinkTableNode * p = pLinkTable->pHead;
pthread_mutex_lock(&(pLinkTable->mutex));
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1 ;
pthread_mutex_unlock(&(pLinkTable->mutex));
free(p);
}
pLinkTable->pHead = NULL;
pLinkTable->pTail = NULL;
pLinkTable->SumOfNode = 0;
pthread_mutex_destroy(&(pLinkTable->mutex));
free(pLinkTable);
return SUCCESS;
}
总结
通过阅读老师提供的代码,学习到了比较重要的软件工程思想,模块化设计让整个系统的维护和变更更加容易;可重用接口降低了软件模块和软件模块之间的耦合度,方便其他开发者重用一些通用模块,避免重复“造轮子”的事情;线程安全让我们的程序可以并发执行却不会发生数据错误。这些软件工程规范、方法和思想应该被我们融入到今后的所有编程实战中。
标签:模块,感受,menu,软工,cmd,链表,线程,pLinkTable,tDataNode 来源: https://www.cnblogs.com/Mirico/p/13939181.html