小学四则运算结对项目报告【GUI】
写在前面
这次的结对项目我做了很长时间,感触也很多。在这次项目中我使用了Java GUI作为和用户的交互方式,但是在上Java课的时候我对GUI和事件驱动这里并没有学的多好,可能是当时对编程还没有什么理解,对这一部分的知识理解地很吃力,只是死记硬背下来应对了考试,在这次项目过程中我又将我的Java书拿出来把这一部分的知识复习了一遍,现在再来看这一部分的知识与当时的感受完全不同了,当时看起来云里雾里的知识现在看来就是理所当然和透彻的了,所以上手地也比较快,我学到的知识也立刻得到了实际的应用。
不仅是专业知识,在这次项目里,我接触了从未想过的结对编程,代码复审;我一步一步地学了用JUnit写单元测试,用Eclemma查看单元测试的覆盖率,分析代码的性能,真正实际接触并亲身做了一些软件工程上的环节,而不仅是从书本上学到了理论,这些经历虽然辛苦,但是带来了很多收获,也让我对这些环节有了更深的了解和记忆。
那么接下来我就用博客记录一下我这次结对项目。
Coding.Net项目地址
https://git.coding.net/ForeverSevrous/CoupleProject.git
PSP
PSP2.1 |
任务内容 |
计划共完成需要的时间(min) |
实际完成需要的时间(min) |
Planning |
计划 |
30 |
* |
· Estimate |
· 估计这个任务需要多少时间,并规划大致工作步骤 |
30 |
* |
Development |
开发 |
1000 |
* |
· Analysis |
· 需求分析 (包括学习新技术) |
60 |
* |
· Design Spec |
· 生成设计文档 |
30 |
* |
· Design Review |
· 设计复审 (和同事审核设计文档) |
30 |
* |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 |
* |
· Design |
· 具体设计 |
30 |
* |
· Coding |
· 具体编码 |
720 |
* |
· Code Review |
· 代码复审 |
60 |
* |
· Test |
· 测试(自我测试,修改代码,提交修改) |
60 |
* |
Reporting |
报告 |
55 |
* |
· Test Report |
· 测试报告 |
30 |
* |
· Size Measurement |
· 计算工作量 |
10 |
* |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
15 |
* |
信息隐藏(Information Hiding)
信息隐藏指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。
在结对项目中,使用命令行模式执行和使用GUI界面执行出题部分时调用了同一部分核心代码,在这里我将出题这一部分信息隐藏,当改变人机交互模式的时候就不会给系统带来全局性的影响。在我的程序中避免了A类调用B类的程序,B类有调用A类的程序这种情况,这也是所谓的“循环依赖”,这种情况会阻碍信息隐藏。
接口设计(Interface Design)
在本项目设计接口过程中,使用有意义的命名方式使接口的功能一目了然,增强了可读性;类名和方法名也都使用了有意义的命名方式;在开发过程中加好了注释,方便自己和他人看懂代码;在接口设计过程中对相关原则也有了一些体会,主要参考以下内容:
https://blog.csdn.net/blueangle17/article/details/55049858
https://jingyan.baidu.com/article/066074d626ea09c3c21cb0b9.html
松耦合(Loose coupling)
耦合的强度依赖于:(1)一个模块对另一个模块的调用;(2)一个模块向另一个模块传递的数据量;(3)一个模块施加到另一个模块的控制的多少;(4)模块之间接口的复杂程度。等等。
模块内子程序(下一个层次上)应共享数据(有一定的耦合度),而减少全局变量能降低子程序性间的耦合性。
类与类之间通常通过接口的契约实现服务提供者/服务请求者模式,这就是典型的松耦合。
在结对项目中,我尽量减少一个模块向另一个模块传递的数据量,如:是否有乘除法通过在主函数中判断调用不同的类,而不是通过传递参数。
计算模块接口的设计与实现过程
吸取在个人项目中同学对我的建议,我画出了用到的类和其中的方法之间的关系图(含有方法的作用),希望能有助于大家的理解:
我的结对项目使用的算法都比较普通,唯一还算值得一提的是我改进了异常提醒的代码,使程序从原来的每运行一次只能报一次错变成了运行一次就将所有的错误都进行提示,这样就提高了用户的使用效率,提升了使用体验。
计算模块接口部分的性能改进
由于出题时对题目的结果和运算过程中的部分结果是有要求的,所以这次虽然在出题时不要求给出结果,但还是需要调用计算模块来提供检查功能,每计算一步就对结果进行判断,对不合格的运算式舍弃不用,但是在出现有乘法的时候数字就会成倍变大,导致运算式不可用,对于这个问题我在出题模块内进行了优化,当出现乘法时,尽量缩小乘数,在一定程度上使结果不会超出限定,加快出题速度。
吸取老师的建议,我们在第一次性能分析的基础上做了CPU性能分析,以下是以下是访问树 Call Tree:
以下是热点 Hot Spots视图,显示了消耗时间最多的方法:
以下是内存性能分析图(包含改进后的成果):
计算模块部分单元测试展示
测试Core类(包含newExpSome,newExpAll,divideExactly)
在测试Core类中的函数时,我模拟了在实际应用时会出现的情况,结合更大限度地覆盖更多代码这一目标,我设计了多组参数,期望能覆盖到各种情况。通过多次进行单元测试,将多次单元测试结合起来,达到了97%。
设计思路:
- 改变运算符个数的上限:由于运算符个数不同产生括号的代码不同,所以增加情况个数能够覆盖更多代码。
- 设置是否有括号和乘除。
- 增加运算次数:这样在使用随机数的函数中能出现更多的情况,覆盖更多的代码,走通一些异常处理代码。
测试Command类(包含main和write方法)
设计思路:
(测试main方法)
在main方法中实现了对输入参数的解析和对输入参数异常的报错,所以测试这个部分的函数最主要的就是构造不同的异常情况,同时也不要忘记测试参数正确时的情况,因为这也是代码的一部分(单元测试中第一次就是因此覆盖率较低)。
(测试write方法)
在write方法中主要实现了将传入的ArrayList分行写入文件,所以测试这个部分的思路就是:在测试方法中构造一个ArrayList,向里面传入一些值,然后以它为参数传入到write方法中,通过输出结果判定是否成功执行。
单元测试代码展示(以测试Core.java为例):
1 import static org.junit.Assert.*; 2 3 import org.junit.After; 4 import org.junit.AfterClass; 5 import org.junit.Before; 6 import org.junit.BeforeClass; 7 import org.junit.Test; 8 9 10 public class CoreTest { 11 12 @BeforeClass 13 public static void setUpBeforeClass() throws Exception { 14 } 15 16 @AfterClass 17 public static void tearDownAfterClass() throws Exception { 18 } 19 20 @Before 21 public void setUp() throws Exception { 22 } 23 24 @After 25 public void tearDown() throws Exception { 26 } 27 28 @Test 29 public void testNewExpSome() { 30 for(int i=0;i<6;i++){ 31 Core.newExpSome(false, 200, 20, 6); 32 } 33 for(int i=0;i<24;i++){ 34 Core.newExpSome(true, 200, 20, 4); 35 Core.newExpSome(true, 200, 20, 4); 36 Core.newExpSome(true, 200, 20, 4); 37 } 38 39 System.out.println("NewExpSome程序正常!"); 40 } 41 42 @Test 43 public void testNewExpAll() { 44 for(int i=0;i<12;i++){ 45 Core.newExpAll(false, 200, 20, 6); 46 } 47 for(int i=0;i<24;i++){ 48 Core.newExpAll(true, 200, 20, 4); 49 Core.newExpAll(true, 200, 20, 4); 50 Core.newExpAll(true, 200, 20, 4); 51 } 52 53 System.out.println("NewExpAll程序正常!"); 54 } 55 56 @Test 57 public void testDivideExactly() { 58 59 } 60 61 }
计算模块部分异常处理说明
以下为各参数会出现的异常及对应的单元测试样例
1.出题数量:出题数量范围错误;出题数量输入值不是数字;未输入出题数量。
单元测试样例:
1 String[] args1 = {“-n”,”-20”} 2 Command.main(args1); 3 String[] args2 = {“-n”,”a”} 4 Command.main(args2); 5 String[] args3 = {“-b”,”-c”} 6 Command.main(args3);
2.运算符上限:上限范围错误;输入值不是数字。
单元测试样例:
1 String[] args1 = {“-o”,”20”} 2 Command.main(args1); 3 String[] args2 = {“-o”,”a”} 4 Command.main(args2);
3.数值范围:上下界范围错误;上下界输入不是数字;下界大于上界。
单元测试样例:
1 String[] args1 = {“-m”,”-20”,”2000”} 2 Command.main(args1); 3 String[] args2 = {“-m”,”a”,”b”} 4 Command.main(args2); 5 String[] args3 = {“-m”,”40”,”20”} 6 Command.main(args3);
4.输入的参数不是规定的参数格式,如输入“-”、“-x”等作为参数。
单元测试样例:
1 String[] args1 = {“-x”} 2 Command.main(args1);
界面模块的详细设计过程
在设计界面的过程中,我分为导航页,获得出题参数页,参数错误提示页,出题完成提示页,上传题目做题页。这几个页面的关系如下:
导航页和获得出题参数页使用了同种方式来生成用户界面,代表导航页和出题参数页的类继承了JFrame类,在这两个类中还分别声明了一个内部类继承了JPanel类,在内部类中加入页面需要的各种组件,然后在外部类的构造方法中新建一个内部类的示例,并将这个内部类的对象加入到外部类的实例中,在这里以导航页的代码为例展示这种方式:
1 import java.awt.event.ActionEvent; 2 import java.awt.event.ActionListener; 3 4 import javax.swing.*; 5 6 public class HelloFrame extends JFrame { 7 public HelloFrame() { 8 setLocationRelativeTo(null); 9 Hello h = new Hello(); 10 add(h); 11 h.make.addActionListener(new ActionListener() { 12 13 @Override 14 public void actionPerformed(ActionEvent e) { 15 // TODO Auto-generated method stub 16 setVisible(false); 17 } 18 19 }); 20 h.ans.addActionListener(new ActionListener() { 21 22 @Override 23 public void actionPerformed(ActionEvent e) { 24 // TODO Auto-generated method stub 25 setVisible(false); 26 } 27 28 }); 29 } 30 31 public static void main(String[] args) { 32 // TODO Auto-generated method stub 33 HelloFrame hf = new HelloFrame(); 34 hf.setTitle("四则运算程序"); 35 hf.setLocationRelativeTo(null); 36 hf.setDefaultCloseOperation(EXIT_ON_CLOSE); 37 hf.setVisible(true); 38 hf.setBounds(300, 200, 640, 410); 39 // 设置显示位置距离左边300像素距离上边200像素及屏幕大小500*400 40 } 41 42 static class Hello extends JPanel { 43 JButton make = new JButton("出题"); 44 JButton ans = new JButton("做题"); 45 JLabel title = new JLabel("欢迎使用四则运算软件,您是要:"); 46 JLabel l = new JLabel(); 47 private MakeExp makeexp = new MakeExp(); 48 49 public Hello() { 50 51 Icon icon = new ImageIcon("3.jpg"); // 在此直接创建对象 52 l.setIcon(icon); 53 add(l); 54 55 make.setSize(150, 50); 56 make.setLocation(250, 150); 57 make.setFont(new java.awt.Font("Dialog", 1, 20)); 58 make.setToolTipText("点我可以生成含四则远算算式的文件哦!"); 59 l.add(make); 60 61 ans.setSize(150, 50); 62 ans.setLocation(250, 250); 63 ans.setFont(new java.awt.Font("Dialog", 1, 20)); 64 ans.setToolTipText("点我可以上传文件做题,还可以记录成绩哦!"); 65 l.add(ans); 66 67 title.setFont(new java.awt.Font("Dialog", 1, 30)); 68 title.setSize(640, 100); 69 title.setLocation(80, -15); 70 l.add(title); 71 72 make.addActionListener(new ActionListener() { 73 74 @Override 75 public void actionPerformed(ActionEvent e) { 76 // TODO Auto-generated method stub 77 setVisible(false); 78 makeexp.setTitle("请输入要求"); 79 makeexp.setLocationRelativeTo(null); 80 makeexp.setDefaultCloseOperation(EXIT_ON_CLOSE); 81 makeexp.setVisible(true); 82 makeexp.setBounds(300, 200, 640, 410); 83 84 } 85 86 }); 87 88 ans.addActionListener(new ActionListener() { 89 90 @Override 91 public void actionPerformed(ActionEvent e) { 92 // TODO Auto-generated method stub 93 Count2_1 c = new Count2_1(); 94 c.setVisible(true); 95 c.setDefaultCloseOperation(EXIT_ON_CLOSE); 96 } 97 98 }); 99 } 100 } 101 }
参数错误提示页和出题完成提示页使用了同种方式来生成用户界面,都是在构造函数中接收字符串,然后将字符串放入JLabel中,再添加到JFrame中,这里以参数错误提示页为例来展示这种方式:
1 static class Waring extends JFrame{ 2 public Waring(ArrayList<String> a){ 3 setLayout(new GridLayout(6,1,15,15)); 4 for(String s:a){ 5 JLabel j = new JLabel(s); 6 add(j); 7 } 8 setSize(500,400); 9 setLocationRelativeTo(null); 10 setTitle("通知"); 11 } 12 }
界面模块与计算模块的对接
(UI模块的设计详见上一部分,这里主要讲述 )
我将我要完成的功能分为两部分,收集用户的意愿(即参数)和到相应的方法中执行,在UI模块中我主要就是通过GUI组件和相关事件来获取用户的意愿,并通过判断参数内容来决定接下来要调用的函数,而计算模块主要就是被调用,接收参数并进行相关的计算和生成功能。两部分各司其职,互不干扰。
以下为本项目的主要功能和截图:
【导航页】
【获得出题参数页】
【参数错误提示页】
【上传题目做题页】
【上传文件格式错误提醒】
【获得最好成绩功能】
结对过程
在这次结对项目过程中我和陈琦在工作室中坐在一起进行结对编程,体验了全新的一个人指导一个人实际操作的开发方式~
关于结对编程
结对编程作为最近流行的编程方式,有不少的优点。第一,结对编程的方式是两名程序开发人员一个扮演驾驶者的角色,进行代码编写,另一个扮演观察员的角色,对代码进行评测。这就使得代码的正确率大大提高,增强代码的质量。第二, 结对编程会使两个人都会发现自己平时没有注意到的地方,相互讨论,可以更快更有效地解决问题和发现自身的不足,从而提高了双方的编码能力。第三,和团队编程相比,结对编程只有两人,想法交流更加迅速,编写速度更快,编程效率更高。
然而,在面对一些问题时,双方可能有不同的看法,容易产生分歧,造成不必要的时间内耗。如果两名开发人员的能力相差过大,可能造成想法上沟通不畅发生争执,不利于团队和谐,或者造成代码基本都是由一方完成,而另一方就是享受成果的局面。
在本次编程中开始由于我们两人从未接触过这种编程方式,所以不熟悉各自的职责,导致了一些小的问题,但是经过一段时间的磨合,我们的配合逐渐合拍了起来,渐渐也体会到了结对编程的好处,就是编程完成后两个人就都理解了代码,不会像之前一样只理解自己写的代码,同时也省去了将两个人代码整合的步骤;而且在编程过程中对于一些问题两个人可以得出更好的结论,就直接优化了代码,减少了后续的工作量;两个人一起编程也可以相互监督,减少开小差的几率,提高工作效率。
以下是我们的优缺点:
陈琦 | 吕晓真 | |
优点 |
注重细节,善于优化程序的代码 |
规划项目进度,思路清晰 |
对代码的理解能力强 |
代码规范,格式简洁,便于阅读 |
|
态度积极,不懒散 |
细心,可以发现程序中隐藏的问题 |
|
缺点 |
耐心欠佳,在解决不了问题时会变得急躁 |
算法欠佳,有时会导致代码过长 |
PSP
PSP2.1 |
任务内容 |
计划共完成需要的时间(min) |
实际完成需要的时间(min) |
Planning |
计划 |
30 |
60 |
· Estimate |
· 估计这个任务需要多少时间,并规划大致工作步骤 |
30 |
60 |
Development |
开发 |
1000 |
3080 |
· Analysis |
· 需求分析 (包括学习新技术) |
60 |
120 |
· Design Spec |
· 生成设计文档 |
30 |
30 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
30 |
20 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 |
10 |
· Design |
· 具体设计 |
30 |
40 |
· Coding |
· 具体编码 |
720 |
2500 |
· Code Review |
· 代码复审 |
60 |
210 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
60 |
150 |
Reporting |
报告 |
55 |
100 |
· Test Report |
· 测试报告 |
30 |
60 |
· Size Measurement |
· 计算工作量 |
10 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
15 |
20 |
后记
这篇博客我也写了很久,在写博客期间我又从头到尾回顾了我的整个项目,重新捋了一遍我的思路,从PSP上也感触颇多,实际编码的时间总是大大超过预计,自我测试时间也是常常翻倍,这就还需要增强自己的代码能力,而且在开始做之前就先想好比较完善的方案,考虑到比较多的情况,就是说不要小看编码前的设计准备工作,要认真做,这样在自我测试阶段才能不会出现太多需要修改的地方,为整个项目进度提速。