首页 > 技术文章 > BUAA软件工程_结对编程

lzh-blod 原文

1.写在前面

项目 内容
所属课程 2020春季计算机学院软件工程(罗杰 任健) (北航)
作业要求 结对项目作业
课程目标 培养软件开发能力
本作业对实现目标的具体作用 培养结对编程开发项目的能力
教学班级 006
github项目地址 IntersectDualProj
结对伙伴博客 17373124

2.PSP表格记录

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 10 10
Development 开发
· Analysis · 需求分析 (包括学习新技术) 40(需求理解)+200(学习技术) 60+300
· Design Spec · 生成设计文档 20 40
· Design Review · 设计复审 (和同事审核设计文档) 30 30
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 30
· Design · 具体设计 150 200
· Coding · 具体编码 300 500
· Code Review · 代码复审 60 80
· Test · 测试(自我测试,修改代码,提交修改) 240 600
Reporting 报告
· Test Report · 测试报告 30 30
· Size Measurement · 计算工作量 15 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 60
合计 1135 1950

3.接口设计学习

  • 信息隐藏(Information Hiding)原则

    In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change)

    信息隐藏是指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。因此,我们设计的计算类中所有属性都是private,所有访问都是通过访问函数实现的。所有容器访问改为迭代器访问。

  • 接口设计(Interface Design)

    参考了一些相关的博客,详见接口设计六大原则

    单一责任原则(Single Responsibility Principle, 简称SRP)

    There should never be more than one reason for a class to change

    考虑到这一原则,我们封装了图形对象的相关类、计算交点的类,异常处理的类等,将各个功能分离。

    Calculator类中,我们将计算分为了多个层次,分写了多个成员函数。分别对点在线上、点在圆内、平行判断、交点计算(线与线、线与圆、圆与圆、汇总计算)进行各模块的编写

  • 松耦合(Loose Coupling)

    In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of,little or no knowledge of the definitions of other separate components.

    我们的接口设计遵从高内聚,低耦合的设计思路。在附加题部分,我与我的队友对于接口的设计讨论了很久。我们这次的计算核心模块和GUI以及命令行的对接,增加了中间转换层,来实现系统的核心模块和用户交互层的彻底解耦。下面的代码就是我们核心模块面向GUI和cmd的中间层设计。内部计算实现彻底隐藏,只注明外部调用接口规范,这个内容会在GUI设计部分详细说明。

    // IOinterface.h
    IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points, std::string msg);//计算模块与GUI的接口
    IMPORT_DLL void cmdProcess(int argc, char* argv[]);//计算模块与命令行的接口
    

4.计算模块接口的设计与实现过程

我们这次的项目是在上一次的个人项目框架上进行扩展的。

在其基础上,我们做了如下改进:

将原有的Line和Circle结构体(原来的Line和Circle只存储数据,故采用结构体)封装为类。为Line类添加type属性,分别可表示直线,射线,线段。由于这三种线的交点计算步骤较为统一,故在较少改动框架的基础上,我们将这三种线融为一个类型。除此之外,运用叉乘计算,实现了pOnLine(Point p, Line l)方法来判断点是否在线上。

