本次结对编程让我学到了许多许多知识,受益匪浅!在此之前,我没想过我能做出一个双击运行的小程序。

    感谢我的队友与我同心协力,感谢室友宇欣告诉我操作符为“最多多少”而不是“多少”并教我使用效能分析工具,感谢陈杰不辞辛苦帮我测试14寸显示屏效果,感谢福孝大佬给我发的安装包!感谢学姐对项目的建议!

    代码仓库地址:https://git.coding.net/Siamese_miao/team.git

    本人:庄莉,学号:2016012034

    队友:王璐瑶,学号:2016012095


 

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为单元测试)。

四则运算结对项目之GUI

  • Command类:命令行测试类,负责接收命令行的参数并启动程序。
  • fileCreate类:创建文件类,负责产生result.text文件,将练习题写入文件以及做题模式的生成记录。
  • formula类:式子类,负责根据调用产生同种类型的式子,含有AddSubtract(加减运算)、arithmetic(简单四则运算)、Bracket(带括号的四则运算)、Bracket_AS (带括号的加减运算)四种函数。
  • calculate类:计算类,负责各种计算,含有有条件产生后一位数、有条件操作符等7个方法。
  • stack类:栈,负责计算式子,并判断式子合法性。

四则运算结对项目之GUI

    其中,有条件生成操作符与后一位数我较为满意,它大大的降低了运行效率,部分代码可看第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     }
result函数

    在栈的运算中加入判断

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等方面分析了计算模块的性能,截图如下:

四则运算结对项目之GUI

     性能分析过程截图:

四则运算结对项目之GUI

四则运算结对项目之GUI

     按F4,出现以下截图。资源全部被回收。证明没有资源泄露。程序性能良好。

四则运算结对项目之GUI

      使用单元测试的CPU分析如下图:

四则运算结对项目之GUI

四则运算结对项目之GUI

四则运算结对项目之GUI

四则运算结对项目之GUI

    使用Command.java的CPU效能分析如下图: 

四则运算结对项目之GUI

四则运算结对项目之GUI

 


 

单元测试

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     }
栈的测试

相关文章: