代码中的软件工程--menu项目的启示

 

------------恢复内容开始------------

1.前言

本文通过对menu项目的分析,探讨代码中涉及的软件工程思想。本文涉及的项目来自孟宁老师的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

2.编译和调试环境的配置

本文使用了vscode作为开发工具,vscode的安装过程就不作解释。编译环境通过菜单选择c++环境,然后选择了gcc10作为编译器

 

 

最后自动生成了vscode的配置文件如下所示:

 

到此我们的环境就算配置成功了,运行一下那个生成的hello文件,可以得到hello world的输出,这里就不再贴图了。

3.模块化设计

模块化,是在软件体系设计时保持系统内部各部分相对独立,以便每个部分都可以独立进行设计和开发。

模块化可以用耦合度和内聚度来衡量。

在上一小节中我们配置好了开发环境,接着就开始menu菜单的开发工作。

因为我们的菜单程序刚开始做,初步版本只实现很小的功能,并在后续一步一步扩展,在这里第一版本只实现Help和Quit命令。

所实现的代码如下:

 

在while循环中通过if语句判断命令的类型并做相应逻辑。

然而这样的代码,在现在看起来非常直观、很好理解,但是却非常不利于后期的维护,比如在后期我们加入10个命令就要不断修改if和else语句。

这时候我们就可以利用模块化的思想来优化代码,比如将命令的种类设计成一个模块。

于是有了下一个版本的代码:

int main()
{
    /* cmd line begins */
    while(1)
    {
        char cmd[CMD_MAX_LEN];
        printf("Input a cmd number > ");
        scanf("%s", cmd);
        tDataNode *p = head;//所有的命令存放到链表中
        while(p != NULL)//循环遍历链表,找到对应的处理命令  
        {
            if(strcmp(p->cmd, cmd) == 0)
            {
                printf("%s - %s\n", p->cmd, p->desc);
                if(p->handler != NULL)
                {
                    p->handler();//执行命令处理函数
                }
                break;
            }
            p = p->next;
        }
        if(p == NULL) 
        {
            printf("This is a wrong cmd!\n ");
        }
    }
} 

这里,我们借助了链表这一数据结构,将命令存储起来,并通过遍历链表找到命令处理的函数执行。

这一版本中,链表的定义如下所示:

typedef struct DataNode
{
    char*   cmd;
    char*   desc;
    int     (*handler)();
    struct  DataNode *next;
} tDataNode;

static tDataNode head[] = 
{
    {"help", "this is help cmd!", Help,&head[1]},
    {"version", "menu program v1.0", NULL, &head[2]},
    {"quit", "Quit from menu", Quit, NULL}
};

这样,如果我们需要扩展一个命令,只需要在链表中插入一个结点,并编写一个对应handler函数即可完成这个命令的功能,

比如下面代码展示了help命令的处理过程:

int Help()
{
    printf("Menu List:\n");
    tDataNode *p = head;
    while(p != NULL)
    {
        printf("%s - %s\n", p->cmd, p->desc);
        p = p->next;
    }
    return 0; 
}

以上的代码修改只是一个很小的例子。进一步地,如果更加规范的模块化程序,我们可以将命令设计成一个单独的模块,定义

命令的初始化逻辑、存放命令的数据结构、查找命令的方式等等,均可以扩展。在精心设计的模块中,我们可以很轻松地完成

menu程序的扩展,而对menu程序的主函数不做修改。

4.可重用接口

虽然在上一节中,我们做了初步的修改,将查找命令的步骤设计成了链表的查找,但是这种做法还是会有很多菜单业务的痕迹。

因此我们可以进一步优化代码,将链表的操作和菜单的业务分离开来。这样做,也是将链表操作定义成一组接口。

接口,是一组互相联系的双方共同遵守的一种协议规范。

在接下来的版本中,我们新建了一个linkedlist.h定义链表操作的接口和linkedlist.c来具体实现它

其中linkedlist.h中定义如下的数据和接口

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

 而将menu菜单的主函数修改成如下:

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

 如此一来,我们将链表的操作和menu菜单的业务分离开来。

在这里,我们还只是对链表做了最简单的功能作为示例,实际上链表的操作有很多,包括初始化、添加删除结点等。在接下去的版本中会再做进一步的修改优化。

5.小结

在本文中,我们以menu菜单小程序为例,简单介绍了vscode环境的搭建、软件工程的模块化以及可重用接口的设计。

虽然例举的例子只是menu菜单的一小部分,其实menu菜单还包含更多、更复杂的设计,但这都离不开上述小节中涉及的软件工程思想。

本次文章就介绍到这里,非常感谢孟宁老师提供的菜单小程序,让我对软件工程模块化思想有了进一步更深的理解!通过学习这些设计理念我也会努力将其运用到实战中,增强代码的可用性。

posted @ 2020-11-08 16:49  xiaoze8  阅读(175)  评论(0)    收藏  举报