具体设计如下:

  • 一共六个类

    classP

  • 类与类之间的关系

    graph LR A[Point] --> B[Line] A --> C[Circle] A --> D[Calcalator] B --> D C --> D D --> E[IOinterface] D --> F[Exception] E --> F E --> G[GUI调用] E --> H[cmd调用]
  • 关键思路依然是几何对象两两求解交点,其中通过一些预判剪枝优化。由于交点计算的关键函数实现都在上一次作业博客中说明了,这次不做特别阐述,着重说明新增功能的拓展。

  • Calculator 类

    • 这是由于新增射线和线段需要做比较多改进的地方

    • 其中Line与Line的交点计算的预判(用到叉乘的方法) 参见 博客,无法预判的内容比如射线,则先计算交点,再通过pOnLine(Point p, Line l)方法来判断点是否在线上。

    • 直线和射线与圆的问题处理,计划先算出交点,再判断点是否在line上

    • 线段与圆关系进行预判,比如线段两点都在圆内时必不存在交点。一定程度的降低时间复杂度

    • 圆与圆的交点无需改动

      class Calculator {
      public:
      	Calculator();
      	inline double xmult(Point v1, Point v2);
      	double xmult(Point o, Point a, Point b);
      	//判断点是否在line上 在line上则return true
      	bool pOnLine(Point p, Line l);
      	// 圆内 return true;  相切/相离  return false;
      	bool pInCircle(Point p, Circle c);
      	bool isParallel(Line l1, Line l2);//判断两线是否平行 (并捕捉 重叠的异常)	 
      	int haveIntersection(Line l1, Line l2, set<Point>& nodeSet);
      	int haveIntersection(Circle c, Line l, set<Point>& nodeSet);
      	int haveIntersection(Circle c1, Circle c2, set<Point>& nodeSet);
      	//计算全部交点
      	int countAllinsect(vector<Line> lVec, vector<Circle> cVec, set<Point> &nodeSet);
      };
        
      
      
  • Line类

    新增射线和线段的功能

    // 'L' -> line;
    // 'R' -> radio;
    // 'S' -> segment;
    class Line
    {
    public:
    	Line();
    	Line(char newType, double x1, double y1, double x2, double y2);
    	char getType();
    	double getA();
    	double getB();
    	double getC();
      double getSlope();
    	Point  getP1();
    	Point  getP2();
    
    private:
    	char type;
    	Point p1;
    	Point p2;
    	double A;
    	double B;
    	double C;
      double slope;
    };
    

5.画出 UML 图显示计算模块部分各个实体之间的关系

uml

6.计算模块接口部分的性能改进

  • 在算法((O(n^{2})))大体思路没有改变的情况下,性能的较大提升很难实现。这里记录了几个小修改:
  1. 直线平行判断优化,我们将直线的slope属性存储在直线class中,判断平行时直接调用判断相等,而不用多次计算斜率
  2. 部分小函数宏定义优化,对于double相等以及大小比较的小型但是多次调用的函数,我们将其设定为宏定义,减少函数调用的时间消耗
  3. 以及一些小的修改,根据函数调用的局部性原理,将分支语句中更多调用的语句提前之类
  4. 学习了助教发的sweep line提示文档,对比其中内容和自行编写的代码,由于sweep line似乎只能优化线段的内容,作业时间有限,也就没有做这个部分的优化。
  • 最终性能分析图片如下:

    占用性能最多的函数还是求直线交点函数,该函数中交点集合的插入部分是占用CPU最多的数据操作。这部分由于set的红黑树原理决定的,无法改善。

    21334

7. Design by Contract,Code Contract 的内容学习

契约式设计和代码协定,这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。这与我在OO课程中接触到的jml类似。

  • 优点

    • 在项目开发初期,可对全局进行把控
    • 明确接口的功能,消除二义性
    • 有利于分工合作
    • 利于使用者对代码的理解
  • 缺点

    • 效率低,代码量大
    • 项目初期很难预知全部需求与问题,契约会被迫修改
  • 应用

    这个方法是很有利于多人协作开发的。虽然我们没有严格按照规范来执行这个方法,但我们也在结对编程中运用到了其思想。在初期,我和我的队友对设计进行了讨论,并确定了设计文档。我俩在里面详细标注了各个类的分工,以及各个函数的作用、输入参数与返回值。

8.计算模块部分单元测试展示

  • 由于openCppCoverage在VS中下载缓慢,可以选择在marketplace中下载,目前还没有发现openCppCoverage插件可以用于检测单元测试覆盖率的功能,只能用它来检测整体代码覆盖率,得出覆盖率如下

