代码中的软件工程 - 命令行的菜单小程序
编译和环境配置
从MinGW官网(http://www.mingw.org/)下载mingw-get-setup.exe,安装过程很简单,就省略了。
完成安装后

下载Basic Setup里所有的包,但是因为网络的问题会非常慢,甚至会下载不下来,科*上网也不管用。解决方法就是自己去官网上下载包,而且因为时间久远,有一些包的路径发生了变化,也只能去官网上下载。

然后将MinGW的bin添加到环境变量中,在cmd中输入gcc -v查看gcc是否安装成功。

然后需要在VS Code中下载一个插件

点击调试,会出现一个launch.json文件,需要修改几个地方

program一项,指明了需要运行的文件,$()的格式是表明这是变量,又vscode自己添加,根据英文可以知道这个变量的含义,miDebuggerPath是我们安装gdb的路径,前面提到过他,是用来调试的二进制文件(可执行二进制文件也叫命令)。
生成tasks.json可以通过,菜单里的任务,然后配置任务,模板然后选other就可以,几个必须的配置项就像下面

再点一次调试就可以运行了。
代码的成长

在第一个目录中,这个项目还只是一个只有几行的hello.c程序,在之后一点一点地壮大、成长。从hello world开始不断迭代调试使代码长的越来越像一个命令行的菜单小程序。
模块化软件设计
模块化是在软件系统设计时保持系统内各部分相对独立,以便每个部分可以被独立地进行设计和开发。这个做法背后地基本原理时关注点地分离。关注点地分离在软件工程领域时最重要地原则,我们习惯上称为模块化;关注点地分离地思想背后地根源时由于人脑处理复杂问题时容易出错,把复杂问题分解成一个个简单问题,从而渐少出错地情形。
在menu项目中,对比lab3.2和lab3.3中的代码。
//lab3.2 menu.c #include <stdio.h> #include <stdlib.h> int Help(); #define CMD_MAX_LEN 128 #define DESC_LEN 1024 #define CMD_NUM 10 /* data struct and its operations */ typedef struct DataNode { char* cmd; char* desc; int (*handler)(); struct DataNode *next; } tDataNode; 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; } /* menu program */ static tDataNode head[] = { {"help", "this is help cmd!", Help,&head[1]}, {"version", "menu program v1.0", NULL, NULL} }; int 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(); } } } int Help() { ShowAllCmd(head); return 0; }
lab3.2中只有menu.c一个文件,在项目中,已经使用结构体封装命令,并使用一个数组存储所有的命令。
//lab3.3 menu.c
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"
int Help();
#define CMD_MAX_LEN 128
#define DESC_LEN 1024
#define CMD_NUM 10
/* menu program */
static tDataNode head[] =
{
{"help", "this is help cmd!", Help,&head[1]},
{"version", "menu program v1.0", NULL, NULL}
};
int 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();
}
}
}
int Help()
{
ShowAllCmd(head);
return 0;
}
//lab3.3 linklist.c #include <stdio.h> #include <stdlib.h> #include "linklist.h" 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; }
lab3.3又做了进一步的封装,将原先在menu.c文件的结构体和函数抽出,结构体和函数声明放在linklist.h中,函数定义放在linklist.c中,简化menu.c文件。符合KISS原则:一行代码只坐意见事,一个块代码只坐一件事,一个函数只做一件事,一个软件模块只做一件事。
可重用接口
消费者重用事指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。其中有四个关键要素:
1、该软件模块是否能满足项目所有要求的功能。
2、采用该软件模块是否比从头构建一个需要更少的工作量。
3、该软件模块是否有完善的文档说明。
4、该软件模块是否有完整的测试及修订记录。
软件模块接口在面向过程的语言中一般是定义一些数据结构和函数接口API,在面向对象的编程语言中一般在类或接口类中定义一些公有的(public)属性和方法。两类编程语言中接口形式上有很大不同,但是不管是函数接口API还是公有的方法本质上都是函数定义。我们将重点介绍两种函数接口方式,即Call-in方式的函数接口和Callback方式的函数接口。这里我们先来理解函数接口规格。
在lab5.1中定义了很多接口。
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); */ tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode)); /* * get LinkTableHead */ tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable); /* * get next LinkTableNode */ tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
线程安全
线程(thread)是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一般默认一个进程中只包含一个线程。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
如下代码是我们在menu.h中定义的两个接口,一个是通过给出命令的名称、描述和命令的实现函数定义一个命令;另一个是启动menu引擎。
/* add cmd to menu */ int MenuConfig(char * cmd, char * desc, int (*handler)()); /* Menu Engine Execute */ int ExecuteMenu()
参照main函数参数的写法,我们可以将menu子系统的接口升级如下:
/* add cmd to menu */ int MenuConfig(char * cmd, char * desc, int (*handler)(int argc, char*argv[])); /* Menu Engine Execute */ int ExecuteMenu()
总结
我们开发软件的时候应该按照软件工程的一般规律,在模块化、可重用接口的设计、以及线程安全的问题上多下文章,尽可能的提高软件开发的效率和软件的质量。
参考:https://gitee.com/mengning997/se/blob/master/README.md#代码中的软件工程

浙公网安备 33010602011771号