集大软件工程15级结对编程week1
集大软件工程15级结对编程week1
0. 团队成员
| 姓名 | 学号 | 博客园首页 | 码云主页 |
|---|---|---|---|
| 孙志威 | 20152112307 | Agt | Eurekaaa |
| 孙慧君 | 201521123098 | 野原泽君 | 野原泽君 |
1. 需求分析:针对现有代码的改进分析,新开发功能的分析。
1. 题目需求:
- 原题要求:
- 写一个能自动生成小学四则运算题目的命令行 “软件”:
- 除了整数以外,还要支持真分数的四则运算,真分数的运算,例如:1/6 + 1/8 = 7/24;
- 运算符为 +, −, ×, ÷;
- 并且要求能处理用户的输入,并判断对错,打分统计正确率
- 要求能处理用户输入的真分数, 如 1/2, 5/12 等;
- 使用 -n 参数控制生成题目的个数,例如执行下面命令将生成10个题目 :Myapp.exe -n 10。
- 把这个程序做成GUI:
- 记录用户的对错总数,程序退出再启动的时候,能把以前的对错数量保存并在此基础上增量计算;
- 有计时功能,能显示用户开始答题后的消耗时间;
- 界面支持中文简体/中文繁体/英语,用户可以选择一种。
- 两个任务:
- 把计算模块提取出来,单独创建一个类。
- 针对提取出来的计算类的接口函数做单元测试。
- 写一个能自动生成小学四则运算题目的命令行 “软件”:
- 功能改进与扩展:
- 增加括号操作符;
- 减少重复题目;
- 增加一个运算符(乘方):用符号 ^ 表示乘方,例如:4 ^ 2=16;
- 回归测试:在开发新功能时避免损坏旧的功能,以确保新的功能不与原有功能冲突,在确认修改的功能正确之后再嵌入代码;
- 效能分析。
2. 代码分析
- 获取代码
- 引用老师提供的代码为: 码云地址
- Clone该项目

- 分析结构
类之间关系
比较码云的链接中给出的代码只有两个类:
TrivialCalculator 和 TrivialCalculatorTest类图

逻辑分析:该项目一共只有4个函数,分别为
- gcd 求出最大公约数
- cal 实现两个数字的加减乘除计算
- zfcal 实现两个分数的加减乘除
- calinput 处理输入的字符串
基本上都是逻辑清晰的分支结构,所以没有“逻辑泥球”
测试用例


测试用例完整覆盖了所有函数的各个分支
分析现有代码需要改进的地方:
- 没有实现真分数和整数的同时存在:使用随机实现真分数和整数的可能同时出现。
- 没有实现表达式中数字个数的随机:利用宏定义统一设置表达式数字个数的最多个数,从2到设置的最多位数随机取一个数,决定表达式的数字长度。
【表达式的生成利用循环“随机符号+随机数字”,最终去除第一位符号来得到】 - 码云里的代码不能运行;
- GUI界面有待改善;
- 代码可读性不强,缺少相应的注释。
- 新开功能的分析:
- 添加括号:
- 表达式中数字个数大于2时,可随机添加括号;
- 左括号的添加位置可以为表达式前,或各个运算符的后面(除了最后一个运算符),右括号的添加位置可以为表达式的最后,或与左括号相隔一个符号开始到表达式最后的每个运算符的左边。
- 减少重复题目:
- 将表达式分解为多个“符号+数字”组合的数组,第一位数字前符号为“+”;
- 对组合数组进行排序,并放入二叉树,左子节点为该父节点组合在数组中的下一位,右子节点为在数组中拥有相同上几位组合的同位的不同组合;
- 每生成一个表达式就v在该二叉树内检索插入,已存在则剔除重新生成。(直说好难理解,下面会用图解释)
- 添加乘方:
- 为了减小使用者和计算机内部的计算量,将最多只添加一个二次幂或三次幂;
- 遍历表达式字符串,得到运算符的位置,并在这些位置和表达式的末尾中随机抽取一个位置,在其右边添加"^2"或 "^3"。
2. 程序设计:针对新开发功能做设计,建议使用思维导图。
程序总体设计

所使用的二叉树算法介绍