cover

  • 在网上查找,VS的单元测试覆盖率可能需要旗舰版才能完成,所以目前没有得出单元测试的结果,但是我们自己的单元测试编写的有层次有条理,尽可能做到了全面的覆盖。

  • 整体测试框架

    根据设计的几大类,采用bottom-up的方式进行测试程序的编写

    3单元测试
  • 对于每个类的每一个函数都进行了细密的测试

    比如下面展示的对于直线类的测试,细致到每一个函数

    TEST_CLASS(LineTest) {
    public:
    	TEST_METHOD(lineTestBasic) {
    		Line l1('L', 1, 1, 2, 2);
    		Line l2('R', 0, 2, 1, 0);
    		Line l3('S', 1, 0, 5, 0);
    
    		// test getType
    		Assert::IsTrue(l1.getType() == 'L');
    		Assert::IsTrue(l2.getType() == 'R');
    		Assert::IsTrue(l3.getType() == 'S');
    
    		// test get abc
    		Assert::IsTrue((l1.getA() == 1 && l1.getB() == -1) ||
    			(l1.getA() == -1 && l1.getB() == 1)
    			&& l1.getC() == 0);
    		Assert::IsTrue((l2.getA() == -2 && l2.getB() == -1 && l2.getC() == 2) ||
    			(l2.getA() == 2 && l2.getB() == 1 && l2.getC() == -2));
    
    		// test get p1 p2;
    		Point p1(1, 1);
    		Point p2(1, 0);
    		Point p3(5, 0);
    		Assert::IsTrue(l1.getP1() == p1);
    		Assert::IsTrue(l2.getP2() == p2);
    		Assert::IsTrue(l3.getP2() == p3);
    
    	}
    };
    
  • 对于求交点的重要复杂部分,我们的测试也做的更细致

    比如直线相交的测试,我们对于几种直线间的情况比如相交、平行、重叠,以及三种直线的情况(线段、射线、直线)都做了非常细致的测试

    3直线测试
    // test parallel
    		TEST_METHOD(LinePrl)
    		{
    			Calculator* calc = new Calculator();
          // 三种线段
    			char line = 'L';
    			char radio = 'R';
    			char segment = 'S';
    			Line lTD(line, 1, 3, 2, 3);
    			Line rTD(radio, 2, 5, 4, 5);
    			Line sTD(segment, 51, 6, 24, 6);
    			Calculator* cal = new Calculator();
    			Assert::IsTrue(cal->isParallerl(lTD, rTD));
    			Assert::IsTrue(cal->isParallerl(lTD, sTD));
    			Assert::IsTrue(cal->isParallerl(rTD, sTD));
    
    			Line l1(line, 3, 3, 5, 5);
    			Line r1(radio, 6, 5, -100, -101);
    			Line s1(segment, 0, 1, 100, 101);
    			Assert::IsTrue(cal->isParallerl(l1, r1));
    			Assert::IsTrue(cal->isParallerl(l1, s1));
    			Assert::IsTrue(cal->isParallerl(r1, s1));
    			Assert::IsFalse(cal->isParallerl(l1, sTD));
    			Assert::IsFalse(cal->isParallerl(r1, sTD));
    			Assert::IsFalse(cal->isParallerl(s1, rTD));
    		}
    

9.计算模块部分异常处理说明

异常处理设计

  • 几种异常
  1. 命令行输入异常(参数、文件名)
  2. 输入文件异常(输入曲线不符合格式,输入线段数目,“乱码”输入)
  3. 直线异常
  4. 曲线异常

异常处理测试

1.1. 命令行输入——参数异常

intersect.exe -n 
intersect.exe -i input.txt -o output.txt -h

1.2. 命令行输入——文件名异常

intersect.exe -i test.txt -o output.txt
intersect.exe -i input.txt -o out.txt
  • 根据以上测试,得到异常处理测试结果

3expCmd测试

2.1. 输入文件内容——输入曲线不符合格式

## 1. 直线输入错误
R 0 43 9 -3 98

# 2. 输入几何对象参数含有前导0
S 09 12 45 89

# 3.  多个字母
S S 3 2 1 

# 4. 只有数字
3 1 5 2 76

# 5. 字母数字乱序
5 L 1 4 6

# 6. -后不接数字
L - - - -

# 7. 错误数字
L 98-736 92 0 82

2.2. 输入线段数目异常

# 1. 输入线段 < 1
0
-94

# 2. 输入线段与实际线段数不同
1
L 0 10 8 83
R 91 46 2 0

4
L 56 28 82 4
R 19 41 34 56
C 56 168 5 

2.3.曲线输入文件无法打开

3.1. 直线不符合标准

## 1. 输入两点重合
L 0 1 0 1 

## 2. 输入数字超范围
R 100000 0 0 0
L -100000 4897 278 1
S -100005 3784 942 61

3.2.有无穷多交点

