代码中的软件工程---menu项目代码分析
前言:本文是基于中国科学技术大学软件学院孟宁老师的高级软件工程课上的c语言项目menu项目进行分析。
一 配置环境
1)在vscode上安装c/c++插件

2)安装c/c++编译器 MinGW
下载地址:[https://sourceforge.net/projects/mingw/]
下载完成后打开

选择mingw32-gcc.bin, mingw32-gcc-g++.bin, 以及mingw32-gdb.bin 下载。
3)配置vscode项目结构

二 项目代码分析
1,简约而不简单
1 /**************************************************************************************************/ 2 /* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */ 3 /* */ 4 /* FILE NAME : menu.c */ 5 /* PRINCIPAL AUTHOR : Mengning */ 6 /* SUBSYSTEM NAME : menu */ 7 /* MODULE NAME : menu */ 8 /* LANGUAGE : C */ 9 /* TARGET ENVIRONMENT : ANY */ 10 /* DATE OF FIRST RELEASE : 2014/08/31 */ 11 /* DESCRIPTION : This is a menu program */ 12 /**************************************************************************************************/ 13 14 /* 15 * Revision log: 16 * 17 * Created by Mengning, 2014/08/31 18 * 19 */ 20 21 22 #include <stdio.h> 23 #include <stdlib.h> 24 25 int Help(); 26 int Quit(); 27 28 #define CMD_MAX_LEN 128 29 #define DESC_LEN 1024 30 #define CMD_NUM 10 31 32 typedef struct DataNode 33 { 34 char* cmd; 35 char* desc; 36 int (*handler)(); 37 struct DataNode *next; 38 } tDataNode; 39 40 static tDataNode head[] = 41 { 42 {"help", "this is help cmd!", Help,&head[1]}, 43 {"version", "menu program v1.0", NULL, &head[2]}, 44 {"quit", "Quit from menu", Quit, NULL} 45 }; 46 47 int main() 48 { 49 /* cmd line begins */ 50 while(1) 51 { 52 char cmd[CMD_MAX_LEN]; 53 printf("Input a cmd number > "); 54 scanf("%s", cmd); 55 tDataNode *p = head; 56 while(p != NULL) 57 { 58 if(strcmp(p->cmd, cmd) == 0) 59 { 60 printf("%s - %s\n", p->cmd, p->desc); 61 if(p->handler != NULL) 62 { 63 p->handler(); 64 } 65 break; 66 } 67 p = p->next; 68 } 69 if(p == NULL) 70 { 71 printf("This is a wrong cmd!\n "); 72 } 73 } 74 } 75 76 int Help() 77 { 78 printf("Menu List:\n"); 79 tDataNode *p = head; 80 while(p != NULL) 81 { 82 printf("%s - %s\n", p->cmd, p->desc); 83 p = p->next; 84 } 85 return 0; 86 } 87 88 int Quit() 89 { 90 exit(0); 91 }
在menu项目的代码中,我们可以看到代码在变量命名或者函数等命名都遵循了一定的代码规范,大大的增加了代码的可读性,同时也使用了合理而又适当的注释。
其他方面,包括代码缩进,括号怎么打,放在哪里,怎么进行换行都符合了一定的规范,视觉上就令人觉得看起来舒服。
2,模块化
整个项目的代码符合了模块化设计的规范,每一个软件模块都只有一个功能目标,并相对独立于其他软件模块,使得每一个软件模块都容易理解容易开发。
从而整个软件系统也更容易定位软件缺陷bug,因为每一个软件缺陷bug都局限在很少的一两个软件模块内。 而且整个系统的变更和维护也更容易,因为一个软件模块内的变更只影响很少的几个软件模块。
耦合度:耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合,松散耦合和无耦合。
一般我们在软件设计中我们追求松散耦合。

menu项目中体现了松散耦合的思想和最求,遵循了KISS原则。
KISS原则:
一行代码只做一件事
一块代码只做一件事
一个函数只做一件事
一个软件模块只做一件事
同时使用了本地化外部接口来提高代码的适用能力:

3,可重用软件
消费者重用是指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。软件开发者在重用已有的软件模块代码时一般会重点考虑如下四个关键因素:
该软件模块是否能满足项目所要求的功能;
采用该软件模块代码是否比从头构建一个需要更少的工作量,包括构建软件模块和集成软件模块等相关的工作;
该软件模块是否有完善的文档说明;
该软件模块是否有完整的测试及修订记录;
如上四个关键因素需要按照顺序依次评估。
* * 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_ */
在这个接口代码中,清晰的写明了接口的目的,命名规范,同时在它在实现上没有和业务代码进行耦合,如果在其他地方上有相似的需求,可以很方便对这个数据结构进行重用。
4,可重入和线程安全
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。
可冲入函数的基本要求;
不为连续的调用持有静态数据;
不返回指向静态数据的指针;
所有数据都由函数的调用者提供;
使用局部变量,或者通过制作全局数据的局部变量拷贝来保护全局数据;
使用静态数据或全局变量时做周密的并行时序分析,通过临界区互斥避免临界区冲突;
绝不调用任何不可重入函数。
可重入的函数不一定是线程安全的,可能是线程安全的也可能不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题; 不可重入的函数一定不是线程安全的。
/* * 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; } /* * 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; }
在上面的代码中我们可以看到,在增加,删除节点时,都会进行加锁,保证线程安全。
如果对包含资源共享的代码区添加互斥锁,则这样的函数依然是可重入的。
/* * Search a LinkTableNode from LinkTable * int Conditon(tLinkTableNode * pNode); */ 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; }
如果,一个函数没有对资源进行修改,不加锁也是可重入的。
浙公网安备 33010602011771号