程序主要部分介绍(C++,C++/QT,Python,C#)
- 生成表达式(C++):采用随机产生字符拼接成字符串的方式生成
- 加工表达式(C++)
- 对生成的表达式进行合法性检查
- 在合理位置添加括号、 幂运算等操作
- 将表达式分割为以“符号+数字”的更小元素
- 将表达式以“元素”为单元进行统一排序
- 避免表达式广义重复(C++)
- 构建了一个二叉树结构用来解析排序后的表达式是否重复
- 二叉树将添加并维护存在过的所有表达式
- 解析表达式(python)
- 使用C++调用python接口
- 使用python的eval函数动态解析表达式
所以就不用正常的中缀-后缀 + 栈解析的做法了(py大法好)
- 前端显示(C++/QT)
- 用QT简单的做了个显示表达式、能检测输入框答案是否正确的GroupBox
- 然后运行时在MainWindows里new几个Box就酱
- 单元测试部分(C#)
- 单元测试用的是MS测试框架
- 测试项目不需要与正常代码放在同一项目下,‘引用’功能和测试资源管理器都十分好用
各新增功能演示
支持括号

表达式不重复
表达式重复的几率为0,具体算法见二叉树算法处

乘方运算

测试分析结果
回归测试:
测试方法大纲

部分测试用例

代码覆盖率测试

效能分析

消耗最大模块
- 发现占用绝大部分时间的都不是用户的函数
基本都是系统调用 - 但是在用户代码中有一个 Evaluator::initPython方法被调用了124次,实际上这里是可以优化的
- 优化建议:将Evaluator的一些需要全局经常调用的方法设置为static的类方法,一旦init后就不需要再调用了,可以大大减少该函数的调用次数
- 发现占用绝大部分时间的都不是用户的函数
3. 代码展示:展示每个功能的核心代码。
字符串生成类
class ExpGenerator
{
public:
ExpGenerator();
~ExpGenerator();
int getGcd(int x, int y);
string generateRawExp();
vector<int> findOperator(string exp);
string addBracket(string exp);
string addPower(string exp);
vector<string> apartExp(string exp);
vector<string> sortExp(vector<string> items);
vector<string> generate(int amount,
vector<double>&results);
};
- 生成字符串
string ExpGenerator::generateRawExp()
{
//产生初始表达式
string exp = "";
char ch[5] = { '+','-','*','~','^' };
int maxNum; if (NUMBER < 2)
{
//表达式位数小于2则报错
exp = "ERROR!";
return exp;
}
else if (NUMBER == 2)
maxNum = 2;
else
maxNum = rand() % NUMBER + 2; for (int i = 0; i < maxNum; i++)
{
//以(符号+数字)*maxNum形成第一位为符号的“伪表达式” //随机生成符号
char c = ch[rand() % 4];
exp.push_back(c); //随机生成数字
stringstream ss;
string s;
int flag = rand() % 10;
if (flag != 0)
//生成整数(90%)
ss << rand() % MAX + 1;
else
{
//生成分数(10%)
int mumNum = rand() % MAX + 2;
int sonNum = rand() % (mumNum - 1) + 1;
sonNum /= getGcd(mumNum, sonNum);
mumNum /= getGcd(mumNum, sonNum);
ss << sonNum << "/" << mumNum;
}
ss >> s;
exp += s;
} //截去第一位符号,生成初始表达式
exp = exp.substr(1, exp.length());
////cout << exp << endl;
return exp;
}
- 拆分字符串并排序
vector<string>ExpGenerator::apartExp(string exp)
{
//遍历初始表达式,以“符号+数字”的结构分割返回
vector<string> items;
int begin = 0;
int end;
unsigned int i;
string str;
exp = "+" + exp;
char c;
for (i = 0; i < exp.length(); i++)
{
c = exp.at(i);
if ((c == '+' || c == '-' || c == '*' || c == '~')
&& (i != 0))
{
end = i;
str = exp.substr(begin, end - begin);
items.push_back(str);
begin = i;
}
}
str = exp.substr(begin, exp.length() - begin);
items.push_back(str); return items;
} vector<string> ExpGenerator::sortExp(vector<string> items)
{
//将分割后的表达式项进行排序,用于二叉树的形成
sort(items.begin(), items.end());
return items;
}
- 生成字符串
二叉树相关
class UniqueBinaryTree
{
public:
UniqueBinaryTree();
~UniqueBinaryTree(); // check if the tree contains the expression
// if contains return true
// else return false
// and add the unique new nodes to the tree
bool add(vector<string>expressionVec, double answer);
// clear the tree but not destoryed it
void clear();
// get amount of the nodes
int nodeAmount();
// get amount of all the resultsNodes
int resultsAmount();
// amount of both result node and the mormal node
int amount();
// Debug: show contents to the console
void showLayer();
private:
// add the result node to the last node (levelNode)
// return true if new result added
// which represent that the new expression
// has the same expression(without brackets) but
// different answers
bool mountAnswerToNode(PNode levelNode, double answer);
int countAnswerNode(PNode levelNode);
void clearAnswerNode(PNode levelNode); Node * head = NULL;
};
- add函数
bool UniqueBinaryTree::add(vector<string> expressionVec , double answer)
{
// treat empty string as true/contains and ignore it
if (expressionVec.empty())
return true; int length = expressionVec.size();
PNode curNode = this->head->right; // compare each node from the first
PNode fatherNode = this->head;
string currentStr; int index = 0; // the current index of stringVector
bool isContained = false;
while (curNode != NULL)
{ currentStr = expressionVec.at(index);
// record the father node to support the insertion later
fatherNode = curNode;
if (curNode->data == currentStr)
{
curNode = curNode->left;
// jump to the next string
index++;
// break when the whole expression finish
if (index >= length)
break;
}
else
{
curNode = curNode->right;
}
}
//if not then it's an new expression
// mount it to the tree
// begin from the last part they're same
// the current node is the last node matches part of the new expression
bool first = true;
// if it reached the end of the expression in the last loop
// will skip it
while (index < length)
{
string curStr = expressionVec.at(index); PNode newNode = new Node();
if (first)
{
// mount the first newNode as the rightChild
fatherNode->right = newNode;
first = false;
}
else
{
fatherNode->left = newNode;
} newNode->data = curStr;
newNode->left = NULL;
newNode->right = NULL;
newNode->answer = NULL;
fatherNode = newNode;
index++;
}
// now the new expression has been added to the tree // add the answer of the expression as well
// and check if the answer is inside the tree
// if it's inside it return false as well
PNode lastNode = fatherNode;
bool newAnswerAdded = this->mountAnswerToNode(lastNode,answer);
// new answer node is unique , and been added to the tree
return !newAnswerAdded;
}
- add函数
具体代码见码云Agt-TrivialCalculator
4. 程序运行:程序运行及每个功能的使用截图。
- 以下图片分别演示了
- 随机生成字符串
- 表达式的加减乘除
- 表达式的括号运算符以及幂运算
- 用户的输入和结果正确反馈
- 表达式的不重复
- 表达式的正确解析
- etc...

5. 结对编程记录

码云记录

编码规范文档

PSP表格
| PSP2.1 | 个人开发流程 | 预估耗费时间(分钟) | 实际耗费时间(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 30 |
| Estimate | 明确需求和其他相关因素,估计每个阶段的时间成本 | 10 | 30 |
| Development | 开发 | 413 | 940 |
| Analysis | 需求分析 (包括学习新技术) | 20 | 60 |
| Design Spec | 生成设计文档 | 60 | 60 |
| Design Review | 设计复审 | 60 | 30 |
| Coding Standard | 代码规范 | 3 | 10 |
| Design | 具体设计 | 30 | 60 |
| Coding | 具体编码 | 120 | 400 |
| Code Review | 代码复审 | 60 | 200 |
| Test | 测试(自我测试,修改代码,提交修改 | 60 | 120 |
| Reporting | 报告 | 60 | 120 |
| · | 测试报告 | 10 | 20 |
| · | 计算工作量 | 30 | 30 |
| · | 并提出过程改进计划 | 60 | 60 |
6. 小结感受:结对编程真的能够带来1+1>2的效果吗?通过这次结对编程,请谈谈你的感受和体会。
我认为1+2是大于2的,结对编程还是有很多收获的。
例如在考虑问题的解决方案的时候,一个人自己思考的时候很容易被自己过去的经验所影响,容易被自己的思维定势束缚住。而在结对编程的时候,由于一边写一边审,互相之间会交流为什么用这种算法,算法有什么优缺点等,能更好地找到更佳的解决方案,所以结对编程也还是有好处的。
队友的感想见博客野原泽君
集大软件工程15级结对编程week1的更多相关文章
- 集大软件工程15级个人作业Week1
集大软件工程15级个人作业Week1 孙志威 201521123077 博客园主页 码云地址 阅读参考材料,并回答下面几个问题 (1)回想一下你初入大学时对网络工程专业的畅想 当初你是如何做出选择网络 ...
- 集大软件工程15级个人作业Week2
集大软件工程15级个人作业Week2 快速通读教材<构建之法>,并参照提问模板,提出5个问题. 在每个问题后面,请说明哪一章节的什么内容引起了你的提问,提供一些上下文 列出一些事例或资料, ...
- 现代软件工程HW2:结对编程-生成五则运算式-Core10组 [PB16110698+PB16120162]
作业具体要求点 这里 Core组要求: 1.Calc() 这个Calc 函数接受字符串的输入(字符串里就是算术表达式,例如 “5*3.5”,“7/8 - 3/8 ”,“3 + 90 * 0.3”等等) ...
- 11061160_11061151_Pair Project: Elevator Scheduler软件工程结对编程作业总结
软件工程结对编程作业总结 11061160 顾泽鹏 11061151 庞梦劼 一.关于结对编程 这次的软工任务既不是单打独斗的个人任务,也不是集思广益的团队项目,而是人数为两人的结对编程.两个人合 ...
- [2019BUAA软件工程]结对编程感想
结对编程感想 写在前面 本博客为笔者在完成软件工程结对编程任务后对于编程过程.最终得分的一些感想与经验分享.此外笔者还对于本课程的结对编程部分提出了一些建议. Tips Link 作业要求博客 2 ...
- 关于软件工程结对编程作业 PairProject : Elevator Scheduler(电梯调度算法的实现与测试)的总结
1)结对编程队友 1106xxxx 张扬 1106xxxx 杨军 其中,此项目的编程实现主要由前者完成. 2)关于结对编程 结对编程的优点: 最直接的一点:在结对编程中,由于有另一个人在你身边和你配合 ...
- 2017-2018-2 165X 『Java程序设计』课程 结对编程练习_四则运算
2017-2018-2 165X 『Java程序设计』课程 结对编程练习_四则运算 经过第一阶段的学习,同学们已经熟悉了这门语言基本的用法.在一次又一次对着电脑编写并提交代码,进行练习的时候,有没有觉 ...
- 软件工程启程篇章:结对编程和进阶四则运算(197 & 199)
0x01 :序言:无关的事 I wrote a sign called "Dead End" in front of myself, but love crossed it wit ...
- BUAA软件工程_结对编程
1.写在前面 项目 内容 所属课程 2020春季计算机学院软件工程(罗杰 任健) (北航) 作业要求 结对项目作业 课程目标 培养软件开发能力 本作业对实现目标的具体作用 培养结对编程开发项目的能力 ...
随机推荐
- 关于svn上传.classpath等问题
1. svn版本:客户端版本与服务器版本 要尽量适配. 2. svn管理项目:有人将.classpath, .project, .mymetadata, .myumldata等文件也纳入到版本控制,如 ...
- oracle USING 用法
提问 using(xx)中可以接多个列吗? using(xx)中的列可以接表名或别名吗? 在使用using的语句中,select * 可以使用吗? 如果表有别名t,select t.* from ta ...
- Linux 小知识翻译 - 「如何成为 Linux 内核开发者」
新年的开始,聊聊「怎么做才能成为Linux内核开发者」. Linux内核的开发都是由志愿开发者们完成的.他们并不属于某些特定的企业. 因此,你也有参加Linux内核开发的资格.不用说,卓越的编码技术以 ...
- Java strictfp
strictfp关键字 用于强制Java中的浮点计算(float或double)的精度符合IEEE 754标准. 不使用strictfp:浮点精度取决于目标平台的硬件,即CPU的浮点处理能力. 使用s ...
- 阿里云短信服务调用例子-Python
阿里云短信服务调用例子 阿里云官方文档https://helpcdn.aliyun.com/document_detail/101893.html 首先需要安装阿里云PythonSDK(下面是pyth ...
- Java多线程(三)如何创建线程
点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...
- 推酷文章中编写js组件系列文章整理
一步一步实现JS拖拽插件 http://www.tuicool.com/articles/RBbmMjY JS组件系列——基于Bootstrap Ace模板的菜单和Tab页效果分享 http://ww ...
- X的平方根的golang实现
实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去. 输入: 输出: 输入: 输出: 说明: 的 ...
- 【技术与商业案例解读笔记】095:Google大数据三驾马车笔记
1.谷歌三驾马车地位 [关键词]开启时代,指明方向 聊起大数据,我们通常言必称谷歌,谷歌有“三驾马车”:谷歌文件系统(GFS).MapReduce和BigTable.谷歌的“三驾马车”开启了大数据时 ...
- vue中父组件调用子组件函数
用法: 子组件上定义ref="refName", 父组件的方法中用 this.$refs.refName.method 去调用子组件方法 详解: 父组件里面调用子组件的函数,父组 ...