代码中的软件工程
参考资料见: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
一、C/C++编译调试环境配置
1.安装VS Code
下载地址 https://code.visualstudio.com/Download ,由于之前已经安装过vscode了,再次不再赘述。
2.安装c/c++扩展插件
打开VS Code,点击左侧扩展按钮,在搜索框中搜索C/C++,点击搜索结果中C/C++右下角安装按钮进行安装
3.安装C/C++编译器和调试器
由于VS Code中的C/C++扩展是不包含编译器和调试器的,所以需要安装mingw-w64。
下载地址:https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe 需要挂代理
下载完成之后运行.exe文件进行安装
安装完成之后需要配置环境变量(安装路径的bin目录)
在命令行输入gcc -v得到如下信息表示安装正确。
4.配置VS Code
在VS Code中按快捷键Ctrl+Shift+P调出命令面板,输入C/C++,选择“Edit Configurations(UI)”进入配置。配置如下图所示,修改编译器路径。配置完成后,此时在侧边栏生成了.vscode文件夹,并且里面有一个c_cpp_properties.json的配置文件。
新建test.cpp文件,点击运行和调试
vscode会自动创建launch.json和tasks.json
tasks.json文件内容
launch.json文件内容,其中miDebuggerPath需要根据本机mingw安装路径进行修改
配置完成后,再次执行test.cpp
至此C/C++编译调试环境配置完成!
二、MENU项目分析
1、分析menu项目各个版本迭代过程
lab1:只包含hello.c测试文件和munu.c(伪代码)
lab2:对lab1中的menu.c进行完善,通过接受用户输入参数,进行if-else判断与预定义的指令(help,quit)进行比较,执行对应的代码
lab3.1:将指令抽象成DataNode结构,结构体中包含指令描述,指令对应的方法和下一个DataNode,从而使多个指令形成组成一个链式结构,将if条件分支的比较方式改为对链表的遍历比较。
lab3.2 :将写在main函数中遍历链表查找DataNode的逻辑封装到到FindCmd(tDataNode * head, char * cmd)方法中
lab3.3:将定义在menu.c中的数据结构和对数据结构的操作(如FindCmd)抽取出来放到linklist.c文件中
lab4:此前的lab,menu.c中初始化链表是通过static的方式,lab4将其初始化改为了InitMenuData函数的方式,此外lab4在linktable中增加了包括增删创建等操作链表的方法,在linktable结构体中增加了pthread_mutex_t mutex;以解决多线程下的线程安全问题
lab5.1:引入回调函数SearchCondition,通过SearchLinkTableNode函数进行调用
lab5.2:为SearchCondition添加参数args从而替代使用全局变量cmd,添加了makefile文件完成编译和连接程序。
lab7.1:新增test.c文件作为程序入口,在test.c中对menu的功能模块进行调用。
lab7.2:新增readme.txt文件
2、模块化软件设计
模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。
模块化软件设计的方法如果应用的比较好,最终每一个软件模块都将只有一个单一的功能目标,并相对独立于其他软件模块,使得每一个软件模块都容易理解容易开发。从而整个软件系统也更容易定位软件缺陷bug,而且整个系统的变更和维护也更容易。因此,软件设计中的模块化程度便成为了软件设计有多好的一个重要指标,一般我们使用耦合度(Coupling)和内聚度(Cohesion)来衡量软件模块化的程度。
耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
高内聚,低耦合的好处体现在系统持续发展的过程中,高内聚,低耦合的系统具有更好的重用性,维护性,扩展性,可以更高效的完成系统的维护开发,持续的支持业务的发展,而不会成为业务发展的障碍。
在模块化设计中,命令行菜单项目通常将数据结构和它的操作与菜单业务处理进行分离处理。进行了模块化设计之后我们往往将设计的模块与实现的源代码文件有个映射对应关系,因此我们需要将数据结构和它的操作独立放到单独的源代码文件中,这时就需要设计合适的接口,以便于模块之间互相调用。
MENU项目中的模块化软件设计
在MENU项目中linktable中定义了数据结构和对数据结构的一系列操作,menu中引入linktable并封装了一些菜单操作,最后以test.c作为程序入口,调用menu中的方法实现菜单功能,体现了模块化软件设计的思想。
/********************************************************************/
/* Copyright (C) SSE-USTC, 2012-2013 */
/* */
/* FILE NAME : linktabe.h */
/* PRINCIPAL AUTHOR : Mengning */
/* SUBSYSTEM NAME : LinkTable */
/* MODULE NAME : LinkTable */
/* LANGUAGE : C */
/* TARGET ENVIRONMENT : ANY */
/* DATE OF FIRST RELEASE : 2012/12/30 */
/* DESCRIPTION : interface of Link Table */
/********************************************************************/
/*
* Revision log:
*
* Created by Mengning,2012/12/30
*
*/
#ifndef _LINK_TABLE_H_
#define _LINK_TABLE_H_
#include <pthread.h>
#define SUCCESS 0
#define FAILURE (-1)
/*
* LinkTable Node Type
*/
typedef struct LinkTableNode
{
struct LinkTableNode * pNext;
}tLinkTableNode;
/*
* LinkTable Type
*/
typedef struct LinkTable tLinkTable;
/*
* Create a LinkTable
*/
tLinkTable * CreateLinkTable();
/*
* Delete a LinkTable
*/
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,void * args);
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);
/*
* get LinkTableHead
*/
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
/*
* get next LinkTableNode
*/
tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
#endif /* _LINK_TABLE_H_ */
3.可重用接口
尽管已经做了初步的模块化设计,但是分离出来的数据结构和它的操作还有很多菜单业务上的痕迹,我们要求这一个软件模块只做一件事,也就是功能内聚,那就要让它做好链表数据结构和对链表的操作,不应该涉及菜单业务功能上的东西;同样我们希望这一个软件模块与其他软件模块之间松散耦合,就需要定义简洁、清晰、明确的接口。
接口:接口是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。换句话说,接口具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。
在面向过程的编程中,接口一般定义了数据结构及操作这些数据结构的函数;而在面向对象的编程中,接口是对象对外开放(public)的一组属性和方法的集合。函数或方法具体包括名称、参数和返回值等。
接口规格是软件系统的开发者正确使用一个软件模块需要知道的所有信息,那么这个软件模块的接口规格定义就必须清晰明确地说明正确使用本软件模块的信息。
MENU项目中的可重用接口
在menu项目中体现了可重用接口的思想,其中linktable.c的SearchLinkTableNode函数形参中的Condition参数是一个函数指针,这样可以利用回调函数为其他模块提供一个更加通用的接口,并且这个函数没有用到任何业务逻辑层的数据,只负责遍历链表,关于是否找到指定条件的节点的判断交由Condition所指向的回调函数来负责。当另外一个模块想要调用该接口时,只需要根据自己的需求去编写Condition函数即可,这样的设计,封装了各个模块的实现细节,使得代码的重用性得到了巨大的提升。
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args)
{
if(pLinkTable == NULL || Conditon == NULL)
{
return NULL;
}
tLinkTableNode * pNode = pLinkTable->pHead;
while(pNode != NULL)
{
if(Conditon(pNode,args) == SUCCESS)
{
return pNode;
}
pNode = pNode->pNext;
}
return NULL;
}
int SearchConditon(tLinkTableNode * pLinkTableNode,void * arg)
{
char * cmd = (char*)arg;
tDataNode * pNode = (tDataNode *)pLinkTableNode;
if(strcmp(pNode->cmd, cmd) == 0)
{
return SUCCESS;
}
return FAILURE;
}
4、可重入函数与线程安全
线程(thread):线程是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一般默认一个进程中只包含一个线程。
可重入(reentrant)函数:可以由多于一个任务并发使用,而不必担心数据错误。
不可重入(non-reentrant)函数:不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。
线程安全:线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
menu项目中线程安全
通常线程安全问题是由于多个线程涉及到共享变量的修改所导致的,在menu项目中链表为共享变量,所以在LinkTable中定义了pthread_mutex_t mutex;通过在给操作共享变量的代码段加锁解锁的方式保证了线程安全。
三、总结
通过学习代码中的软件工程这部分内容,意识到了以前在软件开发中很多没有注意到的问题,如没有考虑到线程安全,代码耦合度过高等等,通过分析孟宁老师MENU项目的源码,学习了一个项目从简单到复杂不断优化迭代的过程,其中体现了很多软件工程的设计思想,受益良多。