概述
0. 前言
本博客主要以VS Code + GCC工具集为主要环境,编译调试孟宁老师的课程项目案例menu[1]。在仔细阅读孟老师提供的代码内容后,领悟在代码中运用到的软件工程思想,体会代码是如何逐步健壮起来的,思考其背后的原因及意义。
1. 环境配置、编译及问题修改
环境配置
本文主要基于VS Code和GCC工具集作为主要环境,在本文的前一篇关于git的应用[2]一文中给出了具体的软件安装、环境变量和文件的配置,这里在文末给出链接,不再赘述。
编译
Menu项目基于c语言实现的,这里给出一个简单的的文件的编译及其运行。运行完毕后,可以在控制台中看到经典输出”hello world!”。
1 #include <stdio.h>
2
3 int main()
4 {
5 printf("hello world!n");
6 }
部分代码问题修改
在部分代码(如lab3以后的运行中),代码可以运行并出现结果,但是在编译的时候,Vs code会抛出一个警告:
虽然程序员可能看不到warning,但是当时我还是查阅了一下为什么会抛出这样的警告,并在头文件上增加了#include<,再次运行后便可以解决这个警告,下面是修改后运行lab3中的代码的结果,可以看到菜单功在lab3中已经初具规模,并已经消除警告。同时我们可以在这个模拟的menu菜单下键入简单的输入(错误输入、help和quit操作)。
2. 代码的成长
正如孟老师在课件中写的那样:罗马不是一天建成的,不要期望一蹴而就。我们写代码,做项目也一样,并非一口吃成一个胖子。
可以看到项目包中的menu的程序也是逐渐成长,从lab1到lab7展现了我们先在一个hello文件得到启发,逐步引入规范化编码、引擎、模块化思想、可重用接口和线程安全等内容,体现了软件工程的思想,使得程序更加的健壮。
3. 模块化设计
模块化设计思想
模块化程序设计是指在进行程序设计时将一个大程序按照功能划分为若干小程序模块,每个小程序模块完成一个确定的功能,并在这些模块之间建立必要的联系,通过模块的互相协作完成整个功能的程序设计方法[3]。
menu代码中的模块设计
我们将目光聚焦在lab和上,在不查看源代码的情况下,可以看到相比较lab,多出了和两个文件。
如果仔细翻看两个文件夹下的代码内容,其实没有本质上的差别,只是在中,我们将menu中运用到的关键数据结构链表定义和函数操作申明在了文件中,将对链表的操作的具体实现在了文件中,主程序中的操作逻辑并没有变化。
这看似好像是多此一举的操作,但是实际上却初具了软件工程中的模块化思想,我们将原本较为复杂的主程序进行了模块化的划分,主程序中只包含了引擎(while循环),将其余复杂的问题以模块、接口的方式提供给主程序,方便我们进行代码维护。模块化的好处就是我们可以将关注点进行分离,就像是算法中的“分治算法”,模块内部是如何实现的我们是不关心的,我们只需要专注于我们当前的问题就可以了。
4. 可重用接口
代码重用和接口
一个好的代码模块应该具有松散耦合的特点,这样软件模块之间依赖程度小,便于重用提升效率。
我们通常要求软件模块只做一件事情,即功能内聚,通常可以通过接口来实现。接口是双方共同遵守的一种协议规范,在软件系统内部通常是定义一组API函数约定模块之间的通信关系。
menu代码中的可重用接口
让我们继续关注整个项目中还有哪些地方体现了模块化思想。在lab3整个设计过程中,我们看到其查询数据结构定义如下,这是一个链表:
typedef struct DataNode
{
char* cmd;
char* desc;
int (*handler)();
struct DataNode *next;
} tDataNode;
但是有趣的是,当我们看到lab4的时候,整体的程序依旧是基于tDataNode定义的链表进行菜单查找的,但是却新增了一个数据结构tLinkTable,只是表示链表的链接,不包含数据内容,其定义如下所示:
/*
* 新增的数据结构
*/
typedef struct LinkTableNode
{
struct LinkTableNode * pNext;
}tLinkTableNode;
/*
* LinkTable Type
*/
typedef struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;
}tLinkTable;
并且在lab4中,我们的查找算法也进行了一定的修改,我们并不是查找tDataNode,而是选用tLinkTable进行搜索,并需要进行类型的强制转换(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) == 0)
{
return pNode;
}
pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode);
}
return NULL;
}
可能你又觉得这是在多此一举了。但实际上,在这里我们做到了业务逻辑层和数据存储层的分离[4]。tLinkTable相当于逻辑层面的索引关键字,并不真正的存放数据,根据此可以通过我们定义的接口进行数据访问。
孟宁老师上课时还提到了,这里的写法就像是面向对象编程思想中的继承概念(子类tDataNode的父类是tLinkTable,在父类基础上实现了扩展操作),虽然c语言是面向过程编程的语言。查询代码只需要使用父类结点,那我们就可以对父类结点进行继承,扩充成我们需要的新数据结构。所以将业务逻辑和数据存储的分离,便于我们进行代码重用,符合软件工程中的开闭原则(可扩展,禁修改)。
5. 线程安全
线程安全概念
线程是操作系统中的概念,是比进程更小的能够运行和调度的最小单位。在引入线程的操作系统中,线程是最小的执行单位,进程是最小的资源分配单位,可以更好的提高程序的吞吐率[5]。
在提升了系统性能的同时,与进程存在的同样的安全问题需要解决,就是线程对资源临界区的访问安全性问题,依然要遵循信号量机制,从而实现线程之间的同步、互斥等访问。
menu代码中的线程安全
在lab5文件夹的linktable中,我们可以看到引入了头文件#include <,并且在链表数据结构中,增加了一个信号量mutex:
typedef struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;//互斥信号量
}tLinkTable;
在lab5中,我们将链表作为了一种临界资源,需要按照信号量机制,对其进行互斥访问,即PV操作。具体的实现例子如下所示:
while(pLinkTable->pHead != NULL)
{
tLinkTableNode * p = pLinkTable->pHead;
pthread_mutex_lock(&(pLinkTable->mutex));//P操作
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1 ;
pthread_mutex_unlock(&(pLinkTable->mutex));//V操作
free(p);
}
在DeleteLinkTable中,我们在while循环中访问链表时,lock操作和unlock操作,就实现了我们上述提到的PV操作,从而可以实现临界资源的互斥访问,保障线程安全。
6. 总结
阅读和运行了menu代码之后,我们可以明显的感受到一个代码是如何健壮起来的,同时是如何一步一步引入软件工程中的一些重要思想(模块化、可重用、线程安全等)。也希望自己能将课堂上学到的这些内容应用在自己的工作生活中,让自己写的代码更加符合软件工程规范。
7. 参考资料
[1] mengning/menu,
[2]git的简单应用与实践,
[3]模块化程序设计,
[4]三层架构:表示层-业务逻辑层-数据访问层,
[5]
最后
以上就是贪玩热狗为你收集整理的android menu菜单 实现点击后不消失_menu项目成长中体会代码的软件工程 - dlzjc的全部内容,希望文章能够帮你解决android menu菜单 实现点击后不消失_menu项目成长中体会代码的软件工程 - dlzjc所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复