本次结对编程让我学到了许多许多知识,受益匪浅!在此之前,我没想过我能做出一个双击运行的小程序。
感谢我的队友与我同心协力,感谢室友宇欣告诉我操作符为“最多多少”而不是“多少”并教我使用效能分析工具,感谢陈杰不辞辛苦帮我测试14寸显示屏效果,感谢福孝大佬给我发的安装包!感谢学姐对项目的建议!
代码仓库地址:https://git.coding.net/Siamese_miao/team.git
本人:庄莉,学号:2016012034
队友:王璐瑶,学号:2016012095
计划PSP
PSP | 任务内容 | 计划共完成需要的时间(h) |
Planning | 计划 | 0.5 |
Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 0.5 |
Development | 开发 | 39.25 |
Analysis | 需求分析 (包括学习新技术) | 0.5 |
Design Spec | 生成设计文档 | 0.25 |
Design Review | 设计复审 (和同事审核设计文档) | 0.25 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 0.25 |
Design | 具体设计 | 2 |
Coding | 具体编码 | 30 |
Code Review | 代码复审 | 1 |
Test | 测试(自我测试,修改代码,提交修改) | 5 |
Reporting | 报告 | 4 |
Test Report | 测试报告(包括博客) | 3 |
Size Measurement | 计算工作量 | 0.5 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 0.5 |
结对编程对接口的设计
信息隐藏(Information Hiding)
Information hiding is part of the foundation of both structured design and object-oriented design. In structured design, the notion of “black boxes” comes from information hiding. In object-oriented design, it gives rise to the concepts of encapsulation and modularity, and it is associated with the concept of abstraction.
在《代码大全》中列出了两类需要隐藏的内容:
第一类信息是复杂化的信息。对于我们的项目,我们的main函数只有一个对gui的实例化,使用者并不知道内部的运行方式。内部的算法实现封装起来,外部只有调用的接口,只可以调用方法,不可以改变内部变量,做到了信息隐藏。
对于第二类,是指变动的信息。比如在用户的输入需求中出现了错误,提示并返回,这个错误在类中进行了适当的处理,错误没有扩散,这样可以提高程序的容错性。
接口设计(Interface Design)
在本项目设计接口过程中,按需求新建接口,使用明确的命名方式使接口的功能清晰化,增强了可读性;接口与接口之间互相独立,使用方便。
松耦合(Loose coupling)
耦合的强度依赖于:(1)一个模块对另一个模块的调用;(2)一个模块向另一个模块传递的数据量;(3)一个模块施加到另一个模块的控制的多少;(4)模块之间接口的复杂程度。等等。
模块内子程序(下一个层次上)应共享数据(有一定的耦合度),而减少全局变量能降低子程序性间的耦合性。
类与类之间通常通过接口的契约实现服务提供者/服务请求者模式,这就是典型的松耦合。
耦合程度越高,模块与模块之间的联系性就更高,系统的灵活性就越低,报错率就更高。在我们的项目中,计算模块的调用都比较单一,没有双向调用,使用之间互不干扰,增加了灵活性。
计算模块接口的设计与实现过程
经过商讨,我们决定基于我的个人项目修改。我先删除了原来的分数运算,在将普通四则运算与括号四则运算拆分,变成简单加减、四则运算、有括号加减与有括号四则运算。如图我分为5个类(test为单元测试)。
- Command类:命令行测试类,负责接收命令行的参数并启动程序。
- fileCreate类:创建文件类,负责产生result.text文件,将练习题写入文件以及做题模式的生成记录。
- formula类:式子类,负责根据调用产生同种类型的式子,含有AddSubtract(加减运算)、arithmetic(简单四则运算)、Bracket(带括号的四则运算)、Bracket_AS (带括号的加减运算)四种函数。
- calculate类:计算类,负责各种计算,含有有条件产生后一位数、有条件操作符等7个方法。
- stack类:栈,负责计算式子,并判断式子合法性。
其中,有条件生成操作符与后一位数我较为满意,它大大的降低了运行效率,部分代码可看第5模块的性能改进模块。
计算模块接口部分的性能改进
基于原来的个人项目代码,由于出现了运算过程以及运算结果数值范围的限制,原本的result(String temp)不再使用,改用了栈运算。
1 // 计算结果
2 public static Object result(String temp) {
3 ScriptEngineManager sem = new ScriptEngineManager();
4 ScriptEngine se = sem.getEngineByName("js");
5 Object last = 0;
6 try {
7 last = se.eval(temp);
8 } catch (ScriptException e) {
9 e.printStackTrace();
10 }
11 return last;
12 }
在栈的运算中加入判断
1 if (Math.abs(sresulat) > upper || Math.abs(sresulat) < lower)
2 {
3 return 0;
4 }
而对于简单加减无括号全程不改变优先级的运算则不过栈,直接边生成数字便运算,减少了运算时间。
另外,原本的操作符是一开始随机生成好的再判断选择后一个数,然后再判断符号是否合法,再修改符号,如果还是有小数或负数,则重新运行生成算式的函数,这样使得代码运行有些慢且多次运行。再加上数值范围的限定以及可以存在负数,我改变了想法。
因为负数的存在,使得加减号并没有数字的限制,而乘法有上限限制,除法有下限限制。所以在只有加减的运算中,符号随机生成,后一个数根据运算符以及数值范围生成合法的数。
1 // 相加不超过范围
2 public static int decide0(int x, int min, int max)
3 {
4 int y;
5 int temp = 0;
6 if (x > 0)
7 {
8 temp = max - min - x + 1;// 加一个正整数范围
9 } else
10 {
11 temp = max - (min - x) + 1;// 加至正整数的范围
12 }
13 if (temp < 0)
14 {// 范围小于0
15 if (x > 0)
16 {
17 temp = Math.abs(x) - min * 2 + 1;// 正整数过大,需加负数
18 y = 0 - (int) (Math.random() * temp) - min;
19 } else
20 {
21 temp = Math.abs(x) - 2 * min + 1;// 负数过小,越值,加小整数至负数范围
22 y = (int) (Math.random() * temp) + min;
23 }
24 } else
25 {
26 y = (int) (Math.random() * temp + min);
27 }
28 return y;
29 }
30
31 // 相减不小于最小
32 public static int decide1(int x, int min, int max)
33 {
34 int temp = 0;
35 int y = 0;
36 if (x > 0)
37 {
38 temp = x - 2 * (min - 1) - 1; // 减一个正数范围
39 } else
40 {
41 temp = max + x - min + 1;// 减一个正数范围
42 }
43 if (temp > 0)
44 {
45 if (x < 0 && temp < min)
46 {
47 temp = Math.abs(x) - 2 * min + 1;// 负数过小,需减负数
48 y = 0 - (int) (Math.random() * temp) - min;
49 } else
50 {
51 y = (int) (Math.random() * temp + min);
52 }
53 } else
54 {
55 temp = max - x - min + 1;// 只有x>0的情况会出现,正数过小,需减负数
56 y = 0 - (int) (Math.random() * temp) - min;
57 }
58 return y;
59 }
当有乘除时,则根据上一个数生成操作符,再根据操作符生成合法的后一位数。
1 // 操作符的选定
2 public static int operator(int num, int middle2, int middle3)
3 {
4 if (Math.abs(num) <= middle2)
5 {// 除法下界
6 if (Math.abs(num) < middle3)
7 {
8 return 3;
9 } else
10 {
11 return 0;
12 }
13 } else if (Math.abs(num) >= middle3)
14 {// 乘法上界
15 return 2;
16 } else
17 {
18 return (int) (Math.random() * 4);
19 }
20 }
21 // 下一位数字的选定
22 public static int[] numberB(int key, int num, int lower, int upper)
23 {
24 int[] find = new int[] { 0, lower };
25 if (key == 0)
26 {
27 find[1] = decide0(num, lower, upper);
28 return find;
29 } else if (key == 2)
30 {
31 int[] judge = new int[2];
32 judge = decide2(num, lower);// 确保能够整除,并不低于下限
33 if (judge[0] == 0)
34 {
35 find[1] = judge[1];
36 return find;
37 } else
38 {
39 find[0] = 1;
40 }
41 } else if (key == 3)
42 {
43 find[1] = decide3(num, lower, upper);
44 if (find[0] == 0)
45 {
46 return find; // 乘法不超过上限
47 }
48 }
49 find[1] = decide1(num, lower, upper);
50 return find;
51 }
这样大大减少了重新调用函数的问题,并且实现了运算过程与数值皆在范围内的功能。
在附加题记录用户模块,一开始使用contains(name)函数判断用户,后来发现这样会出现abc与abcabc被认为同一个人而的情况,经过思考,我们使用字符串的断开。
1 String[] arrays = txt.split(" ");
再使用equals(String)函数判断用户,解决了这个问题。
其中,生成有括号与乘除的式子生成的函数判断耗时最多,因为它的判断较多,限制较多,优先级易改变,容易生成最终不合法的式子而重新运行。
1 // 带括号的四则运算
2 public static String Bracket(int lower, int upper, int o) {
3 int middle2 = lower * lower;// 除法下界
4 int middle3 = upper / lower;// 乘法上界
5 int brack_left = 0; // 记录未匹配的左括号个数
6 int brack = 0; // 括号个数
7 int j = 0;
8 char[] p = new char[] { '+', '-', '÷', '*' };
9 String temp1 = "";
10 int[] num = new int[o + 1]; // 数字
11 int[] key = new int[o]; // 符号所在的下标
12 num[0] = (int) (Math.random() * (upper - lower + 1) + lower);
13 int result;
14 int[] find = new int[2];
15 for (j = 0; j < (o - 1); j++) {
16 if (num[j] < 0) {
17 temp1 += "(" + String.valueOf(num[j]) + ")";
18 } else {
19 temp1 += String.valueOf(num[j]);
20 }
21 int tmpcnt = brack_left;
22 for (int i = 0; i < tmpcnt; i++) { // 若当前有未匹配的左括号,则对每一个未匹配的左括号,都有一定概率生成相应右括号。
23 if ((int) (Math.random() * 5) > 1) { // 生成右括号概率为0.6
24 brack_left--;
25 temp1 += ")";
26 }
27 }
28 key[j] = calculate.operator(num[j], middle2, middle3);
29 find = calculate.numberB(key[j], num[j], lower, upper);
30 if (find[0] == 1) {
31 key[j] = 1;
32 }
33 num[j + 1] = find[1];
34 temp1 += String.valueOf(p[key[j]]);
35 if (((brack * 2) <= o) && (((int) (Math.random() * 2)) == 0)) { // 以一定概率生成左括号,概率为1/2
36 temp1 += "(";
37 brack++;
38 brack_left++;
39 j++;
40 if (num[j] < 0) {
41 temp1 += "(" + String.valueOf(num[j]) + ")";
42 } else {
43 temp1 += String.valueOf(num[j]);
44 } // 生成左括号后必须生成一个数字和运算符,不然可能出现(15)这样的错误
45 key[j] = calculate.operator(num[j], middle2, middle3);
46 find = calculate.numberB(key[j], num[j], lower, upper);
47 if (find[0] == 1) {
48 key[j] = 1;
49 }
50 num[j + 1] = find[1];
51 temp1 += p[key[j]];
52 }
53 }
54 while (j != o) { // 判断是否为最后一个数
55 if (num[j] < 0) {
56 temp1 += "(" + String.valueOf(num[j]) + ")";
57 } else {
58 temp1 += String.valueOf(num[j]);
59 }
60 key[j] = calculate.operator(num[j], middle2, middle3);
61 temp1 += p[key[j]];
62 find = calculate.numberB(key[j], num[j], lower, upper);
63 if (find[0] == 1) {
64 key[j] = 1;
65 }
66 j++;
67 num[j] = find[1];
68 }
69 if (num[o] < 0) {
70 temp1 += "(" + String.valueOf(num[o]) + ")";
71 } else {
72 temp1 += String.valueOf(num[o]);
73 }
74 while ((brack_left) != 0) { // 补全右括号
75 temp1 += ")";
76 brack_left--;
77 }
78 result = stack.work(temp1, lower, upper, 1);
79 if (result == 0) {
80 temp1 = Bracket(lower, upper, o);
81 }
82 return temp1;
83
84 }
85
86 }
项目总体分析图,从内存,多线程,CPU等方面分析了计算模块的性能,截图如下:
性能分析过程截图:
按F4,出现以下截图。资源全部被回收。证明没有资源泄露。程序性能良好。
使用单元测试的CPU分析如下图:
使用Command.java的CPU效能分析如下图:
单元测试
1 @Test 2 public void testWork() { 3 assertEquals(0, stack.work("7-5÷(1*37)÷(1*83)", 1, 900, 1)); 4 assertEquals(30, stack.work("55+(-25)÷5*(20-15)", 2, 300, 1)); 5 assertEquals(80, stack.work("((55+25)÷5)*(20-15)", 2, 300, 1)); 6 assertEquals(0, stack.work("60*(20-15)", 2, 200, 1)); 7 }
第一个断言测试的是无法整除返回错误标志0;
第二个断言测试的是负数运算;
第三个断言测试的是特殊括号位置的运算;
第四个断言测试的是超过数值返回错误标志0。
1 @Test
2 public void testAll() {
3 // 顺序不同以及异常测试。生成的文件会被覆盖。
4 String[] arg0 = new String[] { "-n", "100", "-m", "5", "100", "-o", "3", "-c", "-b" };
5 String[] arg1 = new String[] { "-m", "5", "50", "-o", "3", "-n", "100", "-c" };
6 String[] arg2 = new String[] { "-o", "3", "-m", "5", "50", "-n", "100", "-b" };
7 String[] arg3 = new String[] { "-n", "100", "-o", "3", "-m", "5", "50" };
8 Command.main(arg0);// 有括号四则运算测试
9 Command.main(arg1);// 四则运算测试
10 Command.main(arg2);// 有括号加减运算测试
11 Command.main(arg3);// 加减运算测试
12 }
该部分测试的命令行的更改输入顺序的四种出题选择正常运行。输入异常部分请看第七点。
命令行单元测试覆盖率截图如下:
1 @Test
2 public void testDecide2() {
3 int[] find = new int[2];
4 find = calculate.decide2(20, 2);
5 assertEquals(2, find[1]);
6 find = calculate.decide2(13, 2);
7 assertEquals(1, find[0]);
8 }
decide2(int x, int min)为除法选择除数的函数,函数如下:
1 // 被除数能被除数整除并不低于最小
2 public static int[] decide2(int x, int min)
3 {
4 int[] judge = new int[] { 1, 0 };
5 int temp = Math.abs(x) / min - min + 1;// 除数的范围
6 for (int i = min; i < (temp + min); i++)
7 {
8 if (Math.abs(x) % i == 0)
9 {// 判断是否整除
10 judge[0] = 0;
11 judge[1] = i;
12 return judge;
13 }
14 }
15 return judge;
16 }
其中,judge[0]用于判断该数能否有可整除的除数,1为没有,0为有,judge[1]为除数的值。该单元测试则测试了一次可产生除数与一次不能产生除数的情况。
异常说明
1 @Test
2 public void testAll() {
3 String[] arg4 = new String[] { "-o", "3", "-m", "5", "50", "-n" };
4 String[] arg4_1 = new String[] { "-o", "3", "-n", "-m", "5", "50" };
5 String[] arg4_2 = new String[] { "-n", "100000", "-m", "5", "50" };
6 String[] arg4_3 = new String[] { "-o", "3", "-m", "5", "50" };
7
8 String[] arg5 = new String[] { "-n", "50" };
9 String[] arg5_1 = new String[] { "-m", "5", "-n", "50", "-o", "3" };
10 String[] arg5_2 = new String[] { "-n", "50", "-m", "3" };
11 String[] arg5_3 = new String[] { "-n", "50", "-o", "3", "-m" };
12 String[] arg5_4 = new String[] { "-m", "-n", "50" };
13
14 String[] arg6 = new String[] { "-o", "11", "-m", "5", "50", "-n", "100" };
15 String[] arg6_1 = new String[] { "-n", "100", "-o", "-m", "5", "50" };
16 String[] arg6_2 = new String[] { "-n", "100", "-m", "5", "50", "-o" };
17
18 String[] arg7 = new String[] { "-m", "5", "20", "-n", "100", "-c" };
19 String[] arg7_1 = new String[] { "-m", "5", "50", "-n", "100", "-b" };
20
21 String[] arg8 = new String[] { "-b", "1", "-o", "3", "-m", "5", "50", "-n", "100" };
22 String[] arg8_1 = new String[] { "-c", "1", "-o", "3", "-m", "5", "50", "-n", "100" };
23 String[] arg8_2 = new String[] { "-n", "100", "-m", "5", "50", "-d" };
24
25 Command.main(arg4);// 缺少题数值测试
26 Command.main(arg4_1);
27 Command.main(arg4_2);// 题数值过大测试
28 Command.main(arg4_3);// 缺少题数测试
29
30 Command.main(arg5);// 缺少数值范围
31 Command.main(arg5_1);// 缺少数值范围上限测试
32 Command.main(arg5_2);
33 Command.main(arg5_3);// 缺少数值范围上下限测试
34 Command.main(arg5_4);
35
36 Command.main(arg6);// 操作符数值过大测试
37 Command.main(arg6_1);// 缺少操作符数值测试
38 Command.main(arg6_2);
39
40 Command.main(arg7);// 乘除需要上界大于下界的平方
41 Command.main(arg7_1);// 括号需要操作符数大于1
42
43 Command.main(arg8);// 输入非法测试之b后有数字
44 Command.main(arg8_1);// 输入非法测试之c后有数字
45 Command.main(arg8_2);// 输入非法测试之无辨识字符
46 }
对于命令行可能出现的异常大概有13个:
- 缺少题数值(-n后无带数字,如arg4与arg4_1)时,提醒缺少题数值,并告知-n的范围;
- 题数值过大(-n后数值超过10000,如arg4_2)时,提醒告知题数值范围(过小同理);
- 缺少题数(命令中无-n,如arg4_3)时,提醒-n为必须项,并告知-n范围。
- 缺少数值范围(命令中无-m,如arg5)时,提醒-m为必须项,并告知-m上下限各自范围;
- 缺少数值范围上限(-m后只带一个数字,如arg5_1和 arg5_2)时,提醒缺少上限,并告知上限范围;
- 缺少数值范围上下限(-m后不带数字,如arg5_3和 arg5_4)时,提醒缺少上下限,并告知上下限各自范围;
- 数值范围数值过小过大时,提醒告知操作符数值范围。
- 操作符数值过大(-o后数值超过10,如arg6)时,提醒告知操作符数值范围(过小同理);
- 缺少操作符数值(输入-o,后方没有带数值,如arg6_1与arg6_2)时,提醒缺少操作符数值,并告知-o范围。
- 选择乘除法但是上界小于下界的平方,无法生成含有乘除的式子(如arg7)时,提醒上界需大于下界的平方;
- 选择括号但是操作符默认为1或选择为1,不符合生成括号的条件(如arg7_1)时,提醒选择括号需要操作符数大于1。
- –b(或-c)后带数字(如arg8与arg8_1),提醒-b(或-c)后不能带数字;
- 出现除m、n、o、b、c外的字符如d等(如arg8_2),提醒输入值非法。
界面模块的详细设计过程
设计图如下:
我们先从选择出题或做题开始。
选择出题则进入出题参数输入界面。
利用MouseListener的mouseEntered(MouseEvent e)与setTitle(String);使得鼠标移到参数上,标题会有提示功能。
输入完毕点击确认后,由输入的参数判断是否有异常并提示直至无异常创建文件。
1 public class submitListener implements ActionListener {
2 public void actionPerformed(ActionEvent e) {
3 String m = "题数与数值上下限为必填项,请按标题提示输入正整数!";
4 String m2 = "创建文件成功!";
5 int n0, lower0, upper0, o0, c0, b0;
6 o0 = 1;
7 c0 = 0;
8 b0 = 0;
9 String o1 = "";
10 try {
11 n0 = Integer.parseInt(n.getText());
12 lower0 = Integer.parseInt(lower.getText());
13 upper0 = Integer.parseInt(upper.getText());
14 if (n0 < 1 || n0 > 10000) {
15 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), "题数范围为1-10000", "提示",
16 JOptionPane.INFORMATION_MESSAGE);
17 return;
18 }
19 if (lower0 < 1 || lower0 > 100) {
20 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), "下界范围为1-100", "提示",
21 JOptionPane.INFORMATION_MESSAGE);
22 return;
23 }
24 if (upper0 < 50 || upper0 > 1000) {
25 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), "上界范围为50-1000", "提示",
26 JOptionPane.INFORMATION_MESSAGE);
27 return;
28 }
29 if (upper0 < (2 * lower0)) {
30 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), "上界必须大于两倍下界", "提示",
31 JOptionPane.INFORMATION_MESSAGE);
32 return;
33 }
34 } catch (NumberFormatException e2) {
35 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), m, "提示", JOptionPane.INFORMATION_MESSAGE);
36 return;
37 }
38 try {
39 o1 = o.getText();
40 o0 = Integer.parseInt(o1);
41 } catch (NumberFormatException e2) {
42 if (!o1.equals("")) {
43 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), "请输入1-10的正整数或不输入保持默认,默认为1", "提示",
44 JOptionPane.INFORMATION_MESSAGE);
45 return;
46 }
47 }
48 if (c.isSelected()) {
49 c0 = 1;
50 }
51 if (b.isSelected()) {
52 b0 = 1;
53 }
54 if (o0 == 1 && b0 == 1) {
55 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), "括号需要操作符数量大于1", "提示",
56 JOptionPane.INFORMATION_MESSAGE);
57 return;
58 }
59 if (c0 == 1 && upper0 < (lower0 * lower0)) {
60 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), "乘除法需要上界数值大于下界的平方", "提示",
61 JOptionPane.INFORMATION_MESSAGE);
62 return;
63 }
64 createFile.fileCreate(n0, lower0, upper0, o0, c0, b0);
65 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), m2, "提示", JOptionPane.INFORMATION_MESSAGE);
66 System.exit(0);
67 }
68 }
选择做题则先输入做题人名字(在这里建议使用英文,中文名字无法很好的记录)。
接着上传文件,在这里使用了txt文件过滤器,使之仅可上传txt文件。
1 FileFilter filter = new FileNameExtensionFilter("Text file", "txt");
2 JFileChooser fileChooser = new JFileChooser();
3 fileChooser.setAcceptAllFileFilterUsed(false);
4 fileChooser.addChoosableFileFilter(filter);
5 FileSystemView fsv = FileSystemView.getFileSystemView();
另外,出题与做题都统一为utf-8编码,免去执行文件编码错误。
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8"));
InputStreamReader read = new InputStreamReader(new FileInputStream(file), "utf-8");
上传成功后开始计时做题,并于最后结果中显示用时。
经过后面JTextPane控件的启发,我考虑到出题时题目长度有长短,为了更加美观显示,应该需要自动换行,我同样采用了HTML编辑文本的想法,做出改进。
1 public static void JlabelSetText(JLabel jLabel, String longString) throws InterruptedException {
2 StringBuilder builder = new StringBuilder("<html>");
3 char[] chars = longString.toCharArray();
4 FontMetrics fontMetrics = jLabel.getFontMetrics(jLabel.getFont());
5 int start = 0;
6 int len = 0;
7 while (start + len < longString.length()) {
8 while (true) {
9 len++;
10 if (start + len > longString.length())
11 break;
12 if (fontMetrics.charsWidth(chars, start, len) > jLabel.getWidth()) {
13 break;
14 }
15 }
16 builder.append(chars, start, len - 1).append("<br/>");
17 start = start + len - 1;
18 len = 0;
19 }
20 builder.append(chars, start, longString.length() - start);
21 builder.append("</html>");
22 jLabel.setText(builder.toString());
23 }
计时方面我原本采用秒数计时,后来考虑到当做题时间较长时,秒数很难清晰明确的表达,所以改用了 hh:mm:ss 法显示。
1 public static String getTimeStrBySecond(long second) {
2 if (second <= 0) {
3 return "00:00:00";
4 }
5 int hours = (int) second / HOUR_SECOND;
6 if (hours > 0) {
7
8 second -= hours * HOUR_SECOND;
9 }
10 int minutes = (int) second / MINUTE_SECOND;
11 if (minutes > 0) {
12 second -= minutes * MINUTE_SECOND;
13 }
14 return (hours > 10 ? (hours + "")
15 : ("0" + hours) + ":" + (minutes > 10 ? (minutes + "") : ("0" + minutes)) + ":"
16 + (second > 10 ? (second + "") : ("0" + second)));
17 }
自动换行处理与秒数转换被我写入新类——dataDeal类中。
最终做完题目后除了显示用时,还显示题数、分数、错题以及该题正确答案,非首次用户会显示历史分数以及最高分数。
原本该部分使用了JTextArea控件,但学姐建议正确答案部分对齐显示会更加美观,并提出了C#中的ListView控件,但很遗憾,Java中似乎并没有。JTextArea控件是纯文本显示,很难做到不同的对齐方式,所以我删除了该类。经过多方学习比较,我最终选择了JTextPane控件,该控件简单易用,可将文本显示为HTML文本,大大提高了编辑的样式性。我最终采取了表格法对齐,另外对重点突出的地方加粗变红显示,达到强调与一定视觉冲击效果,可从后文看到对比图。
1 String text = "<p style='font-family:楷体; font-size:19'>" + name + " 本次用时<span style='color:red'><strong> "
2 + dataDeal.getTimeStrBySecond(spentTime) + " </strong></span>,得分<span style='color:red'><strong> "
3 + goal + " </strong></span>分。<br>";
4 if (size0 == 0) {
5 text += "你总共答了<span style='color:red'><strong> " + size
6 + " </strong></span>道题,并全部答对!<span style='color:red'><strong>恭喜!</strong></span></p>";
7 } else {
8 text += "你总共答了<span style='color:red'><strong> " + size
9 + " </strong></span>道题,答对<span style='color:red'><strong> " + size1
10 + " </strong></span>道,答错<span style='color:red'><strong> " + size0
11 + " </strong></span>道,分别为:</p><p><table border=0>";
12 for (int i = 0; i < (size0 * 2); i++) {
13 text += "<tr><td style='font-family:楷体; font-size:19'><strong>" + wrong.get(i++)
14 + " </strong></td><td width='180' style='font-family:楷体; font-size:19;color:red'><strong> "
15 + wrong.get(i) + "</strong></td></tr>";
16 }
17 }
18 text += "</table></p>";
19 text += "<p style='font-family:楷体; font-size:19'>" + createFile.record(name, goal) + "</p>";
20
21 JTextPane textarea = new JTextPane();
22 textarea.setContentType("text/html");
23 textarea.setText(text);
24 textarea.setEditable(false);
25 JScrollPane textAreascrollPane = new JScrollPane(textarea);
26 add(textAreascrollPane, BorderLayout.CENTER);
界面模块与计算模块的对接
如图所示
在界面模块选择出题输入参数之后调用fileCreate类,再由fileCreate类调用计算模块,创建result.txt
在界面模块选择做题输入名字、上传文件、做题。做题时调用计算模块的stack类计算判断正确性,记录错题。最终结果由计算模块中的fileCreate类的record(String name, int goal)记录,由界面模块显示。
实现的功能大致有12个,并且为了提高用户体验,修改了图标并增加了背景,将操作符数修改为下拉框选择,默认选择为1,避免输入非数字错误:
模式选择
出题参数输入(前后对比图)
出题参数要求提醒
输入参数有误提醒(见第七点异常)
生成文件
记录用户
上传文件(只允许txt文件)
判断文件是否为空或非练习题
计时
一道一道做题并且题目过长时自动换行
评分
根据学姐给的建议做出了修改,以下为前后对比图,正确答案对齐,使之更加美观。另外我修改了做题时间的显示形式,这样当做题时间较长时可以更加清晰的看出时间情况。而做题时间、得分情况、错题与正确答案皆加粗甚至标红,使之更加显眼,提高用户体验。
记录历史分数与最高分数
结对编程
我们先一起分析了需求与功能的实现,并提出了一些有实质性的方法,并确认数据的传递方式。再分析各自的个人项目代码,指出了双方优劣性,在综合考虑选择基础代码加以改进。
我们根据自己较为擅长的方面分工,如相对之下,我对gui较为熟悉,而她对字符串处理较为熟悉,则我负责界面展示而她负责命令行的分析。各自写完之后我们再复审双方代码,对代码不理解之处询问并补充注释,以及对双方异常情况补充。最后在一起整合双方代码,使之成为完整项目。
结对编程的优缺点
在此过程中我们互相帮助、互相学习、能力上得到互补,而代码和产品质量提高,有效地减少bug并且考虑到更多方面的情况。有两台电脑可以测试程序效果,如她的电脑比我小,我的gui显示不同,她的部分算式被遮挡,最终我选择了将按钮部分的面板设为透明,解决了这个问题。
不足之处在于队友之间的进度相互影响,不同的代码风格之间的磨合也花费了一定时间。
双方优缺点:
庄莉 | 王璐瑶 | |
优点 | 认真细心,有责任心 | 任劳任怨 |
代码能力高 | 对字符串以及字符串数组的处理十分熟练 | |
动手能力强 | 很有想法,有好点子 | |
缺点 | 有时候对于小问题过于钻牛角尖 | 因生病而不在状态,没注意到比较细的地方,时间较少 |
实际PSP
PSP | 任务内容 | 实际完成需要的时间(min) |
Planning | 计划 | 0.5 |
Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 0.5 |
Development | 开发 | 53.25 |
Analysis | 需求分析 (包括学习新技术) | 0.5 |
Design Spec | 生成设计文档 | 0.25 |
Design Review | 设计复审 (和同事审核设计文档) | 0.25 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 0.25 |
Design | 具体设计 | 1 |
Coding | 具体编码 | 40 |
Code Review | 代码复审 | 1 |
Test | 测试(自我测试,修改代码,提交修改) | 10 |
Reporting | 报告 | 9 |
Test Report | 测试报告 | 8 |
Size Measurement | 计算工作量 | 0.5 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 0.5 |
本次结对编程真的让我学到很多知识,尤其是各种操作,就像上一篇博客的链接一样,我查了许许多多这样的链接,学习了一种有一种的方法,与队友配合,完成了这次项目。而每次写博客,都能重新总结我的思路,受益良多。
虽然真的很辛苦,但能做出来也就够了。
以下部分由于时间与精力关系,我们小组并没有完成,仅提供思路参考,有想法的同学可加以尝试。
附加题多语言思路参考:程序国际化
https://blog.csdn.net/zhuxinquan61/article/details/51540806
https://blog.csdn.net/zhoudaxia/article/details/37536195
思路注意点参考:
- 文字描述使用短语、名词与阿拉伯数字,便于翻译。 标准语言建议英语,中文字符难以使用字符串判断内容。
- 语言选择可为下拉框列表旁边带一不可编辑的文本框。由于可以自添加,所以可以建立一个文件(如txt),文件中加入选项,执行代码时读出文件,for循环将每一项添加进下拉框列表中。有一项设置为其他或自定义,选择它时,旁边的文本框变为可编辑,用于添加语言(也可让下拉框为可编辑,当输入的语言下拉选项中没有时认为添加新语言)。
- 添加语言则需配置文件(参考网址),将所有需要翻译的文字让用户对应翻译。生成新的配置文件,并命名为该语言(使用英文命名),将该语言添加到保存下拉框选项的文件中。
- 当用户选择一种语言时,通过其相同的命名,即可调用该语言的配置文件进行翻译,达到多语言转化功能。