#1.  正确情况
3
S 1 1 3 3
S 5 5 100 100
R 0 0 -55 -55

# 2. 异常
2
S 0 1 7 8
R -4 -3 -3 -2

2
S 0 1 7 8
L 9 10 100 101

2
R -4 -5 0 -1
L -99 -100 -50 -51

2 
S 1 0 3 0
S 2 0 4 0 

2
S 1 0 3 0
S 2 0 3 0 

2 
S 1 0 3 0
S 1 0 5 0 

2
S 1 0 3 0
S 0 0 5 0

4.1. 圆不符合标准

## 1. 输入圆半径小于1
C 0 0 0

C 84 72 -23

## 2. 输入数字超范围
C -100000 4897 278
  • 对于以上的样例,分别写了测试样例,得到测试结果如下:

    epctest

10.界面模快的详细设计过程

这次的GUI我们是用Qt实现的。Qt的良好封装机制使得Qt的模块化程度非常高,可重用性较好,对于用户开发来说比较方便。 Qt提供了一种称为signals/slots的安全类型来替代 callback,使得各个元件之间的协同工作变得十分简单。

(1)界面设计

我们先通过集成在 Qt Creator 中的 Qt Designer 对窗体进行可视化设计。

最终界面如下:

pairProjectGUI

为了方便用户操作、减少用户记忆负担、减少用户错误信息,我们的设计做了如下改良:

  • 关文件导入:我们实现的“..."按钮可直接浏览文件夹选择文件,无需手动输入路径。
  • 添加操作:由于几何对象的参数要求为在(-100000,100000)之间的不含前导零的整数,我们设置了SpinBox,其限制了正确的参数形式,避免手动输入带来的参数格式错误问题。对于几何体的类型,我们实现了下拉选框。
  • 删除操作:为了减少用户的记忆负担,我们在listWidget中实时呈现了当前几何图形。为了避免删除操作的错误信息和比较过程的繁琐,我们设置了复选框。用户选择列表中的几何图形,点击“删除几何图形”即可。这样既方便了用户,也减少了我们异常处理的负担。
  • 求解交点:点击“求解交点”按钮,则会计算出所有交点的左边,并在下方显示求解的交点数。
  • 绘制:在用户完成全部对当前几何图形的修改和求解交点后,点击“绘制几何图形和交点”按钮,我们将统一更新画布。这样避免了用户频繁的阶段性操作带来的无用计算。

界面使用注意点:
在点击求解交点后,方有交点的数据,才能绘制出交点。
在点击绘制几何图形和交点后,才会更新当前几何图形的绘制。

(2)主要代码说明

Qt使用了信号和槽来代替回调函数,实现对象间的通信。当一个特定的事件发生时,信号会被发送出去。Qt的窗体部件(widget)拥有众多预先定义好的信号。槽,则是对一个特定的信号进行的反馈。我们这次的实现主要是创建窗体部件(widget)的子类并添加自定义槽,以便对感兴趣的信号进行处理。

我们实现的类中,有两个重要的属性vector figuresvector points,分别存放当前几何对象当前交点

a.文件导入

//点击"..."按钮,浏览文件夹
void IntersectGUI::on_findFileButton_clicked() 
{
	filePath =
		QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Save path"), QDir::currentPath()));  //文件路径
	if (!filePath.isEmpty())
	{
		if (ui.fileBox->findText(filePath) == -1)
			ui.fileBox->addItem(filePath);//在comboBox中显示文件路径
	}
}
//点击"输入文件"按钮,导入文件数据
void IntersectGUI::on_infileButton_clicked()
{
	QFile* file = new QFile;   //申请一个文件指针
	file->setFileName(filePath);   //设置文件路径
	bool ok = file->open(QIODevice::ReadOnly);
	if (ok)
	{
	……//读入文件并将文件中的数据处理后存入figures中	
		}
		file->close();
	}
}

b.求解交点

点击“求解交点”按钮,将当前几何体的数据转换成相应的接口处的数据,调用dll中的函数,计算交点并返回。具体接口设计,下一部分详细介绍。

