代码中的软件工程

  本文是学习孟宁老师的代码中的软件工程所作的学习记录。所使用的menu项目从hello world开始一步步构建起一个功能完善,逻辑清晰的项目,受益匪浅。下文逐步展开该项目的成长过程。

参考文献:

 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

1.环境配置

  1、首先要下载安装C/C++的编译器,这儿选择MinGW编译器。下载64位的编译器以后,直接打开进行安装,配置环境变量。

   

  安装成功后打开cmd,可以检查gcc版本。

  2.打开Vscode安装C/C++扩展,来提供对C++的支持,安装成功后如图所示,很简单。

  

  3.运行hello world

  打开项目后会有一个.vscode文件夹,并且里面有一个launch.json和tasks.json文件,这是项目配置文件,帮助找到编译器和调试器的地址,其中的路径应该与前面安装的一致。

  

  此时项目就可以运行了。

 二、模块化设计

  模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。关注点的分离的思想背后的根源是由于人脑处理复杂问题时容易出错,把复杂问题分解成一个个简单问题,从而减少出错的情形。

  本项目中尝试把数据结构和具体的业务代码相分离,并且逐步打包到不同的模块中。例如,在lab3.3中将链表数据结构放到linklist.h中声明,在linklist.c中实现,在menu.c 中调用它实现了模块的分离,下图展示了该项目的结构和linklist.h的内容。

  

 

三.可重用接口 

  虽然当前项目已经做了比较好的模块分割,项目的结构更加清晰,可维护性也大大加强。但其实链表模块中包含了具体业务的痕迹,如果我想在其他项目中使用这个模块,就没有那么容易了。因此,我们需要更加通用的接口。

  可重用接口可以使接口进行通用化,把linklist接口改为更为通用的listable接口,listable接口只实现链表的逻辑部分,数据部分由调用者添加,增加了代码的可重用性。

  这段代码展示了listable的接口部分,注意链表节点没有数据,只有逻辑实现这种做法可以使链表模块更加通用。

 1 typedef struct LinkTableNode
 2 {
 3     struct LinkTableNode * pNext;
 4 }tLinkTableNode;
 5 
 6 /*
 7  * LinkTable Type
 8  */
 9 typedef struct LinkTable
10 {
11     tLinkTableNode *pHead;
12     tLinkTableNode *pTail;
13     int            SumOfNode;
14     pthread_mutex_t mutex;
15 }tLinkTable;
16 
17 /*
18  * Create a LinkTable
19  */
20 tLinkTable * CreateLinkTable();
21 /*
22  * Delete a LinkTable
23  */
24 int DeleteLinkTable(tLinkTable *pLinkTable);
25 /*
26  * Add a LinkTableNode to LinkTable
27  */
28 int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
29 /*
30  * Delete a LinkTableNode from LinkTable
31  */
32 int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
33 /*
34  * get LinkTableHead
35  */
36 tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
37 /*
38  * get next LinkTableNode
39  */
40 tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);

这段代码展示了menu模块对接口的调用方法,注意此处加载了数据部分。

typedef struct DataNode
{
    tLinkTableNode * pNext;
    char*   cmd;
    char*   desc;
    int     (*handler)();
} tDataNode;
//初始化链表
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);
    pNode = (tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd = "version";
    pNode->desc = "Menu Program V1.0";
    pNode->handler = NULL; 
    AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode);
    pNode = (tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd = "quit";
    pNode->desc = "Quit from Menu Program V1.0";
    pNode->handler = Quit; 
    AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode);
 
    return 0; 
}

/* 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;
}

三。线程安全

  线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

  可重入(reentrant)函数:可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。

  可重入的函数不一定是线程安全的,可能是线程安全的也可能不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题;不可重入的函数一定不是线程安全的。

menu项目中使用锁机制来保证线程安全。

/*创建链表时初始化锁*/
tLinkTable * CreateLinkTable()
{
    tLinkTable * pLinkTable = (tLinkTable *)malloc(sizeof(tLinkTable));
    if(pLinkTable == NULL)
    {
        return NULL;
    }
    pLinkTable->pHead = NULL;
    pLinkTable->pTail = NULL;
    pLinkTable->SumOfNode = 0;
    //初始化锁
    pthread_mutex_init(&(pLinkTable->mutex), NULL);
    return pLinkTable;
}
/*涉及到共享数据同时写的地方采用加锁去锁机制*/
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项目的学习总结。

posted @ 2020-11-09 23:54  zhaowenhao  阅读(128)  评论(0)    收藏  举报