代码中的软件工程

  软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。在软件开发过程中,软件工程发挥极其重要的作用。本文以一个menu小程序为案例,对模块化设计、可重用接口、线程安全等议题结合代码进行理解和分析。

  参考资料: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.在vscode上下载C/C++插件;

 

 2.安装C/C++编译器,配置环境变量,配置后使用gcc -v检查是否配置成功;

 

 3.进入vscode配置launch.json和tasks.json文件

 

 二、模块化设计

  模块化是在软件系统设计时保持系统内各部分相对对立,以便每一个部分可以被独立地进行设计和开发。每一个模块只具有单一的功能,并且独立于其他的模块,使得系统在开发、定位bug、系统变更和维护等方面变得更加容易。

  一般使用耦合度和内聚度来衡量软件模块化的程度。耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合、松散耦合和无耦合。内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度,理想的内聚是功能内聚,使得每一个模块只具备单一的功能。

linktable.c

struct LinkTable
{
    tLinkTableNode *pHead;
    tLinkTableNode *pTail;
    int            SumOfNode;
    pthread_mutex_t mutex;

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

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

 menu.c

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

 

  在linktable.c中定义了所需的数据结构,以及对它的各种操作,不涉及任何的业务代码,专心完成对数据结构的操作,而在menu.c中无需任何直接对数据结构的操作代码,只需要调用提供的接口即可。linktable.c只实现一个功能,即是对数据结构的操作,实现了功能的内聚。menu.c中只调用接口完成所需的操作,而不直接对数据结构进行操作,降低了模块之间的耦合度。

 

三、可重用接口

  接口就是相互联系的双方共同遵守的一种协议规范,在软件系统内部一般接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。接口定义了软件模块对系统的其他部分如何访问所提供的服务。

  接口规格是软件系统的开发者正确使用一个软件模块需要知道的所有信息,那么这个软件模块的接口规格定义必须清晰明确地说明正确使用本软件模块的信息。一般来说,接口规格包含五个基本要素:

  • 接口的目的;
  • 接口使用前所需要满足的条件,一般称为前置条件或假定条件;
  • 使用接口的双方遵守的协议规范;
  • 接口使用之后的效果,一般称为后置条件;
  • 接口所隐含的质量属性 
/*
 * LinkTable Node Type
 */
typedef struct LinkTableNode
{
    struct LinkTableNode * pNext;
}tLinkTableNode;

/*
 * LinkTable Type
 */
typedef struct LinkTable 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);
/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode,void * args);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);
/*
 * get LinkTableHead
 */
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
/*
 * get next LinkTableNode
 */
tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);

   linktable.h中列出了各种接口的定义,符合上述的五大基本要素中的几种。如int DeleteLinkTable(tLinkTable *pLinkTable):

  • 该接口的目标是删除一个链表,函数名DeleteLinkTable清晰明确的表明了接口的目标;
  • 该接口的前置条件是链表必须存在使用该接口才有意义,也就是链表pLinkTable!=NULL;
  • 使用该接口的双方遵守的协议规范是通过数据结构tLinkTableNode和tLinkTable定义的;
  • 使用该接口之后的效果是删除了一个链表,这里是通过int类型的值作为返回值来作为后置条件;
  • 该接口没有特别要求的质量属性。

 

、线程安全

  线程是操作系统能够进行运算调度的最小单位。它包含在进程当中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 

  如果多线程间并发运行,执行结果和多线程线性运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。线程安全问题都是由全局变量及静态变量引起的。例如对两个线程对一个全局变量进行操作,预期中第一个进程读写了全局变量,然后第二个线程也读写了全局变量。然而在并发的情况下,可能出现第一个线程读取了全局变量还没来得及写回,这是第二个线程读取了全局变量,然后两个线程分别对全局变量进行写回。这时运行的结果和预期中的并不相同,是线程不安全的。

  解决线程安全问题的一个方法就是加锁,在一个线程对全局变量或静态变量读写的时候,加上锁,在未完成读写操作的时候不准其他线程对此变量进行读写,在读写完成之后才解开锁,让其他线程访问此变量。

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

   如函数DeleteLinkTable中在对链表节点进行操作的时候,对链表上锁,防止其他线程对链表进行读写,在完成了删除操作之后解开锁,释放了链表让其他线程读写,保证了线程的安全。

posted @ 2020-11-10 21:06  大方对  阅读(138)  评论(0)    收藏  举报