代码中的软件工程-基于menu项目的理解
作者:互联网
代码中的软件工程——基于menu项目的理解
0.前言
本博客基于孟宁老师的menu项目案例,以VS Code + GCC工具集为主要环境。在仔细阅读menu项目源码后,结合代码分析其中涉及的软件工程思想。
1.编译和调试环境配置
下载安装MinGW-w64,VS Code,并在扩展部分添加C/C++插件。试运行hello.c文件:
环境配置成功!
2.软件工程思想
2.1模块化设计
模块化设计是软件工程开发中的指导思想,是指把整个项目分成相对独立的模块,使每个模块可以被独立的进行设计和开发。它的本质是软件开发中的关注点分离,把大问题分解成小问题,实际上就是分而治之的思想。模块化设计可以降低系统的耦合度,使之具有更好的扩展和可重用性。
在menu项目案例中,我们重点关注lab3.2和lab3.3。
发现lab3.3多出了linklist.c和linklist.h文件;仔细阅读代码,linklist.h文件的内容是对menu中应用到的关键数据结构的定义;而linglist.c文件则是调用了linklist.h文件,使用该文件定义的数据结构创建了具体的函数,并提供了相应操作。最后,lab3.3中的menu.c文件调用该函数,实现具体功能。对比lab3.2中的menu.c文件,它将数据结构的定义,函数的定义,功能的具体实现放在一起,很容易让阅读代码的人感到无所适从,难以定义代码所实现的功能。
/*lab3.2 menu.c*/
/**************************************************************************************************/
/* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */
/* */
/* FILE NAME : menu.c */
/* PRINCIPAL AUTHOR : Mengning */
/* SUBSYSTEM NAME : menu */
/* MODULE NAME : menu */
/* LANGUAGE : C */
/* TARGET ENVIRONMENT : ANY */
/* DATE OF FIRST RELEASE : 2014/08/31 */
/* DESCRIPTION : This is a menu program */
/**************************************************************************************************/
/*
* Revision log:
*
* Created by Mengning, 2014/08/31
*
*/
#include <stdio.h>
#include <stdlib.h>
int Help();
#define CMD_MAX_LEN 128
#define DESC_LEN 1024
#define CMD_NUM 10
/* data struct and its operations */
typedef struct DataNode
{
char* cmd;
char* desc;
int (*handler)();
struct DataNode *next;
} tDataNode;
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;
}
/* menu program */
static tDataNode head[] =
{
{"help", "this is help cmd!", Help,&head[1]},
{"version", "menu program v1.0", NULL, NULL}
};
int main()
{
/* cmd line begins */
while(1)
{
char cmd[CMD_MAX_LEN];
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;
}
lab3.3的主程序(menu.c)中的主体就是一个while循环,而其调用的函数的实现细节则被放在另一个模块中,这就是模块化设计的具体应用,将复杂的问题变成当前模块较简单的逻辑,把剩下的未解决问题放到另一个模块中。一层一层,最终将复杂问题拆分成一个个简单模块。
/*lab3.3 menu.c
/**************************************************************************************************/
/* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */
/* */
/* FILE NAME : menu.c */
/* PRINCIPAL AUTHOR : Mengning */
/* SUBSYSTEM NAME : menu */
/* MODULE NAME : menu */
/* LANGUAGE : C */
/* TARGET ENVIRONMENT : ANY */
/* DATE OF FIRST RELEASE : 2014/08/31 */
/* DESCRIPTION : This is a menu program */
/**************************************************************************************************/
/*
* Revision log:
*
* Created by Mengning, 2014/08/31
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"
int Help();
#define CMD_MAX_LEN 128
#define DESC_LEN 1024
#define CMD_NUM 10
/* menu program */
static tDataNode head[] =
{
{"help", "this is help cmd!", Help,&head[1]},
{"version", "menu program v1.0", NULL, NULL}
};
main()
{
/* cmd line begins */
while(1)
{
char cmd[CMD_MAX_LEN];
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;
}
/*lab3.3 linklist.h */
/**************************************************************************************************/
/* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */
/* */
/* FILE NAME : linklist.h */
/* PRINCIPAL AUTHOR : Mengning */
/* SUBSYSTEM NAME : menu */
/* MODULE NAME : linklist */
/* LANGUAGE : C */
/* TARGET ENVIRONMENT : ANY */
/* DATE OF FIRST RELEASE : 2014/09/10 */
/* DESCRIPTION : linklist for menu program */
/**************************************************************************************************/
/*
* Revision log:
*
* Created by Mengning, 2014/09/10
*
*/
/* data struct and its operations */
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 */
/**************************************************************************************************/
/* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */
/* */
/* FILE NAME : linklist.c */
/* PRINCIPAL AUTHOR : Mengning */
/* SUBSYSTEM NAME : menu */
/* MODULE NAME : linklist */
/* LANGUAGE : C */
/* TARGET ENVIRONMENT : ANY */
/* DATE OF FIRST RELEASE : 2014/09/10 */
/* DESCRIPTION : linklist for menu program */
/**************************************************************************************************/
/*
* Revision log:
*
* Created by Mengning, 2014/09/10
*
*/
#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;
}
2.2可重用接口
我们通常要求一个模块只实现一个功能,即功能内聚。这样可以增加模块的通用性。如果某模块实现的功能过多,在另一个需求场景下,所需要的功能可能会发生变动,再对模块进行修改就显得过于繁琐。若一个模块只实现一个功能,那调用相应模块的接口即可。即便需要修改,也只需阅读该模块的代码,而不用涉及实现其它功能的代码。这样的模块接口可重用性高。
在lab3中,其查询数据结构为一个链表,定义如下:
typedef struct DataNode
{
char* cmd;
char* desc;
int (*handler)();
struct DataNode *next;
} tDataNode;
若换一个应用场景,上述结构可能就不太适用。在lab4中,新增了一个数据结构tLinkTable,只表示链表的链接,不包含数据内容,其定义如下:
/*
* 新增的数据结构
*/
typedef struct LinkTableNode
{
struct LinkTableNode * pNext;
}tLinkTableNode;
/*
* LinkTable Type
*/
typedef struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;
}tLinkTable;
同时新增了一些函数API,例如:
/*
* get LinkTableHead
*/
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
在lab5中,还有另一种方法:
/*
* Search a LinkTableNode from LinkTable
* int Conditon(tLinkTableNode * pNode,void * args);
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);
利用callback函数参数使Linktable的查询接口更加通用,有效地提高了接口的通用性.这样可以有效地隐藏软件模块内部的实现细节,为外部调用接口的开发者提供更加简洁的接口信息,同时也减少外部调用接口的开发者有意或无意的破坏软件模块的内部数据。
2.3线程安全
线程是操作系统中的概念,是比进程更小的能够运行和调度的最小单位。在引入线程的操作系统中,线程是最小的执行单位,进程是最小的资源分配单位,可以更好的提高程序的吞吐率。在提升了系统性能的同时,与进程存在的同样的安全问题需要解决,就是线程对资源临界区的访问安全性问题,依然要遵循信号量机制,从而实现线程之间的同步、互斥等访问。
如果代码有多个线程在同时运行,如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
为确保线程安全,我们可以用读写锁来实现。
/*lab5.1 linktable.h */
typedef struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex; /*互斥信号量 */
}tLinkTable;
lab5.1中 linktable.c文件调用了 linktable.h 头文件,并采用了PV操作来保证临界资源的互斥访问,从而确保线程安全:
/*lab5.1 linktable.c */
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);
}
3.总结
模块化设计,可重用接口,线程安全等软件工程思想在从事编程开发的过程中都是非常重要的思想。时刻牢记,才能写出更为规范,通用性更强的代码。
4.参考文献
[1]menu项目案例:https://github.com/mengning/menu
标签:head,menu,代码,cmd,软件工程,线程,tDataNode,NULL 来源: https://www.cnblogs.com/hyyang5543/p/13945090.html