这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能) |
开发信息
结对成员
姓名 | 学号 |
---|---|
林家汇 | 3118005281 |
于翔 | 3118005296 |
项目地址
GitHub仓库地址:https://github.com/linjiahui020/examination
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 815 | 995 |
· Analysis | · 需求分析 (包括学习新技术) | 100 | 150 |
· Design Spec | · 生成设计文档 | 50 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 25 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 40 | 60 |
· Coding | · 具体编码 | 500 | 600 |
· Code Review | · 代码复审 | 20 | 15 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 80 |
Reporting | 报告 | 120 | 170 |
· Test Report | · 测试报告 | 40 | 50 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 100 |
合计 | 965 | 1185 |
效能分析
程序随机生成10000个运算式,从图中可以看到堆内存的消耗在上升后逐渐趋于稳定了
在 Overview 中可看到:类的利用率算是在较高的利用水平,并且CPU的利用率却并不高
对于内存:内存的消耗在短时间内达到很高的占用
从下面数据中看出类的消耗:由于计算中大量使用了字符串的加减,所以为了减轻虚拟机的负担,我们使用StringBuilder来进行大部分的字符串操作
设计实现过程
大致流程分析
首先,队友林家汇讨论并确定了编程语言。为降低开发以及学习成本并能按时完成开发工作,我们最终决定使用比较熟悉的Java语言进行相关的开发。
其次,在分析需求设计项目时,发现涉及到随机生成并且校对,故需要大量操作字符串,所以使用StringBuilder来进行相应的操作,提升系统的执行分析速度;其次需求中还涉及到了相关的文件处理操作,所以使用到IO流及相关操作,同时为了让用户有更好的交互性以及用户体验,又需使用到 Java 提供的 Swing 界面。
最后,由于需要分析效能,故需要使用到相应的性能分析神器 ——JProfiler!
程序类分析
- CreateUtil类:创建运算相关操作
- create方法:随机生成式子
- createNum方法:随机生成操作数
- createSign方法:随机生成符号
- formulaNum方法:设定随机生成一定数目的式子
- numRange方法:判断操作数是否超过最大值
- HandleUtil类:计算过程的相关方法
- createNum方法:将答案按规范生成出来
- gcd方法:求两数的最大公因数
- charFind方法:存储指定字符的位序式子
- changeNum方法:将数字字符串转为数字值
- judge方法:判断式子是否符合规范
- change方法:将字符串的操作数分子分母转成数字
- CalculateUtil类:计算方法
- add方法:加法运算
- minus方法:减法运算
- multiply方法:乘法运算
- divide方法:除法运算
- calculate方法:对运算符号左右的两个数进行运算
- calculateFormula方法:计算式子
- CheckUtil类:校验式子(查重)
- spiltStringBuilderToArray方法:将式子拆分普通数组
- spiltStringBuilderToList方法:将式子拆分成List数组
- spiltStringBuilderToOrderList方法:将式子拆分成有序的 List 数组
- judgeRepeat方法:判断内容是否有重复
- IODao类:
- storageResult方法:将结果存储到文件中
- storageFile方法:存储过程式子和答案
- readFile方法:读取文件
- OperatorVar枚举类:对运算符相关信息进行封装,避免多次的 new 减低效能
- MainView类:用户交互的主页面
- UserView类:用户填写等相关操作的页面
流程图分析
代码说明
计算式子
/**
* 对运算符号左右的两个数进行运算
* @param index 运算符的位序
* @param extraCopy 待计算的式子
* @return
*/
public static StringBuilder calculate(int index,StringBuilder extraCopy) {
char sign = extraCopy.charAt(index);
int beginIndex = 0, endIndex = -1;
int[] datas;
for(int index1=0; ; beginIndex=index1) {
//找到第一个操作数的开头空格
index1 = extraCopy.indexOf(" ", index1+1);
if(index1==(index-1)) {
break;
}
}
datas = HandleUtil.change(extraCopy, beginIndex);
int numerator1 = datas[1];
int denominator1 = datas[2];
datas = HandleUtil.change(extraCopy, index+1);
int numerator2 = datas[1];
int denominator2 = datas[2];
endIndex = datas[0];
//删除数字部分
extraCopy.delete(beginIndex+1,endIndex);
//根据符号进行相应的运算
switch(sign){
case '+':
extraCopy.insert(beginIndex+1, add(numerator1,denominator1,numerator2,denominator2));
break;
case '-':
if(!HandleUtil.judge(numerator1, denominator1, numerator2, denominator2)) {
//识别答案是否为负数
extraCopy.insert(0, "@ ");
break;
} else{
extraCopy.insert(beginIndex+1, minus(numerator1,denominator1,numerator2,denominator2));
break;
}
case '*':
extraCopy.insert(beginIndex+1, multiply(numerator1,denominator1,numerator2,denominator2));
break;
case '÷':
if(numerator2 == 0) {
//识别答案是否为负数,是的话在开头插入@作为标识
extraCopy.insert(0, "@ ");
break;
} else{
extraCopy.insert(beginIndex+1, divide(numerator1,denominator1,numerator2,denominator2));
break;
}
default: break;
}
return extraCopy;
}
/**
* 按优先级进行运算(* / + -)
* @param extraCopy copy副本
* @return 返回
*/
public static StringBuilder calculateFormula(StringBuilder extraCopy) {
// logger.info(extraCopy.toString());
//记录符号的位序
int index = -1;
//计算式子
Pattern pattern1 = Pattern.compile("[*]|[÷]");
Matcher m1;
while((m1 = pattern1.matcher(extraCopy)).find()) {
index = m1.start();
calculate(index, extraCopy);
if(extraCopy.charAt(0)=='@') {
break;
}
}
//如果式子正确,在进行加运算(从左到右)
if(extraCopy.charAt(0)!='@') {
Pattern pattern2 = Pattern.compile("[-]|[+]");
Matcher m2;
while((m2 = pattern2.matcher(extraCopy)).find()) {
index = m2.start();
calculate(index, extraCopy);
if(extraCopy.charAt(0)=='@') {
break;
}
}
}
//如果运算结束后(式子正确),调整答案格式
if(extraCopy.charAt(0)!='@') {
int datas[];
datas = HandleUtil.change(extraCopy, 0);
//分子
int numerator = datas[1];
//分母
int denominator = datas[2];
//将原存储内容清空
extraCopy.setLength(0);
//将答案换成标准格式
extraCopy.append(HandleUtil.creatNum(numerator, denominator));
}
return extraCopy;
}
规范化答案显示结果
/**
* 将答案按规范生成出来
* @param numerator 分子
* @param denominator 分母
* @return 式子
*/
public static StringBuilder creatNum(int numerator, int denominator) {
StringBuilder num = new StringBuilder();
int gcdNum = gcd(numerator, denominator);
numerator /= gcdNum;
denominator /= gcdNum;
if (numerator >= denominator) {
//分子大于等于分母
if (numerator % denominator == 0) {
//结果为整数
num.append(numerator / denominator + "");
} else {
//结果为带分数
int interger = numerator / denominator;
numerator = numerator - (interger * denominator);
num.append(interger + "'" + numerator + "/" + denominator + "");
}
} else {
//分子小于分母
if (numerator == 0) {
//分子等于0
num.append(0 + "");
} else {
//其他情况
num.append(numerator + "/" + denominator + "");
}
}
return num;
}
/**
* 求两数的最大公因数
* @param num01 数字1
* @param num02 数字2
* @return 返回公因数
*/
public static int gcd(int num01, int num02) {
int num;
while (num02 != 0) {
num = num01 % num02;
num01 = num02;
num02 = num;
}
return num01;
}
测试运行
程序启动及介绍
打开命令行,输入java -jar examination-system.jar
执行程序:
程序界面:
说明:
进入到程序说明中,可查看程序使用规则指引:
如果不输入程序设置(为空)或者填写内容不符合规则,会智能提示用户:
程序执行流程
输入正确参数并执行题目生成后,提示是否导入题目,选择”确认“:
与此同时在命令行中显示了生成所需要的时间(ms):
并且打印了生成的题目和答案,清晰直观:
设置存放路径:
在本程序中,为了方便用户以及更好地使用体验,尽管不填写绝对路径,也会自动在相对路径(当前命令行执行路径)下自动生成Answers.txt
文件(存放答案)以及Exercises.txt
文件(存放题目)
打开Exercises.txt
文件,可以看到生成了10000条不重复的四则运算题目,且题目均符合用户输入的规则范围:
打开Answers.txt
文件,可以看到生成了10000条不重复的四则运算题目对应的答案:
读取并做题功能
生成题目后,程序跳到做题页面:
查错
写完题目的查错功能:
没想到全错了。。
与此同时,当前文件夹中生成检测结果:
快速生成题目
做完题目后,可快速以用户之前输入的相同的配置来再次生成题目:
新生成题目如图:
项目小结
开发遇到的问题:
- 由于前期没有和队友很好的规划好架构(没有花上太多时间去规划),同时也缺少了一些必要的交流,导致后期项目的开发受阻,难度加大。
- 在开发过程中,没有对项目有较好和清晰的分包,所以造成了业务层没有完全抽离出来,代码和功能的组织结构性做的不够好。
- 因为没有比较好的前期规划,所以在后期的开发测试的调试中,遇到BUG并着手解决 BUG 时会比较困难。
结对感受
- 于翔:和林家汇大佬结对项目实在是太舒服了,因为他是主攻Java语言的,对Java语言以及开发项目有了很多自己的经验和见解,所以功能开发时遇到了很多问题都会找他讨论和解决,同时也能帮我解决很多想要实现的效果,两个人做项目比一个人做轻松太多了,果然是1+1>2。
- 林家汇:于翔在用户体验以及交互方面的各种想法总是让我折服,他对于项目的开发提出了很多宝贵的想法,实在是加快了我们开发项目的速度。虽然我们开发时遇到了不少困难,但都被我们克服解决了,作为我们的第一次结对项目合作,能做成这样子的配合真的非常难得了,很期待和他的下一次合作。
闪光点
- 林家汇闪光点:对Java很熟练,擅长对代码的执行时间和内存消耗进行优化,可以很快规划出大致项目代码结构。
- 于翔闪光点:擅长对页面交互、程序流程及用户体验的优化,学习能力强。会发现出很多代码bug