由Menu小项目所引发的对软件工程的思考

  学习了孟老师的这几节课程,我学习了如何搭建一个简单的命令行menu小程序,从最简单的程序开始,一步步的根据软件工程的一般规律,进行逐步开发、完善,最终实现了一个比较通用的menu程序,可以让别的开发者进行作为内嵌子程序调用执行。经过这段时间的学习,收益匪浅,于是将一些学到的东西总结如下。

  为了menu小程序的开发,首先需要配置VSCOde 的C++环境,因为VScode不带C++的编译器。

1 编译和环境配置

  首先从官网下载MinGW-w64,下载Install安装程序,安装过程十分简单,一路next即可。安装完毕之后通过cmd执行gcc -v查看安装是否成功:

 

   安装完成之后我们打开VScode,在扩展部分添加C/C++插件:

 

  之后打开文件夹,在项目根目录下调用终端程序,输入code.命令,即可生成三个配置文件task.json,launch.json,c_cpp_propertities.json,我们修改task.json中的command项和option,将之前的mingw64的目录复制到如下部分即可:

"command": "D:\\2Software\\mingw64\\bin\\g++.exe""options": {
              "cwd": "D:\\2Software\\mingw64\\bin"
          }

  至此,环境配置成功,可以运行menu小程序。

2 软件工程一般原理分析

  2.1  模块化设计

  模块化设计是软件工程开发中的指导思想,是指把整个项目分成相对独立的模块,使每一个模块可以被独立的进行设计和开发。它的本质是软件开发中的关注点分离,把大问题分解成小问题,再逐个击破,实质就是分而治之的思想。

  模块化设计可以降低系统中的耦合度,可以进行更好的扩展和可重用。体现在menu项目中,就是数据结构和菜单业务进行分离处理,在逻辑上进行了切分,同时将接口的声明和实现放在不同的文件中,具体的分析详见代码头部的注释:

 

linklist.h文件进行接口的声明,.c文件进行接口的具体实现,具体使用的时候只需要调用接口即可:

//linklist.h接口的声明,同时把底层数据结构放在这里
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接口的具体实现过程,可以不关注,直接调用即可


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

 

//main函数,易于扩展,只需要在head数组添加新功能即可,同时在业务逻辑不涉及具体的数据结构,只传入指针,那么下次不使用链表的时候也可以重用。p->handler也有多态的思想在里面:

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();
        }
   
    }
}

 

  2.2  可重用接口

  可重用接口这部分是使接口进行通用化,把linklist接口改为更为通用的listable接口,增加了代码的可重用性。分析见代码的顶层注释:

//linktable.h文件
//LinktableNode结构体只保留了最基本的遍历功能,具体的data数据并没有包含,这是因为用户可以自己添加自己所需要的数据,而linktable.h这个通用接口只需要实现最基本的遍历功能即可,无需关心数据,只需关心遍历这一个逻辑,这样就使接口更通用,可重用性更高。

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);

 

//主程序调用更加一般的接口来实现

main()
{
    InitMenuData(&head); 
   /* 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();
        }
   
    }
}

 

//使用callback方式进行回调,有点像lamb表达式

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);
}
//为了更加通用,可以修改cmd数组,使其变为局部变量,同时增加一个args参数

tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);

int SearchCondition(tLinkTableNode * pLinkTableNode, void * args)
{
    char * cmd = (char*) args;
    tDataNode * pNode = (tDataNode *)pLinkTableNode;
    if(strcmp(pNode->cmd, cmd) == 0)
    {
        return  SUCCESS;  
    }
    return FAILURE;           
}

 

  2.3  线程安全

线程安全主要是使用锁机制,在多个进程同时写的时候或者一个读一个写的时候容易发生。可重入的函数不一定是线程安全的,可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重用函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题。不可重用的函数一定不是线程安全的。详细结合代码分析,见代码首行注释

//这个方法一般不会引发线程安全问题,因为创造table会开辟不同的空间
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;
}

/*
 * 删除可能会引发线程安全问题,主要是在free操作上会引发安全问题
 */
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;        
}

/*
 * 不会引发线程安全问题
 */
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;        
}

/*
 * 一般不会有线程安全问题,但是如果一个进程创建,一个删除,可能会引发线程安全问题
 */
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;        
}

/*
 读操作,不会引发线程安全问题
 */
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;
}

/*
 * 读操作,不会引发线程安全问题
 */
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable)
{
    if(pLinkTable == NULL)
    {
        return NULL;
    }    
    return pLinkTable->pHead;
}

/*
 * 读操作,不会引发线程安全问题
 */
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;
}

 

 

 总结:

我们开发软件的时候应该按照软件工程的一般规律,在模块化、可重用接口的设计、以及线程安全的问题上多下文章,尽可能的提高软件开发的效率和软件的质量。

posted @ 2020-10-31 16:59  吾心似秋月666  阅读(349)  评论(0编辑  收藏  举报