结对编程收获
结对编程收获
沈三景
软件工程
结对编程
前言
本次结对编程,过程异常坎坷,遇到了很多以前没有遇到过的问题,比如:如何写API文档才能让UI组更加容易读懂,如何定义接口,如何封装代码等等。但是结果还算不错,学会了一些在正常上课过程中没法学到的东西。
结对编程
在结对编程中,比起个人作业多出了两个人交流以及分工的部分,两人分别承担驾驶员和领航员的角色的模式,“驾驶员”负责具体的编码工作,“领航员”则负责检查,及时纠正代码中的问题。结对编程的形式使得代码处于不断地审查过程,每一段代码都由一个人编写,另一个人检查,最大程度上减少了出现bug的可能。在本次结对编程作业中,为了更好的体验驾驶员和领航员的角色,我和队友商量了一下后决定由队友写generate(),我写calc()。在写generate()时,队友担任驾驶员,我担任领航员,负责审查;在写calc()时,则相反。
calc()函数的一些想法
刚开始我打算先通过二叉树将中缀表达式转化为后缀表达式,然后用堆栈处理后缀表达式,由于题目要求所有的计算步骤中都不允许出现负数,所以可以通过二叉树的左右子树交换的方式来规避负数的出现(即出现负数就交换)。但是后来看了顾老师的课件(当时在复习二叉树)发现可以将中缀表达式直接通过堆栈的方式来计算(这里要求构造两个栈,一个存操作数,另一个存运算符)当时就感觉我应该好好复习数据结构了。言归正传,下面附上calc()的全部代码(篇幅限制没有给出里面函数的代码):
int calc(vector<string> &vs, vector<string> &vscal)
{
string str;
vector<string> vstr;
stack<string> OPND;
stack<string> OPTR;
for (int q = 0; q < vs.size(); q++)
{
str = vs[q];
splitstr(vstr, str);
OPTR.push("#");
int i = 1;
int k = 0;
int sumsub = 0;
int foundsub = 0;
string op;
string a, b, c;
double doublea, doubleb, doublec;
map<int, int> order1, order2;
for (int j = 0; j < vstr.size(); j++)
{
order1[j] = j;
order2[j] = j;
}
while (1)
{
if (!isoptr(vstr[i]))
{
OPND.push(vstr[i]);
i++;
continue;
}
else
{
while (1)
{
op = OPTR.top();
switch (cmp(op, vstr[i]))
{
case 0:
b = OPND.top();
OPND.pop();
a = OPND.top();
OPND.pop();
OPTR.pop();
stringtodouble(b, doubleb);
stringtodouble(a, doublea);
if (op == "+")
{
doublec = doublea + doubleb;
doubletostring(c, doublec);
OPND.push(c);
}
if (op == "-")
{
sumsub++;
doublec = doublea - doubleb;
if (doublec < 0)
{
doublec = doubleb - doublea;
for (k = 0; k < vstr.size(); k++)
{
if (vstr[k] == "-")
{
foundsub++;
}
if (foundsub == sumsub)
{
break;
}
}
int k1 = k, k2 = k;
k1--;
k2++;
while (vstr[k1] != "+" && vstr[k1] != "-" && vstr[k1] != "#")
{
k1--;
}
while (vstr[k2] != "+" && vstr[k2] != "-" && vstr[k2] != "#")
{
k2++;
}
int s;
for (s = 1; s < k2 - k; s++)
{
order2[k1 + s] = order1[k + s];
}
order2[k1 + s] = k;
int t = k1 + s;
for (s = 1; s < k - k1; s++)
{
order2[t + s] = order1[k1 + s];
}
doubletostring(c, doublec);
OPND.push(c);
}
else
{
doubletostring(c, doublec);
OPND.push(c);
}
}
if (op == "*")
{
doublec = doublea * doubleb;
doubletostring(c, doublec);
OPND.push(c);
}
if (op == "/")
{
doublec = doublea / doubleb;
doubletostring(c, doublec);
OPND.push(c);
}
break;
case 1:
OPTR.push(vstr[i]);
break;
case 2:
OPTR.pop();
break;
default:
break;
}
if (cmp(op, vstr[i]) != 0)
{
break;
}
}
}
if (vstr[i] == "#" && OPTR.top() == "#")
{
break;
}
i++;
}
string ans;
ans = OPND.top();
string anscal = "";
for (int i = 1; i < vstr.size() - 1; i++)
{
// cout << vstr[order2[i]];
anscal += vstr[order2[i]];
}
anscal += "=";
// cout << "=";
anscal += ans;
// cout << ans;
vscal.push_back(anscal);
vstr.clear();
while (!OPND.empty())
{
OPND.pop();
}
while (!OPTR.empty())
{
OPTR.pop();
}
}
return 0;
}
算法的思想如下:
从上面的代码可以看出,我已经养成了一定的代码规范,如:
- {}各占一行
- 缩进,一般以4个空格
- 行宽,不多于100个字符
- 命名有一定的实际意思,诸如判断运算符的函数命名为isoptr()
这也算是学这门课的一个收获吧。
关于封装的一些想法
查了Google,数据封装是一种把数据和操作数据的函数捆绑在一起的机制,是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。对于本次实验来说我们要写很多函数,这有利于代码维护。但是我们只需提供一个接口,UI组不需要关心我们内部的具体结构,不需要实际细节,就能够使用我们的core。我们刚开始打算用dll封装,但是查了一些资料,以及在按照常规套路进行一番操作之后,发现并不能封装成DLL,编译器报错说是找不到文件(文件明明都在啊……),忙了一段时间之后还是不能解决,请教了以及封装好的组,还是解决不了。于是弃疗直接用头文件加上命名空间来与UI组对接,在对接过程中也出现了许多问题,比如:IDE不能识别加进去的头文件,解决办法:在IDE里添加新的空文件,然后将代码复制过去。(至今没有明白这是为啥)
关于接口的一些想法
有部分UI组使用内存操作的,另外的一些组是通过文件的方式操作,所以我们定义了两套接口,具体情况如下:
-
通过文件操作:
参数说明:
numofques:算式总数
numofopera:最多产生的运算符个数
range:操作数范围
accuracy:精度,取值范围为0~4,表示小数点后0~4位,超出范围取0或4
zerodiv:是否支持整除,true表示支持整除,此时精度无效函数说明:
GenerateNobrackets:产生不带括号的算式,结果需要另外计算
Generatebrackets:产生带括号的算式,结果可以直接在产生时计算函数原型:
void GenerateNobrackets(int numofques, int numofopera, int range, int accuracy, bool zerodiv);
void Generatebrackets(int numofques, int numofopera, int range, int accuracy, bool zerodiv);调用函数后产生的算式存在result.txt,算式结果存在key.txt
-
通过内存操作
参数说明:
numofques:算式总数
numofopera:最多产生的运算符个数
range:操作数范围
accuracy:精度,取值范围为0~4,表示小数点后0~4位,超出范围取0或4
zerodiv:是否支持整除,true表示支持整除,此时精度无效函数说明:
GenerateNobrackets:产生不带括号的算式,结果需要另外计算
Generatebrackets:产生带括号的算式,结果可以直接在产生时计算函数原型:
void GenerateNobrackets(vector &vs, vector &vscal,int numofques, int numofopera, int range, int accuracy, bool zerodiv);
void Generatebrackets(vector &vs, vector &vscal,int numofques, int numofopera, int range, int accuracy, bool zerodiv);调用函数后产生的算式存在vs里面,算式结果存在vscal里面
关于Debug的一些想法
Debug是一个老大难的问题,我本人也是十分畏惧Debug,对于一些编译不通过的bug还好说,但是对于一些运行中出现的error就十分的头疼,比如越界,非法访问内存地址。所幸这次是结对编程,队友很carry,有时候很低级的错误自己打死发现不了,但是队友能够一眼看出,这大大的提高Debug的效率,做到了优势互补。
关于对接
刚开始对接很不顺利,因为第一组是用文件进行操作的,而我们是用vector方式传递数据,这就很头疼,最后我觉得其它UI组也可能用文件进行操作,于是就添加了file版本,最终在我给出file版本后,那一组仅仅用了不到20分钟就对接完成(目前最快的一组不到十分钟就完成了,haha)。
关于本次结对编程的想法与收获
- 最大的收获就是一个良好的接口应该在一开始就和 UI组商量好, 例如给UI需要哪些数据,是用什么形式封装,这样封装会产生哪些问题,这些问题该怎么解决诸如此类。一个好的接口可以简化使用,更好的体现功能,也可以将内部的实现隐藏起来,保护程序不被修改,事半功倍。
- 结对编程能够很大的提高效率,特别是队友比较可靠的时候。
- 要多与UI组沟通,这个沟通不仅仅是刚开始时的沟通,而是在编程时,尽可能的多交流,这样才能及时发现对接上的一些问题,真正做到敏捷原则。