一、预估与实际
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 8 | |
• Estimate | • 估计这个任务需要多少时间 | 10 | 8 |
Development | 开发 | ||
• Analysis | • 需求分析 (包括学习新技术) | 30 | 15 |
• Design Spec | • 生成设计文档 | 30 | 35 |
• Design Review | • 设计复审 | 10 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
• Design | • 具体设计 | 20 | 20 |
• Coding | • 具体编码 | 500 | 90 |
• Code Review | • 代码复审 | 20 | 40 |
• Test | • 测试(自我测试,修改代码,提交修改) | 200 | 150 |
Reporting | 报告 | ||
• Test Repor | • 测试报告 | 40 | 20 |
• Size Measurement | • 计算工作量 | 15 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 50 | 40 |
合计 | 476 |
二、需求分析
我通过百度的方式了解到,小学一年级数学有如下的几个特点:
- 特点
-
只有加减法
-
只有正整数运算
-
没学负数
-
只会运算100以内的数
-
加法只会 :2位数加整10的数、2位数加小于等于10的数
-
减法只会 :2位数减整10的数、2位数减小于等于10的数
-
我通过百度的方式了解到,小学二年级数学有如下的几个特点:
- 特点
-
比小学一年级多了乘除法
-
也是只会正整数运算
-
同样没有学过负数
-
会2位数相加减的运算了
-
只会运算100以内的数
-
经过分析,我认为,这个程序应当:
- 可接接收2个参数:题目数量、年级(不输入第二个参数时候默认年级1)
- 会检测出各种输入参数的错误
- 会根据传入的年级生成对应需求的题目
- 最后将题目答案输出到out.txt文件
- 各部分函数代码给注释,有明确的逻辑。
- 程序运行中、文件输出后要给明确的提示
三、设计
1. 设计思路
-
这个程序有一个类,3个函数,功能如下:
- 主函数里调用check()函数排除输入参数的各种问题
- 主函数里调用init()函数初始化题目和答案
- 主函数里调用OutPutFile()函数将题目和答案输出到out.txt文件中
-
check()类用来排除输入参数的各种问题,函数流程如下:
- 检测输入的参数的数量(只能为0、1、2这几种)
- 检测输入的是否为正整数
- 检测参数的大小是否符合需求
-
init()类用来生成题目和答案,函数流程如下:
- 生成2个可变字符串
- 生成2个随机数
- 在循环内将2个随机数用可变字符串拼接成题目和答案,并且保存答案
-
OutPutFile()函数将题目和答案输出到out.txt文件
-
函数流程图:
-
算法的关键:
- 判断输入的参数是否合法,对于不合法的各种情况需给予提示
- 生成的数字是否满足大纲要求时的判断逻辑
2. 实现方案
写出具体实现的步骤
- 准备工作:
- 先在Github上Fork厂库,并克隆到本地
- 编写任务1代码
- 在任务1的基础上改进逻辑并增加任务2功能
- 优化任务二代码,并增加注释
- 代码测试
- 技术关键点:拼接可变字符串、按需求产生随机数、文件io流、输入参数的各种判断
四、编码
编码过程:
- main函数:
- 创建一个可变字符串str用于拼接题目
- 创建check()函数专门用于检测输入参数的各种错误
- 将str和main函数的参数传入init()初始化函数中
- 创建OutPutFile()用于题目和答案输出到out.txt文件
- init()函数:
- 创建1个可变字符串str1用于拼接答案
- 创建1个for循环
- 循环内生成2个随机数
- 用str拼接题目
- 用条件判断语句判断当前的运算符并算出答案
- str1拼接答案
- str1拼接到str尾部
遇到的问题:
- 如何使用cmd接收的参数
查阅别人的博客发现,用cmd输入的参数的使用方法和字符串数组的使用方法一致。
- 加减法怎么才能保证是随机的
原先我是用问号表达式来控制加减后面发现这种发放并不适用于2年级的加减乘除的情况。思考了一会儿,我将加减乘除法存入字符串数组当中,然后用随机数随机数组的下标就实现了,1年级随机数范围[0-1],2年级随机数范围[0-3]。
- 怎么才能实现题目和答案分离输出
原先我打算用字符串来完成,但是后面突然想到,如果要进行大批的字符串拼接可变字符串的拼接效率是最高的,所有我改变使用可变字符串Stringbuffer来进行拼接,我用2个可变字符串分开拼接题目和答案,最后再把题目和答案拼接到一起就解决了这个问题。
-
如何实现年级判断
- 我在年级判断的时候卡壳了一会儿,主要是因为1个参数应该默认为1年级,2个参数也可能是1年级也可能是二年级。经过一番思考我加了参数长度的判断就解决了问题。
-
我在字符串拼接的时候有换行out.txt 文件里面的题目全部显示在一行
百度查阅资料发现记事本里面的换行是' '
1. 调试日志
- OutPutFile()函数,将可变字符串转成二进制流打印却一直为空。
解决:我发现在主函数创建的可变字符串str在init()函数里面又重新创建了一次,删除init()函数里面的str可解决问题。
- 用' '换行了但是out.txt文件里面的题目和答案还是都在一行。
解决:txt文件的换行是' ',修改在字符串拼接过程中的换行符即可。
- 输入的参数为英文、特殊符号、参数个数不对、参数数值过大程序意外停止
解决:将判断参数各种错误的代码独立抽出来写成一个check()函数,并且在出现错误的地方增加提示和正确的结束程序,防止程序抛错误。
2. 关键代码
/*
* init函数产生题目和答案
*/
public static void init(StringBuffer strbuf, String[] s) {
int result = 0; // 表示题目答案
int remainder = 0; // 表示余数
String operator = null; // 表示随机出来的运算符
String[] mark_code = { "+", "-", "*", "/" }; // 小学1年级和2年级所有的运算
StringBuffer strbuf1 = new StringBuffer(); // 用于答案的拼接
//for循环拼接题目和答案
for (int i = 1; i <= Integer.valueOf(s[0]); i++) {
//判断年级 判断该年级对应的运算
if (s.length == 1) {
operator = mark_code[(int) (Math.random() * 2)];
} else {
if (Integer.valueOf(s[1]) == 1) {
operator = mark_code[(int) (Math.random() * 2)];
} else if (Integer.valueOf(s[1]) == 2) {
operator = mark_code[(int) (Math.random() * 4)];
} else {
System.out.println("输入年级有误!!");
System.exit(0);
}
}
//题目序号
String number = "(" + i + ")" + " ";
// 生成2个随机数
int first = (int) (Math.random() * 100);
int second = (int) (Math.random() * 100);
// 按照年级需求生成指定的题目
if (operator.equals("+")) {
if (s.length == 2 && s[1].equals("2")) {
//二年级的加法题目答案不超过100
while (first + second > 100) {
first = (int) (Math.random() * 100);
second = (int) (Math.random() * 100);
}
} else {
while (first + second > 100) {
//1年级的加法:
//1. 答案不超过100
//2. 可以是2位数+个位数
//3. 可以是2位数+整的10位数
if (first % 2 == 0) {
first = (int) (Math.random() * 100);
second = (int) (Math.random() * 100);
if (second > 10)
second = second / 10;
} else {
first = (int) (Math.random() * 100);
if (first > 10)
second = second / 10 * 10;
second = (int) (Math.random() * 100);
}
}
}
result = first + second;
} else if (operator.equals("-")) {
// 控制左边的数必须大于右边的数。
if (first < second) {
int t;
t = first;
first = second;
second = t;
}
if (!(s.length == 2 && s[1].equals("2"))) {
//2年级的减法:
//1. 答案不能为负数
//2. 可以是2位数-个位数
//3. 可以是2位数-整的10位数
if (second > 10)second = second / 10 * 10;
result = first - second;
} else {
result = first - second;
}
} else if (operator.equals("*")) {
//控制乘法只能是99乘法表上面的
while (first > 10 || second > 10) {
first = (int) (Math.random() * 10);
second = (int) (Math.random() * 10);
}
result = first * second;
} else {
// 当运算符为除时候 除数不能为0
while (second == 0) {
second = (int) (Math.random() * 100);
}
//控制除数只能是小于等于10
while (second > 10) {
second /= 10;
}
if (first % second == 0) {
result = first / second;
} else { //控制不能整除将答案和余数都显示出来
if (first > second) {
result = (int) (first / second);
remainder = first - (int) (first / second) * second;
} else {
result = (int) (first / second);
remainder = second;
}
}
}
// 如果为除法要考虑是否有余数拼接在答案后面
if (operator.equals("/")) {
strbuf1.append(number + first + " " + operator + " " + second + " " + "=" + " " + result + "..." + remainder + "
");
} else {
strbuf1.append(number + first + " " + operator + " " + second + " " + "=" + " " + result + "
");
}
strbuf.append(number + first + " " + operator + " " + second + "
");
}
// 将题目和答案拼接在strbuf可变字符串当中
strbuf = strbuf.append("
" + strbuf1);
}
3. 代码规范
请给出本次实验使用的代码规范:
- 第一条代码中的命名均不以下划线或美元符号开始,也不一下划线或美元符号结束。
- 第二条代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
- 第三条类名使用UpperCamelCase风格,但是以下情形例外:DO / DTO / VO / AO / PO 等。
- 第四条方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵循驼峰形式。
- 第五条变量和常量的命名方式:非公有(private/protected/default)变量前面要加上小写m;
- 第六条类型与中括号紧挨相连来定义数组。正例:定义整形数组 int[] arrayDemo;并人工检查代码是否符合规范
- 第七条杜绝完全不规范的缩写,避免忘文不知义。反例:AbstractClass“缩写”命名成AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。
- 第八条为了达到代码自己自解释的目标,任何自定义编程元素在命名时,使用精良完整的单词组合来表达其意。但是有些名称实在过于长,这个可以适当的缩写,不要忘文不知义就可以。这个不是客观的规定。
- 第九条不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
- 第十条大括号的使用约定。如果是大括号内为空,则简介地写成{}即可,不需要换行;如果是非空代码块则:
- 左大括号前不换行。
- 左大括号后换行。
- 右大括号前换行。
- 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
五、测试
测试序号 | 具体输入 | 预期结果 | 实际结果 |
---|---|---|---|
1 | java MathExam6348 0 | 生成0道1年级题目 | 符合预期结果 |
2 | java MathExam6348 000000000098 | 生成98道1年级题目 | 符合预期结果 |
3 | java MathExam6348 0000000000112 2 | 生成112道2年级题目 | 符合预期结果 |
4 | java MathExam6348 123asd | 请输入一个正整数 | 符合预期结果 |
5 | java MathExam6348 123.5 | 请输入一个正整数 | 符合预期结果 |
6 | java MathExam6348 -123 | 请输入一个正整数 | 符合预期结果 |
7 | java MathExam6348 asdf56465 | 请输入一个正整数 | 符合预期结果 |
8 | java MathExam6348 1231654561 | 输入的数字过大 | 符合预期结果 |
9 | java MathExam6348 1231654561 2 | 输入的数字过大 | 符合预期结果 |
10 | java MathExam6348 -123 2 | 请输入一个正整数 | 符合预期结果 |
11 | java MathExam6348 | 输入参数的数量错误 | 符合预期结果 |
12 | java MathExam6348 501 1 | 生成501道1年级题目 | 符合预期结果 |
10 | java MathExam6348 456421231564 1 | 输入的数值过大 | 符合预期结果 |
六、总结
-
完成了第一次的作业其实感觉收获挺多的,从一开始的像个无头苍蝇不知道该怎样入手,到现在的思路清晰才体会到‘分而治之’的好处。头一次接触到这样的作业总感觉很难很难尤其是在看了题目之后完全不想着手,但是在之后的2天里,看到栋哥在群里面放的鸡汤,我被鸡汤深深的给打动了,终于静下心来开始研究作业,看了几遍作业我不知道该如何下手,就先将任务1任务2的代码写完了,然后复制了博客,发表一份,再在原来的基础上面一直改。做完需求分析我才发现我原来的代码是有这么多bug,然后就是一直修各种bug的过程了,改完了bug我的代码又感觉乱的不得了,开始了整理代码、重构代码、标注注释的不归路,经历一番彻骨寒终于改好了。
-
先需求分析再上代码
-
先需求分析再上代码
-
先需求分析再上代码
- 不需求分析直接上代码??bug多到你飞起。
-
先写注释再写代码
-
先写注释再写代码
-
先写注释再写代码
- 这是我血和泪的教训啊!写完注释代码就是填空了而且逻辑还清晰。下次再直接上代码我剁手
-
还有就是每次写完一个小功能要及时交到github上面,做好注释这样才能方便以后的维护工作。
-
另外分而治之和重构是必要的我也是最后阶段才领悟的。基于这个思想我的代码可以更具有逻辑性且分工明确,便于排查问题。
- 我将代码分成三部份分别执行三个功能:
- check()函数专门检查输入的参数的各种错误
- OutPutFile()函数专门负责文件输出成out.txt文件
- init()初始化生成题目和答案保存在可变字符串当中
- 我将代码分成三部份分别执行三个功能: