这次的结对项目我做了很长时间,感触也很多。在这次项目中我使用了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 }