代码中的软件工程--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菜单还包含更多、更复杂的设计,但这都离不开上述小节中涉及的软件工程思想。
本次文章就介绍到这里,非常感谢孟宁老师提供的菜单小程序,让我对软件工程模块化思想有了进一步更深的理解!通过学习这些设计理念我也会努力将其运用到实战中,增强代码的可用性。

浙公网安备 33010602011771号