是一个标题
211614269 林凯 211601233 张康凌
一、预估与实际
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
• Estimate | • 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | ||
• Analysis | • 需求分析 (包括学习新技术) | 60 | 120 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 30 | 30 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
• Design | • 具体设计 | 60 | 60 |
• Coding | • 具体编码 | 240 | 400 |
• Test | • 测试(自我测试,修改代码,提交修改) | 30 | 30 |
Reporting | 报告 | ||
• Test Repor | • 测试报告 | 30 | 30 |
• Size Measurement | • 计算工作量 | 10 | 20 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 570 | 800 |
二、需求分析
我通过查询教学大纲及研究相关试卷的方式了解到,小学三级数学有如下的几个特点:
- 特点1
- 减法的差应该大于0
- 特点2
- 除数要不等于0
- 特点3
- 除法结果不应该有余数
- 特点4
- 减法的差应该大于0
- 特点5
- 运算符2-4个
- 特点6
- 需要括号
经过分析,我认为,这个程序应当:
- 要保证减法时的差不为负数
- 除法时要保证被除数不为0且计算结果没有余数
- 随机出现合适的括号(由于实在想不到很好的方式实现,出题模块暂时没有此功能,计算的模块支持带括号式子的计算)
三、设计
1. 设计思路
-
这个程序有两个类
- MathExam负责生成题目
- Calculate类专门用来计算表达式的结果
-
算法的关键是什么?
- 调度场算法
- 定义两个栈,一个栈叫做保存运算符记为op,另一个栈保存最终的表达式记为rpn。
- 数字直接入op栈
- op栈顶若是(则无条件入栈
- 运算符要与op栈顶比较,优先级大则入栈,小于或等于则op出栈后再入栈
- 逆波兰式求值
- 定义一个栈
- 从左到右扫描逆波兰式
- 读到一个数字时就将它压入栈中
- 读到一个运算符时,就从栈中弹出两个数字,并将该运算符作用于这两个数字,然后将计算结果再压入栈中
- 逆波兰式读取完毕时,栈中剩下的就是最终结果
- 生成合适的随机数
- (int) (min + Math.random() * (max - min + 1)) 可生成min-max范围内的数;
- 调度场算法
2. 实现方案
- 准备工作:先在Github上创建仓库,克隆到本地
- 技术关键点:梳理一下设计思路,可能遇到哪些技术关键点
- 如何将调度场算法运用到该题中去
- 随机数的使用,本程序需要大量的使用Math.random()方法
- 全局静态变量的使用,本程序需要多个静态函数共享数据
- ArrayList数组的使用,用该数组存储题目及答案的字符串,相比普通数组,该数组可以动态增加空间
四、编码
请说明你如何按照设计思路进行编码,并记录你在开发中遇到的问题,与解决过程
1. 调试日志
- 在代码的什么位置,代码出现了什么问题,问题会导致什么结果,怎么解决的
- 在使用调度场算法将题目转换成逆波兰式时出现了问题,因为题目的数字可能是个位也可能是两位,直接通过下标截取字符串的方法明显不合适。最后我决定写一个方法,将题目中的符号及数值按顺序存放至字符串数组中,使用的时候遍历数组取出即可。
- 在出题时,有可能出现除法有余数的情况,导致题目不符合要求。我的做法是在生成除号时,将前一个被除数取出,同时随机出被除数,若不能整除,则一直循环随机出被除数,最后一定能保证不会有余数。
2. 关键代码
请展示一段程序的关键代码,并解释代码的作用
//返回符号优先级
public static int priority(String str) {
switch (str) {
case "(":
return 0;
case "+":
case "-":
return 1;
case "*":
case "/":
return 2;
default:
return -1;
}
}
// 转换成逆波兰式
public static void reversePolishNotation() {
for (int i = 0; i < slen; i++) {
//scut字符串数组按顺序存放了题目的数值及符号
if (!(scut[i].equals("+") || scut[i].equals("-") || scut[i].equals("*") || scut[i].equals("/") || scut[i].equals("(") || scut[i].equals(")"))) {
srpn.push(scut[i]);
} else {
if (soperators.isEmpty() || scut[i].equals("(")) {
soperators.push(scut[i]);
} else {
if (priority(scut[i]) > priority(soperators.peek())) {
soperators.push(scut[i]);
} else {
if(scut[i].equals(")")) {
while (!soperators.peek().equals("(")) {
srpn.push(soperators.pop());
}
soperators.pop();
}else {
while ((!soperators.isEmpty()) && (priority(soperators.peek()) >= priority(scut[i]))) {
srpn.push(soperators.pop());
}
soperators.push(scut[i]);
}
}
}
}
}
//此时原表达式已扫描完毕,将符号栈里剩余的符号全部存至表达式栈
while (!soperators.isEmpty()) {
srpn.push(soperators.pop());
}
}
- 该段代码将原表达式通过调度场算法转换成了逆波兰式
3. 代码规范
请给出本次实验使用的代码规范:
- 第一条、代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
- 第二条、类名使用UpperCamelCase风格
- 第三条、方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵循驼峰形式。
- 第四条、方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵循驼峰形式。
- 第五条、静态变量(static)前面加上小写s。
4. 结对过程
- 先互相沟通,对于要求进行分析,提出合适的解决思想
- 我先设计出Calculate类专门负责计算传入式子的结果,该类主要用到了调度场算法及逆波兰式的计算
- 张康凌同学则写出了生成三年级题目的基本算法
- 我则继续在出题算法上进一步修改,使其更符合题目的要求
- 实现基本功能后则一起进行相应的测试
五、测试
请思考并记录你认为必要的测试点,并记录测试用例与测试结果
测试项目 | 测试语句 | 预期结果 | 测试结果 |
---|---|---|---|
不输入任何参数 | java MathExam4269 | 输入错误 | 输入错误 |
输入一个参数 | java MathExam4269 20 | 输入错误 | 输入错误 |
输入二个参数 | java MathExam4269 -n 20 | 输入错误 | 输入错误 |
输入4个参数且题数在前 | java MathExam4269 -n 20 -grade 2 | 出20道二年级题目 | 出20道二年级题目 |
输入4个参数且年级在前 | java MathExam4269 -grade 2 -n 20 | 出20道二年级题目 | 出20道二年级题目 |
输入4个参数且题数过多 | java MathExam4269 -grade 2 -n 1000 | 输入错误 | 输入错误 |
六、总结
-
吸取上次个人作业的教训,开始时要先设计好相应的框架,思路
-
编程是一件非常需要细心的事情,不仅要实现好功能,后续也要考虑对代码进行一些优化
-
每次修改程序,都应该进行回滚测试,确保新增功能不会影响原功能的实现,调试程序时也要考虑到尽可能多的情况
-
对于结对编程
-
设计时应与队友一起进行,自己和队友都要理解相应的思路,这样无论谁做驾驶员时都能理解代码,避免因为没有沟通充分导致后续编程出现问题
-
结对编程时双方都要轮流做驾驶员,另一方也需要做好领航者,没有任何一方可以偷懒
-
结对编程的好处很多,碰到问题时可以一起讨论,观察代码的一方也容易发现编写代码者没发现的错误
-