代码中的软件工程--menu项目
作者:互联网
1、前言
本篇博文基于孟宁老师上课内容和所提供资料(https://gitee.com/mengning997/se/blob/master/README.md#%E4%BB%A3%E7%A0%81%E4%B8%AD%E7%9A%84%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B)而完成,通过一个menu项目,浅谈一些我对于软件工程的理解。感谢孟宁老师在我此过程中所给予的帮助!
2、通过VSCode装C++及环境配置
1、打开VSCode,在扩展栏内搜索c++,并安装。
2、下载MinGW,找到最新版的 "x86_64-posix-seh"。安装MinGW。
3、配置MinGw环境。
配好后,按下 win + R,输入cmd,回车键之后输入g++,再回车,验证环境是否配置成功。出现如下界面,即代表配置成功。
4、配置C++环境,在VSCode内点击运行栏,点击创建launch.json文件。
选择C++(GDB/LLDB)
对文件进行修改,如下图所示。其余配置按所给提示配好。
修改后:
"version": "0.2.0", "configurations": [ { "name": "(gdb) 启动", "type": "cppdbg", "request": "launch", "program": "${fileDirname}\\${fileBasenameNoExtension}.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "miDebuggerPath": "D:\\MinGW\\mingw64\\bin\\gdb.exe", //自己路径 "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ],"preLaunchTask": "task g++" } ] }
该文件配置完成后,返回hello.c文件,按F5运行,点击“任务配置”,配置tasks.json,如下所示。
{ "version": "2.0.0", "tasks": [ { "type": "shell", "label": "task g++", //修改此项 "command": "D:\\MinGW\\mingw64\\bin\\g++.exe", "args": [ "-g", "${file}", "-o", "${fileDirname}\\${fileBasenameNoExtension}.exe" ], "options": { "cwd": "D:\\MinGW\\mingw64\\bin" }, "problemMatcher": [ "$gcc" ], "group": "build" } ] }
至此,环境配置完毕。
3、代码的成长
在menu项目中,我们可以很清晰看到整个代码的成长过程。最开始的lab1中仅仅可以判别输入是否是help,而后面的每一个版本都在前一版本的基础上进行新功能的添加和原功能的改进,直到最后实现了一个完整的菜单功能。
4、模块化设计
模块化软件设计的主要思想是,在设计较复杂的程序时,一般会采用自顶向下的方法,将问题划分为几个部分,再将各个部分再次细化,直到分解为比较好解决的问题为止。这样做可以有效地降低程序的复杂度,使程序设计、调试和维护等操作变得简单化。
此外,为了衡量模块化程度,我们引入了耦合度和内聚度的概念。其中,耦合度是指软件模块之间的依赖程度;而内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。
代码中模块化的主要目的是为了包容变化,当我们的需求发生变化时,我们可以尽可能的减少对代码的修改。下面我们结合menu项目里lab3.1中的menu.c的具体代码来看看模块化具体是如何实现的。
#include <stdio.h> #include <stdlib.h> int Help(); int Quit(); #define CMD_MAX_LEN 128 #define DESC_LEN 1024 #define CMD_NUM 10 typedef struct DataNode { char* cmd; char* desc; int (*handler)(); struct DataNode *next; } tDataNode; static tDataNode head[] = { {"help", "this is help cmd!", Help,&head[1]}, {"version", "menu program v1.0", NULL, &head[2]}, {"quit", "Quit from menu", Quit, NULL} }; int main() { /* cmd line begins */ while(1) { char cmd[CMD_MAX_LEN]; printf("Input a cmd number > "); scanf("%s", cmd); tDataNode *p = head; while(p != NULL) { if(strcmp(p->cmd, cmd) == 0) { printf("%s - %s\n", p->cmd, p->desc); if(p->handler != NULL) { p->handler(); } break; } p = p->next; } if(p == NULL) { printf("This is a wrong cmd!\n "); } } } int Help() { printf("Menu List:\n"); tDataNode *p = head; while(p != NULL) { printf("%s - %s\n", p->cmd, p->desc); p = p->next; } return 0; } int Quit() { exit(0); }
这段代码便是一段比较经典的使用模块化设计的代码。我们可以看到,在这段代码中main()部分代码逻辑是基本不需要修改的,而我们主要需要进行改动的是命令描述部分及其处理函数部分的代码,主要是下面部分的代码:
尽管上面的代码已经完成了初步的模块化,但还可以进一步改进,使其可以满足开闭原则,即对扩展开放,对修改关闭。我们发现上面的代码在功能性需求出现变化时,我们可以很好的进行维护,但当一些非功能性需求发生改变(例如数据结构发生变化等情况时),我们就需要对上述代码进行大量的修改。因此,我们可以把整段代码划分为业务逻辑层和数据存储层。我们把与功能相关的代码放在menu.c文件中,把与数据结构及其操作相关的代码放在linklist.h文件中在,并且在linklist.c文件中实现所需函数,于是便有了lab3.3中的代码。
5、可重用接口
在对代码进行初步模块化设计之后,我们还对代码进行优化,便需要为代码设计一些合适的接口,进一步消除模块间的耦合,使每个模块所实现的功能更加清晰。
在menu项目中,我们用到了链表的数据结构,而在一个比较大的项目中,我们可能会多次用到链表的结构。因此,我们希望能写一个链表的通用模块,方便以后的重用。于是便有lab4中linktable.c和linktable.h文件。
#ifndef _LINK_TABLE_H_ #define _LINK_TABLE_H_ #include <pthread.h> #define SUCCESS 0 #define FAILURE (-1) /* * LinkTable Node Type */ typedef struct LinkTableNode { struct LinkTableNode * pNext; }tLinkTableNode; /* * LinkTable Type */ typedef struct LinkTable { tLinkTableNode *pHead; tLinkTableNode *pTail; int SumOfNode; pthread_mutex_t mutex; }tLinkTable; /* * Create a LinkTable */ tLinkTable * CreateLinkTable(); /* * Delete a LinkTable */ int DeleteLinkTable(tLinkTable *pLinkTable); /* * Add a LinkTableNode to LinkTable */ int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); /* * Delete a LinkTableNode from LinkTable */ int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); /* * Search a LinkTableNode from LinkTable * int Conditon(tLinkTableNode * pNode); */tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable); /* * get next LinkTableNode */ tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); #endif /* _LINK_TABLE_H_ */
但在使用通用的linktable模块后,menu程序业务代码变得复杂了许多,显然是因为我们的接口定义的不够好。于是,便进行了lab4到lab5的进化。下面是lab5中linktable.h文件:
#ifndef _LINK_TABLE_H_ #define _LINK_TABLE_H_ #include <pthread.h> #define SUCCESS 0 #define FAILURE (-1) /* * LinkTable Node Type */ typedef struct LinkTableNode { struct LinkTableNode * pNext; }tLinkTableNode; /* * LinkTable Type */ typedef struct LinkTable { tLinkTableNode *pHead; tLinkTableNode *pTail; int SumOfNode; pthread_mutex_t mutex; }tLinkTable; /* * Create a LinkTable */ tLinkTable * CreateLinkTable(); /* * Delete a LinkTable */ int DeleteLinkTable(tLinkTable *pLinkTable); /* * Add a LinkTableNode to LinkTable */ int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); /* * Delete a LinkTableNode from LinkTable */ int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); /* * Search a LinkTableNode from LinkTable * int Conditon(tLinkTableNode * pNode); */ tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode)); /* * get LinkTableHead */ tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable); /* * get next LinkTableNode */ tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); #endif /* _LINK_TABLE_H_ */
优化代码的精髓在于callback方法。在上面两段代码中,唯一变化在于lab5中的linktable.h中新增了一个SearchLinkTableNode函数,这是一个acll-in方式的函数;而在lab5中的menu.c文件中又新增了一SearchCondition函数,这是一个callback方式函数(如下所示)。利用这种方式就可以使得linktable接口变得更加通用。
int SearchCondition(tLinkTableNode * pLinkTableNode) { tDataNode * pNode = (tDataNode *)pLinkTableNode; if(strcmp(pNode->cmd, cmd) == 0) { return SUCCESS; } return FAILURE; }
6、线程安全
线程安全是指在多线程同时运行时,它们可能同时运行同一段代码,如果它们运行的结果与期待的结果相同的话,则说明是线程安全的。线程安全问题只会出现在写操作上,而读操作无需考虑。我们通常采用锁机制来保证线程安全。以lab7.1中的linktable.c为例,代码中对于删除节点的函数,添加节点的函数均采用上锁的方法来保证线程的安全。
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; } /* * Add a LinkTableNode to LinkTable */ int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) { if(pLinkTable == NULL || pNode == NULL) { return FAILURE; } pNode->pNext = NULL; pthread_mutex_lock(&(pLinkTable->mutex)); if(pLinkTable->pHead == NULL) { pLinkTable->pHead = pNode; } if(pLinkTable->pTail == NULL) { pLinkTable->pTail = pNode; } else { pLinkTable->pTail->pNext = pNode; pLinkTable->pTail = pNode; } pLinkTable->SumOfNode += 1 ; pthread_mutex_unlock(&(pLinkTable->mutex)); return SUCCESS; } /* * Delete a LinkTableNode from LinkTable */ int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) { if(pLinkTable == NULL || pNode == NULL) { return FAILURE; } pthread_mutex_lock(&(pLinkTable->mutex)); if(pLinkTable->pHead == pNode) { pLinkTable->pHead = pLinkTable->pHead->pNext; pLinkTable->SumOfNode -= 1 ; if(pLinkTable->SumOfNode == 0) { pLinkTable->pTail = NULL; } pthread_mutex_unlock(&(pLinkTable->mutex)); return SUCCESS; } tLinkTableNode * pTempNode = pLinkTable->pHead; while(pTempNode != NULL) { if(pTempNode->pNext == pNode) { pTempNode->pNext = pTempNode->pNext->pNext; pLinkTable->SumOfNode -= 1 ; if(pLinkTable->SumOfNode == 0) { pLinkTable->pTail = NULL; } pthread_mutex_unlock(&(pLinkTable->mutex)); return SUCCESS; } pTempNode = pTempNode->pNext; } pthread_mutex_unlock(&(pLinkTable->mutex)); return FAILURE; }
7、小结
以上便是我通过阅读menu项目代码,结合孟宁老师上课所讲的内容以及所提供的学习资料,对软件工程的一个初步的理解。希望可以将所学到的内容应用在以后所写的代码中,使代码更简洁、更易读、更便于重用。再次感谢孟宁老师在学习过程中所给予的帮助!
8、参考资料
https://github.com/mengning/menu
标签:--,menu,pNode,int,软件工程,mutex,pLinkTable,tLinkTableNode,NULL 来源: https://www.cnblogs.com/qq610190147/p/13916659.html