代码中的软件工程—代码的成长
一、环境配置
1.编程软件VSCode。
2.VSCode安装C/C++扩展。

3.编译环境,安装gcc,根据自己的系统选择对应的mingw-c。
4.配置环境变量,右击“此电脑”,点击属性,高级设置,环境变量,最后在Path变量中添加安装gcc目录下bin文件夹的地址。
5.运行一个“hello world”程序验证环境是否配置完成。
二、代码的成长
最初编辑一个目录程序menu,我们希望的功能是能根据输入的字符串匹配能够匹配正确,所以就有了最初的少数几行代码
int main() { char cmd[128]; while(1) { scanf("%s", cmd); if(strcmp(cmd, "help") == 0) { printf("This is help cmd!\n"); } else if(strcmp(cmd, "quit") == 0) { exit(0); } else { printf("Wrong cmd!\n"); }
后来我们又想不仅能匹配正确,还能输出这个命令的描述还能执行这个命令,因此我们又添加了新的结构DataNode,又修改了查找方式
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 "); } } }
接着,这个结构体又不单单是这一个程序能用到,这是一个基本数据结构,链表。为了这样的数据结构能被其他程序共享,所以我们把这个数据结构的定义写在了一个新的.h文件里,虽然menu对这个数据结构操作仅仅用到了查找操作,为了适应其他程序我们还应该写上其他的操作功能。就这样代码又再次变长。
#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); /* * get LinkTableHead */ tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable); /* * get next LinkTableNode */ tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); #endif /* _LINK_TABLE_H_ */
随着main函数里功能越来越多,代码越来越长,变量越来越多,不仅程序员调Bug也变得越来越困难,而且代码的可阅读性,可维护性和拓展性也变得越来越差,所以就有力模块化编程,我们把main中的每一个功能单独写成一个函数,调试时我们只要对某一个模块调试就行,更新也仅仅更新它就可以。这样代码又一次成长。
#include <stdio.h> #include <stdlib.h> #include "linktable.h" #include "linktable.c" int Help(); int Quit(); #define CMD_MAX_LEN 128 #define DESC_LEN 1024 #define CMD_NUM 10 char cmd[CMD_MAX_LEN]; /* data struct and its operations */ typedef struct DataNode { tLinkTableNode * pNext; char* cmd; char* desc; int (*handler)(); } tDataNode; int SearchCondition(tLinkTableNode * pLinkTableNode) { tDataNode * pNode = (tDataNode *)pLinkTableNode; if(strcmp(pNode->cmd, cmd) == 0) { return SUCCESS; } return FAILURE; } /* find a cmd in the linklist and return the datanode pointer */ tDataNode* FindCmd(tLinkTable * head, char * cmd) { return (tDataNode*)SearchLinkTableNode(head,SearchCondition); } /* show all cmd in listlist */ int ShowAllCmd(tLinkTable * head) { tDataNode * pNode = (tDataNode*)GetLinkTableHead(head); while(pNode != NULL) { printf("%s - %s\n", pNode->cmd, pNode->desc); pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode); } return 0; } int InitMenuData(tLinkTable ** ppLinktable) { *ppLinktable = CreateLinkTable(); tDataNode* pNode = (tDataNode*)malloc(sizeof(tDataNode)); pNode->cmd = "help"; pNode->desc = "Menu List:"; pNode->handler = Help; AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode);return 0; } /* menu program */ tLinkTable * head = NULL; int main() { InitMenuData(&head); /* cmd line begins */ while(1) { printf("Input a cmd number > "); scanf("%s", cmd); tDataNode *p = FindCmd(head, cmd); if( p == NULL) { printf("This is a wrong cmd!\n "); continue; } printf("%s - %s\n", p->cmd, p->desc); if(p->handler != NULL) { p->handler(); } } } int Help() { ShowAllCmd(head); return 0; } int Quit() { exit(0); }
三、模块化设计
模块化设计是在软件系统设计时保持系统内部各部分相对独立,保证每一个部分都能被独立开发与设计。比较好的模块只能完成一个功能或者目标,并且十分独立,和别的模块完全没有依赖关系。这样设计系统,如果存在bug,也仅仅只局限在某个模块。当然也更加的易维护,一个模块的变动,由于模块间独立,别的模块甚至不需要修改。
评价一个系统模块化设计好坏的指标就是耦合度,它能体现模块之间的亲密程度,软件设计中我们要追求松散耦合。
3.1数据结构的独立
以menu代码为例,最初只有一个tDateNode的结构体
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} };
这样的代码及其难维护,这个结构体只有修改一点点,整个程序所以的用到此结构体的代码都需要修改,所以数据结构的独立十分必要,因此写出了这样的新的数据结构
typedef struct LinkTableNode { struct LinkTableNode * pNext; }tLinkTableNode; /* * LinkTable Type */ typedef struct LinkTable { tLinkTableNode *pHead; tLinkTableNode *pTail; int SumOfNode; pthread_mutex_t mutex; }tLinkTable;
如此数据结构的内容是统一了,但是依然存在不足,就是数据结构的操作部分,数据结构的变动,我们依旧需要修改main中所有的操作,因此操作这部分还是要独立出来,别的模块需要对数据结构做某种操仅仅调用专门进行操作的模块即可。
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode)) { if(pLinkTable == NULL || Conditon == NULL) { return NULL; } tLinkTableNode * pNode = pLinkTable->pHead; while(pNode != pLinkTable->pTail) { if(Conditon(pNode) == SUCCESS) { return pNode; } pNode = pNode->pNext; } return NULL; } /* * get LinkTableHead */ tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable) { if(pLinkTable == NULL) { return NULL; } return pLinkTable->pHead; } /* * get next LinkTableNode */ tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) { if(pLinkTable == NULL || pNode == NULL) { return NULL; } tLinkTableNode * pTempNode = pLinkTable->pHead; while(pTempNode != NULL) { if(pTempNode == pNode) { return pTempNode->pNext; } pTempNode = pTempNode->pNext; } return NULL; }
3.2功能模块化
试想如果所有功能都写着main中,不仅十分的长而且代码混乱没有结构,如果存在bug是要对整个系统的调试,工作量巨大,另外变量众多也十分容易搞混,因此mian中只有保留程序的流程就行,其他功能独立写成一个模块。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "linktable.h" #include "linktable.c" #include "menu.h" tLinkTable * head = NULL; int Help(); int Quit(); #define CMD_MAX_LEN 1024 #define CMD_MAX_ARGV_NUM 32 #define DESC_LEN 1024 #define CMD_NUM 10 /* data struct and its operations */ typedef struct DataNode { tLinkTableNode * pNext; char* cmd; char* desc; int (*handler)(int argc, char *argv[]); } tDataNode; int SearchConditon(tLinkTableNode * pLinkTableNode,void * arg) { char * cmd = (char*)arg; tDataNode * pNode = (tDataNode *)pLinkTableNode; if(strcmp(pNode->cmd, cmd) == 0) { return SUCCESS; } return FAILURE; } /* 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; } /* show all cmd in listlist */ int ShowAllCmd(tLinkTable * head) { tDataNode * pNode = (tDataNode*)GetLinkTableHead(head); while(pNode != NULL) { printf("%s - %s\n", pNode->cmd, pNode->desc); pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode); } return 0; } int Help(int argc, char *argv[]) { ShowAllCmd(head); return 0; } /* add cmd to menu */ int MenuConfig(char * cmd, char * desc, int (*handler)(int argc, char*argv[])) { tDataNode* pNode = NULL; if ( head == NULL) { head = CreateLinkTable(); pNode = (tDataNode*)malloc(sizeof(tDataNode)); pNode->cmd = "help"; pNode->desc = "Menu List:"; pNode->handler = Help; AddLinkTableNode(head,(tLinkTableNode *)pNode); } pNode = (tDataNode*)malloc(sizeof(tDataNode)); pNode->cmd = cmd; pNode->desc = desc; pNode->handler = handler; AddLinkTableNode(head,(tLinkTableNode *)pNode); return 0; }
int main(int argc,char* argv[]) { MenuConfig("version","XXX V1.0(Menu program v1.0 inside)",NULL); MenuConfig("quit","Quit from XXX",Quit); ExecuteMenu(); }
四、可重用接口
尽管已经做了初步的模块化设计,但是分离出来的数据结构和它的操作还有很多菜单业务上的痕迹,为了这个API的可重用性,必须保证linktable它仅完成数据结构的操作,不能有一点点依赖某个项目。
因此这里linktable模块,这一模块是可重用的,只对外暴露其接口。linktable模块只做与数据处理有关的操作,不涉及对menu业务的操作,进一步实现了内聚,降低了耦合度。所以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);
强大的内聚功能已经实现,还有一问题就是要降耦合,还是以menu项目为例,寻找某个节点时使用FindCmd方法,数据结构里的的寻找节点方法又通过SearchCondition来判断此节点是否是我们所找的节点,而SearchCondition用依赖cmd这个业务层中定义的变量,所以linktable中依然存在业务层的痕迹,所以需要修改。
int SearchCondition(tLinkTableNode * pLinkTableNode) { tDataNode * pNode = (tDataNode *)pLinkTableNode; if(strcmp(pNode->cmd, cmd) == 0) { return SUCCESS; } return FAILURE; } /* find a cmd in the linklist and return the datanode pointer */ tDataNode* FindCmd(tLinkTable * head, char * cmd) { return (tDataNode*)SearchLinkTableNode(head,SearchCondition); }
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode)) { if(pLinkTable == NULL || Conditon == NULL) { return NULL; } tLinkTableNode * pNode = pLinkTable->pHead; while(pNode != pLinkTable->pTail) { if(Conditon(pNode) == SUCCESS) { return pNode; } pNode = pNode->pNext; } return NULL; }
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args) { if(pLinkTable == NULL || Conditon == NULL) { return NULL; } tLinkTableNode * pNode = pLinkTable->pHead; while(pNode != NULL) { if(Conditon(pNode,args) == SUCCESS) { return pNode; } pNode = pNode->pNext; } return NULL; }
以上是修改后的寻找方法,我们把cmd这个业务层的变量当作参数传递过来,以此降低耦合度。
五、线程安全
线程是操作系统最小的执行单位,在一个进程中可能共享同一个全局变量。在一个并发的计算机中,同时有两个或以上的线程在运行,如果每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。所以比较合适的方法就是为这个变量上读写锁。
以menu为例,在定义结构体时就增加了线程互斥变量
typedef struct LinkTable { tLinkTableNode *pHead; tLinkTableNode *pTail; int SumOfNode; pthread_mutex_t mutex; }tLinkTable;
并且在配套的操作方法里增加了对这个节点的上锁和解锁
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; }

浙公网安备 33010602011771号