int IntersectGUI::on_calcPButton_clicked()
{
	points.clear();
	std::string input;
	size_t n = figures.size();
	……//将figures中的几何体数据转换成相应的接口中的数据input
	int cnt = 0;
	//cnt = guiProcess(&points,figures);
	try {
		cnt = guiProcess(&points, input);
	}
	catch (std::exception e) {
		QString dlgTitle = QString::fromLocal8Bit("计算出现错误");
		QMessageBox::critical(this, dlgTitle, e.what());
		return 0;

	} {
	}
	ui.lineEdit->setText(QString::number(cnt));//反馈交点数
	return cnt;
}

c.图形绘制

这一部分,我们重写了paintEvent()方法。点击“绘制几何图形和交点”的按钮时,调用update()函数,重新绘制。

void IntersectGUI::paintEvent(QPaintEvent*)
{
	init_canvas(); //初始化画布 (底色和坐标轴)
	if (figures.size() != 0) {
		for (vector<string>::iterator iter = figures.begin(); iter != figures.end(); ++iter) {
			draw_figures_from_str(*iter);//绘制几何图形
		}
		draw_points();//绘制交点
	}
}

void IntersectGUI::on_drawFigureButton_clicked()
{
	update();
}

//将不同的string数据绘制成不同的几何图形
void IntersectGUI::draw_figures_from_str(string str)
{
	QStringList list = (QString::fromStdString(str)).split(" ");
	QString type = list.at(0);
	……
	if (type == QString::fromLocal8Bit("L")) {
		draw_line(x1, y1, x2, y2);
	}
	else if (type == QString::fromLocal8Bit("S")) {
		draw_seg(x1, y1, x2, y2);
	}
	else if (type == QString::fromLocal8Bit("R")) {
		draw_ray(x1, y1, x2, y2);
	}
	else {
		draw_circle(x1, y1, r);
	}
}

由于Qt中的drawLine()方法,只能绘制两点间的线段。所以在实现绘制直线和射线的时候,我们计算了当前线与画布边界的交点。代码简单,但是很长,在这里就不展示了。

11.界面模块与计算模块的对接。

(1)接口数据格式介绍

计算模块与界面模块的对接,用到了此接口:

int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);

msg存放的是当前几何图形的信息,数据格式与文件中读取的格式相同。如:

4
C 3 3 3
S 2 4 3 2
L -1 4 5 2
R 2 5 -1 2

points存放求解的交点。

(2)GUI导入dll的方式如下:

#pragma comment(lib,"calcInterface.lib")
_declspec(dllexport)  extern "C"  int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);

(3)具体代码实现

GUI相关的代码只在求解交点处调用了dll的guiProcess()方法。

int IntersectGUI::on_calcPButton_clicked()
{
	points.clear();
	std::string input;
	size_t n = figures.size();
    //转换数据
	input += std::to_string(n) + "
";
	for (size_t i = 0; i < n; i++) {
		input += figures.at(i) + "
";
	}
	
	int cnt = 0;
	try {
		cnt = guiProcess(&points, input);
	}
	catch (std::exception e) {
		...
	} {
	}
	ui.lineEdit->setText(QString::number(cnt));//界面反馈交点总数
	return cnt;
}

IntersectGUI类中的vector<std::pair<double, double>> points属性存放求解的交点。在调用guiProcess前,清空当前points中的元素,传入points的引用。将figures中的数据转换为接口对应类型的数据传入。guiProcess()会将求解的交点写入points中,并返回交点数。

guiProcess()代码如下:

int guiProcess(std::vector<std::pair<double, double>>* points, 
	std::string msg) {
	try {
		vector<Line> lVec;
		vector<Circle> cVec;
        //将msg中的几何信息解析并存入lVec和cVec中
		istringstream input(msg);
		fileExcHandler(input, lVec, cVec);
        //计算交点
		set<Point> pointSet = getAllIntersect(lVec, cVec);
        //将交点信息写入points中
		for (auto iter = pointSet.begin(); iter != pointSet.end(); iter++) {
			Point p = (Point)* iter;
			points->push_back(make_pair(p.getX(), p.getY()));
		}
        //返回交点总数
		return (int)pointSet.size();
	} catch (fileException& fileError) {
		cout << fileError.what() << endl;
	}
	catch (lineException& lineError) {
		cout << lineError.what() << endl;
	}
	catch (CircleException& circleError) {
		cout << circleError.what() << endl;
	}
	return -1;
}

