menu项目中体会软件工程的优雅设计

本文参考文档如下:
软件工程-码农的自我修养 作者:孟宁 基于此文学习了软件工程实践中的约定规范与设计方法
项目源码-menu 基于此项目进行了学习与分析,总结而得本篇报告

软件工程是一个庞大的议题,每每想起脑海中便浮现起一本本厚重的砖头书目,初学者学习起来不免有些乏味且漫无目的,本文结合孟老师软件工程课上教授的软件工程方法与菜单系统menu项目就编译调试环境搭建、模块化设计、可重用、线程安全、编译实战五个方面展开讨论。

一、编译调试环境搭建

1. C++/C运行环境配置

大部分情况下,C++/C环境配置好编译器与调试器即可,linux环境下使用sudo apt-get install -y gcc安装编译器,使用sudo apt-get install -y gdb安装调试器,两者安装好以后可以分别使用gcc -v以及gdb -v命令检查运行环境是否安装成功。

2. vscode配置

vscode本质上只是一个文本编辑器,那么为了编译和调试项目我们需要加装C/C++插件来使用一些C++/C配置文件管理、代码补全等功能,至此基本的C/C++设置已经配置好了。

接下来使用vscode打开项目文件夹,开始进行具体项目的编译调试配置,相对应编译器以及调试器,分别需要对项目进行编译配置文件tasks.json以及调试配置文件launch.json来设置编译命令参数、编译入口文件、gcc和gdb可执行文件路径等参数。注:这两个文件可以在vscode中使用模板来一键生成十分方便,具体做法参考vscdoe官方文档。

至此所有的配置工作都已经结束,打开程序入口文件即可执行项目代码。

二、模块化设计

什么是模块化设计?模块化(modular)编程,是强调将计算机程序的功能分离成独立的、可相互改变的“模块”(module)的软件设计技术,它使得每个模块都包含着执行预期功能的一个唯一方面(aspect)所必需的所有东西。“模块化编程”现在指称将整个程序的代码分开成各部分的高层分解:结构化编程是采用结构化控制流的低层代码使用,而面向对象编程是对象的“数据”使用,对象是某种数据结构。在面向对象编程中,使用接口作为一种架构上的模式(pattern)来构造模块叫做基于接口编程 。

为什么要用模块化设计?1.便于并行进行开发设计,提高开发效率。2.便于排查错误与排查性能瓶颈。3.有利于提高系统健壮性。4,契合容器云时代的部署思维。

menu项目中的模块化设计可见menu.c和linktable.c两个模块之间,linktable.c作为服务提供方通过linktable.h向外暴露自己的接口,这样服务调用方只用关心服务接口api而无需关心具体实现,同时也避免了linktable.c模块的具体实现泄露,这样的模块化设计在服务的提供调用双方都兼顾了简洁与安全。这种通过中间接口层暴露服务的方式不仅仅在单个项目中使用,在跨进程通信的业务场景中也比较常见,例如RPC调用。

怎样在开发过程中合理的拆分模块?常见的拆分有:按照业务类型拆分模块(水平拆分),按照处理层级拆分模块(垂直拆分),但是实际场景下,应该结合项目热点业务、数据存储方案、分布式架构的具体情况来融合两种拆分,以达到整体较优的效果。

三、可重用接口

什么是接口?接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。接口具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。那什么是可重入接口?可重入接口就是描述精确、耦合度低的接口。描述精确表示接口要包含完整的五要素:接口目的、接口前置条件、接口协议规范、接口后置条件、接口质量属性;耦合度低表示这个接口能可扩展地支持多场景、多客户的服务调用而不用频繁修改,例如大多数面向对象语言提供的泛型特性就可以很好的提供接口解耦的功能。

menu项目中的可重用接口是怎么实现的呢?以linktable.h中的SearchLinkTableNode接口为例。

/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode,void * args);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode));

该接口使用传入callback函数作为调用参数来查询链表,我认为精髓就在此,调用方在查询结束后的后续处理全权委托服务方来代为处理,这样的设计使得调用方不用去纠结返回的结果是什么然后再根据结果处理,让本来的两次通信变成了一次通信,在此之上,调用方还可以定制化的选择自己的回调函数而不用服务方关心如何处理,在这里调用方服务放的交流都是抽象的,服务方只需要接受到什么就执行什么即可,大大的增加了接口的可扩展性。

项目在这个基础上进行了改进,将SearchLinkTableNode接口进一步解耦:

/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode,void * args);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);

通过增加void * args这个抽象参数来进一步扩充了接口的扩展性,首先void *类型就扩展了参数的类型,然后args参数解放了全局变量cmd的耦合度,使得整个接口就能支撑起更多类型的调用,支持更多方式的调用。

四、线程安全

线程安全是一个老生常谈的话题,为什么线程会不安全,本质上来说就是出现了(1)资源共享(2)调度随机(3)缺少同步机制的情况,例如数据库中多事务操作中的脏读、幻读、不可重复读等问题的解决都要依赖于线程安全机制。线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

什么是函数的可重入性?可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。可重入的函数不一定是线程安全的,而不可重入的函数一定不是线程安全的。

现代编程设计语言中,很大一部分都会提供线程同步工具包,例如Java的JUC包,在linux c中,pthread.h库中提供了pthread_mutex_lock pthread_mutex_trylock pthread_mutex_unlock等函数来支持线程同步访问,在menu项目中,linktable模块就实现了很好的线程同步机制,以DeleteLinkTable删除链表方法为例:

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

其中涉及到修改链表结构的写操作都是互斥的,所以加锁保证其运行过程中的线程安全。

五、编译实战

make是一个工具程序(Utility software),经由读取叫做“makefile”的文件,自动化建构软件。大多数情况下,它被用来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。它使用叫做“makefile”的文件来确定一个target文件的依赖关系,然后把生成这个target的相关命令传给shell去执行。

为什么使用make?(1)减少开发时间成本,一键编译整个工程源文件(2)管理项目中多文件之间的依赖关系,依靠文件修改时间来进行联合编译,省去重复编译的时间。

menu项目中的make操作

进入到menu项目下分别键入make、make clean命令可以构建工程文件、联编生成可执行.o文件以及清除所有构建文件,类似脚本一样的流程使得编译调试时不用一个一个文件去辛苦的编译,从而开发效率能够得到提高。

连续键入两次make命令可见make工具不会重复的去编译已经编译过的内容,节省了编译的时间,这个过程是依赖目标文件的修改时间判断的。

整个项目的make都依赖于makefile文件来构建的,它就像一个脚本一样,也有自己的语法,详细语法可以参阅GNU make中文手册

至此本篇报告结束,感谢阅读。

posted @ 2020-11-09 16:13  fwv  阅读(164)  评论(0)    收藏  举报