(4)对接相关功能实现

未点击“求解交点”按钮时,绘制的几何图形。

1584982017704

点击“求解交点”按钮后,再绘制

GUI3

绘制交点并返回交点数。

12.结对过程

时间表

时间 事项
3.12晚 讨论结对编程总体规划
3.12-3.14 独立进行需求分析、学习和设计的工作,辅以资源交流
3.14下午 讨论代码设计,确定最终设计和实际结对编程的各种方法和规范
3.14晚-3.16上午 进行合作编程,完成step1的功能及其测试
3.18-3.21 改进接口设计,并完成GUI部分和异常处理部分
3.23-3.24 完成博客部分

工具使用

13.结对编程总结

  • 结对编程本身

    • 优点
      • 在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作能有更强的解决问题的能力。
      • 对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感
      • 在心理上, 当有另一个人在你身边和你紧密配合, 做同样一件事情的时候, 你不好意思开小差, 也不好意思糊弄
      • 在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。因为一个人的知识已经被其他人共享
    • 缺点
      • 两人合作需要磨合,并不一定合得来。
      • 开发者之间可能就某一问题发生分歧,产生矛盾,造成不必要的内耗
  • 本人在结对编程中的表现

    • 优点
      • 能积极完成工作
      • 善于寻求帮助。(在这里要特别感谢zwx大佬和助教,被我问了很多问题,帮我解决了不少困惑~谢谢!)
      • 学习能力较强。这次作业让我学会了用Qt写GUI,虽然熬了不少夜,但能学习新知识,还是蛮开心的!
    • 缺点
      • 相比于我的队友,我的计划性就没那么强了。ddl玩家,这次就被我的队友拿着计划表一次次push。
      • 比较执着,不太会让步。这应该是我性格中的缺点了,认定了就很偏执,得改。
      • 深层次的问题不了解,解决问题的能力有待提高。这次开发过程中碰到的跟编译器有关的问题不是很懂,debug花了很长时间。
  • 队友在结对编程中的表现

    • 优点

      • 她比较积极主动,也经常担任项目中的领导工作。在作业发布后不久,我们就联系交流作业了。
      • 计划性强,在开发初期对各个部分有清晰的时间安排,做事有条理。
      • 善于沟通,结识的人广。她在项目初期就联系了合作小组。我们早早沟通商量,减少了很多对接工作的负担。
    • 缺点

      由于疫情原因,我们并不能在编程时实时保持交流,加上我电脑有一天晚上坏了。所以对于接口的设计部分她与合作小组确定了方案,却未能与我及时沟通。这也不是缺点,只是结对过程中的一点小矛盾。里面也有我的错,我太执着己见了。不过好在我们愉快得解决了这个问题,最终达成共识。

14. PSP表回填(见2)

界面模块,测试模块和核心模块的松耦合【附加题】

  • 和我们(A组)进行松耦合对接的是1602108817373439结对编程(B)小组
  • dll导出,即新建dll导出项目,在pch.h 文件中导入头文件,并在每一个*.cpp文件中include "pch.h文件,之后生成dll即可
  • 之后与GUI和cmd程序分别对接,其中有一个需要注意的问题,就是dll导出的编译器需要和导入文件的编译器相同,否则就会出现无法导入的情况,我们两个小组都采用的VS IDE上的release x64版本的编译器,下面展示松耦合实际测试

cmd松耦合

  • B组dll导入A组cmd程序

3cmdA->B

  • A组dll导入B组cmd程序

    BceAcmd

  • B组dll导入A组GUI程序

    AceBGUI

  • A组dll导入B组GUI程序

    BceAGUI

由于我们两个小组在项目初期就确定了合作,所以接口设计是在双方商量好的情况下早早决定了的。对接过程中,碰到的唯一问题是编译版本问题。B组的dll是Release X64编译的,而我们是Debug X64编译的,导致开始对接时总是导入不成功。后来我们统一了编译版本(Release X64),问题也就解决了。
由于我们之前没有将dll同名,现已经改成统一名字。只需互换dll无需再编译即可运行,更改后的GUI版本和dll已经发布到github上了。

推荐阅读