一、仓库地址:https://git.coding.net/q834934034/Project.git
二、PSP表格:
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
Planning |
计划 |
60 |
· Estimate |
· 估计这个任务需要多少时间 |
50 |
Development |
开发 |
1050 |
· Analysis |
· 需求分析 (包括学习新技术) |
60 |
· Design Spec |
· 生成设计文档 |
40 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
30 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
· Design |
· 具体设计 |
60 |
· Coding |
· 具体编码 |
660 |
· Code Review |
· 代码复审 |
60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 |
Reporting |
报告 |
120 |
· Test Report |
· 测试报告 |
60 |
· Size Measurement |
· 计算工作量 |
30 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
三、接口设计
对于所问的接口设计,我并不知道它是什么,于是我通过看书和查阅资料,对于问题中提到的三个词汇做出解释:
1.Information Hiding(信息隐藏)
即信息隐藏原则:是指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。
为什么要信息隐藏:
-
隐藏复杂度:这样你就不用再去应付它,除非你要特别关注的时候;
-
隐藏变化源:这样当变化发生时,其影响就能被限制在局部范围内。复杂度的根源包括复杂的数据类型、文件结构、布尔判断以及晦涩的算法等等。
信息隐藏的特点:(1)不可感知性(2)鲁棒性(反映了信息隐藏技术的抗干扰能力)(3)隐藏容量(应用于隐蔽通信中时,为了提高通信的效率)
2. Interface Design(接口设计)
接口的特点
-
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
-
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
-
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
3.Loose Coupling(松耦合)
松耦合就是降低系统部件和部件之间的耦合性,也就是降低部件的依赖性,使得部件之间相对独立,这样对于日后系统的维护及扩展都是很有好处的。在J2EE中可以使用接口来降低程序的耦合度,因为多态的特性,父类或者接口的引用是可以接受子类对象的。
这里我因为做的是网页版,所以只是利用外部调用方法和实现接口,由内部封装好算法,外部提供接口,一个常见的MVC模型,模型、视图和业务逻辑分块进行,互不影响。
四、计算模块接口的设计和实现过程。
接口的设计和实现过程主要涉及到3个类,一个接口,10个函数和一个主函数。
Command类:包含主函数,生成四则运算模块的入口。通过该类从后台获取参数要求,并进行判断参数是否符合要求,若符合,则将通过调用接口产生符合要求的运算式。若不符合要求,则抛出异常,给出提示重新输入;
IsRight类:将从后台输入的自妇产进行解析,看是否符合生成运算式的要求,若符合,则将符合的参数返回到Command类,若不符合,则将提示参数不符合要求,重新输入;
Algorithm接口:主要包含生成带括号的运算式和不带括号的运算式两个函数,还有在生成运算式过程中会用到的判断符号优先级和将代表运算符的数字转换为相应的字符的函数(0-"+",1-"-",2-"*",3-"÷");
AlgorithmImpl类:Algorithm接口的具体实现。
类之间的关系如下:
这里的关系图和接口实现是工作室其他同学的,我借鉴过来的。
五、计算模块接口部分的性能改进
我在运算模块的性能分析过程中,大约花费了5小时,首先我自己因为不了解,所以用了接近一半的时间向工作室的其他同学请教,并且观察他们做性能改进的过程,经过性能分析知道了我有些资源在使用结束后是没有回收的,经过排查后,我发现了是在写入文件后没有关闭输出流,导致了资源没有完全回收。发现这一问题后,我针对它进行了改进,关闭了输出流。使项目的性能得到了提高。在项目中消耗最大的函数isParenthesis和noParenthesis两个方法,由于限制条件太多,所以当题目数量过大时,函数消耗会很大。在分析过程中发现在运行过程中,没有对循环时产生的list清空,所以浪费很大空间,针对这些问题,我们也对代码进行了修改。以下是性能改进截图:
六、单元测试
单元测试主要分为Algorithm接口的实现AlogriyhmImpl类中的方法测试和IsRight类中的方法测试
AlgorithmTest类部分代码:
1 @Test 2 public void algorithm() throws Exception { 3 Algorithm algorithm =new AlgorithmImpl(); 4 for(int i =0;i<12;i++){ 5 System.out.println(algorithm.algorithm(1,100,1,1)); 6 System.out.println(algorithm.algorithm(10,200,2,1)); 7 System.out.println(algorithm.algorithm(5,200,4,1)); 8 System.out.println(algorithm.algorithm(5,100,7,1)); 9 System.out.println(algorithm.algorithm(10,200,1,2)); 10 System.out.println(algorithm.algorithm(10,100,2,2)); 11 System.out.println(algorithm.algorithm(1,100,4,2)); 12 } 13 } 14 15 @Test 16 public void bracketsAlgo() throws Exception { 17 Algorithm algorithm =new AlgorithmImpl(); 18 19 for(int i =0;i<10;i++){ 20 System.out.println(algorithm.BracketsAlgo(10,100,1,1)); 21 System.out.println(algorithm.BracketsAlgo(1,100,5,1)); 22 System.out.println(algorithm.BracketsAlgo(10,100,1,2)); 23 System.out.println(algorithm.BracketsAlgo(5,200,4,2)); 24 } 25 }
IsRightTest测试类中的部分代码:
1 @Test 2 public void cTest() throws Exception { 3 IsRight isRight = new IsRight(); 4 String s[]={ "-n","12", "-m","1","100","-c","-o"}; 5 String s1[]={ "-n","12", "-m","1","100"}; 6 System.out.print(isRight.cTest(s)); 7 System.out.print(isRight.cTest(s1)); 8 } 9 10 @Test 11 public void mTest() throws Exception { 12 IsRight isRight = new IsRight(); 13 String s[]={ "-n","12", "-m","1","100","-c","-o"}; 14 String s1[]={ "-n","12", "-m","1","100"}; 15 String s2[]={ "-n","12", "-m","1","10000"}; 16 String s3[]={ "-n","12", "-m","333","100"}; 17 String s4[]={ "-n","12", "-m","60","100"}; 18 String s5[]={ "-n","12", "-m","1","2"}; 19 System.out.print(isRight.mTest(s)); 20 System.out.print(isRight.mTest(s1)); 21 System.out.print(isRight.mTest(s2)); 22 System.out.print(isRight.mTest(s3)); 23 System.out.print(isRight.mTest(s4)); 24 System.out.print(isRight.mTest(s5)); 25 }
构造测试数据的思路:尽可能多的把可能出现得情况列举出来,提高代码覆盖率。又因为产生的符号的随机数都是随机的,因此应该在测试时多测试几次,降低偶然性。
单元测试的测试覆盖率截图:
七、模块部分异常处理
在计算模块中,用户如果传入不合法参数,程序会捕获异常,显示异常类型并提示错误参数。由于我们的项目是用web实现的,对于不合法的参数(如汉字,符号)以及参数为空这些问题都会在前端进行判断,并提醒用户,所以在异常处理的得上设计中,只做了参数范围不合法一种异常类型,由于题目会有上下界的要求,但当上下界范围较小时,无法产生题目,因此我们设定当上下界只差小于30时,抛出异常,提示用户扩大范围。
当符号数量不在合理范围内时:
1 @Test 2 public void createCharacter() throws Exception { 3 4 System.out.println(createArithmetic.CreateCharacter(56,true)); 5 6 }
当上下界不在合理范围时:
1 @Test 2 public void createNumber() throws Exception { 3 System.out.println(createArithmetic.CreateNumber(4,-9,800)); 4 }
八、界面的设计
界面这一块是我自己写的,很简洁,没有什么花哨;
先是一个主页面,订制需求,在这个页面输入参数来生成题目;
再是一个上传文件的页面,这里会有一个提示只能上传.txt文件,上传成功页面会自动跳转到成功页面并询问你是否开始答题;
答题页面很普通没什么可说的,当你完成答题点击终止答题按钮的时候,会跳转到显示结果的页面,这里会把你做的题以及正确答案显示出来,并告诉你用时和正确率;
还有一个提示页面,即你上传文件格式错误或者生成题目不在设计范围内则会转到错误提示的页面,可以返回重新选择。
以上就是界面的设计。一下是部分代码:
<body>
<div class="main">
<form>
<p>请输入生成的题目数量:
<input type="number" name="" id="number" />
</p>
<p>请输入算式数值范围:
<input type="number" name="" id="range" /> ~
<input type="number" name="" id="range" />
</p>
<p>请输入题中最多包含的运算符数:
<input type="number" name="" id="" />
</p>
<p>题中是否包含乘除法:
是<input type="radio" name="check" id="" />
否<input type="radio" name="check" id="" />
</p>
<p>题中是否包含括号:
是<input type="radio" name="braket" id="" />
否<input type="radio" name="braket" id="" />
</p>
<button id="creat">点击生成题目文件</button>
<button id="upload"><a href="submit.html" id="a">上传文件</a></button>
</form>
</div>
</body>
/*背景*/
body{
background-image: url(../img/picture1.jpg);
}
/*主体样式*/
.main{
border: 1px solid #E0FFFF;
margin: 0 auto;
520px;
height: 350px;
}
/*表单样式*/
form{
position: relative;
top: 60px;
left: 10px;
}
/*取值范围框的大小*/
#range{
50px;
}
/*生成文件按钮位置*/
#creat{
position: relative;
top: 20px;
left: 75px;
}
/*生成文件按钮位置*/
#upload{
position: relative;
top: 20px;
left: 130px;
}
/*上传跳转链接样式*/
a:link,a:visited{
text-decoration: none;
color: black;
}
九、界面模块与计算模块的对接
%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%> <html> <head> <title>生成题目</title> </head> <center> <body style="margin-top:13%;margin-left:1%;background:url(../../img/bg.jpg)no-repeat ;background-position-x:55%;background-position-y:-10%;padding-top: 20px;font-size: 20px;"> <font style="color:#1e90ff" size="3">${msg}</font> <form method="post" action="${pageContext.request.contextPath}/arithmetic?state=download&path=${path}"> <c:if test="${fileName != null}"> <input type="text" name="fileName" value="${fileName}" style="border-style:none; 60px"> <input type="submit" value="下载" /> </c:if> </form> <form name="create" method="post" onsubmit="return checkform()" action="${pageContext.request.contextPath}/arithmetic?state=create">
import myException.MyException; import util.Calculation; import util.CreateArithmetic; import util.Impl.CalculationImpl; import util.Impl.CreateArithmeticImpl; public class Command { public static void main(String [] args) throws Exception { CreateArithmetic arithmetic = new CreateArithmeticImpl(); int num = 0; boolean isParenthesis = false; boolean isMulAndDiv = false; int lownum = 0; int upnumber = 0; int chaNum = 1; String msg = ""; boolean flag = true; try { for(int i = 0; i < args.length ;i++){ switch (args[i]) { case "-n": if(args[i+1].matches("[0-9]+") && i < args.length-1){ num = Integer.parseInt(args[i+1]); i++; }else { msg = "参数类型错误"; flag = false; } break; case "-m": if((args[i+1].matches("[0-9]+")) && (args[i+2].matches("[0-9]+")) && i < args.length-2){ lownum = Integer.parseInt(args[i+1]); upnumber = Integer.parseInt(args[i+2]); i += 2; }else { msg = "参数类型错误"; flag = false; } break; case "-o": if((args[i+1].matches("[0-9]+")) && i < args.length-1){ chaNum = Integer.parseInt(args[i+1]); i++; }else { msg = "参数类型错误"; flag = false; } break; case "-c": isMulAndDiv = true; break; case "-b": isParenthesis = true; break; default: msg = " 输入参数不合法参数"; flag = false; } if(flag == false){ System.out.println(msg); break; } } if(num == 0 || lownum == 0 || upnumber == 0){ System.out.println("缺少参数"); }else { if(flag){ arithmetic.createFile(num,isParenthesis,isMulAndDiv,lownum,upnumber,chaNum); System.out.println("文件创建成功!"); } } }catch (MyException e){ System.out.println("参数范围不合法!"); } } }
十、结对过程
因为我和我的队友是室友,所以我们合作起来非常方便,在修改代码和BUG的时候也很有默契,能够做到优势互补;
十一、结对编程的优缺点
下面是一些结对编程的优点:
- 程序员互相帮助,互相教对方,可以得到能力上的互补。
- 可以让编程环境有效地贯彻Design。
- 增强代码和产品质量,并有效的减少BUG。
- 降低学习成本。一边编程,一边共享知识和经验,有效地在实践中进行学习。
- 在编程中,相互讨论,可能更快更有效地解决问题。
当然,结队编程也会有一些不好的地方:
- 对于有不同习惯的编程人员,可以在起工作会产生麻烦,甚至矛盾。
- 有时候,程序员们会对一个问题各执己见(代码风格可能会是引发技术人员口水战的地方),争吵不休,反而产生重大内耗。
- 两个人在一起工作可能会出现工作精力不能集中的情况。程序员可能会交谈一些与工作无关的事情,反而分散注意力,导致效率比单人更为低下。
- 结对编程可能让程序员们相互学习得更快。有些时候,学习对方的长外,可能会和程序员们在起滋生不良气氛一样快。比如,合伙应付工作,敷衍项目。
- 面对新手,有经验的老手可能会觉得非常的烦躁。不合适的沟通会导到团队的不和谐。
- 新手在面对有经验的老手时会显得非常的紧张和不安,甚至出现害怕焦虑的的精神状态,从而总是出现低级错误,而老手站在他们后面不停地指责他们导致他们更加紧张,出现恶性循环。最终导致项目进展效率低下,并且团队貌合神离。
- 有经验的人更喜欢单兵作战,找个人来站在他背后看着他可能会让他感到非常的不爽,最终导致编程时受到情绪影响,反而出现反作用。
我的优点:比较细心、代码能力较强、对问题的分析能力强
我的 缺点:有时容易沉不住气、遇到困难解决效率比较低
郑栋的优点:遇到难题能沉得住气,这也是和我互补的一个地方、协作能力很好、有耐心
郑栋的缺点:容易犯低级错误
十二、实际PSP表格
PSP |
任务内容 |
实际时间(min) |
Planning |
计划 |
50 |
Estimate |
估计这个任务需要多少时间,并规划大致工作步骤 |
30 |
Development |
开发 |
1610 |
Test |
测试 |
130 |
Algorithm Optimization |
算法优化 |
220 |
Interface Design |
接口设计 |
180 |
Coding Standard |
代码规范 |
60 |
Design |
具体设计 |
200 |
Coding |
具体编码 |
660 |
Code Review |
代码复审 |
60 |
Test |
测试 |
150 |
Reporting |
报告 |
120 |
Test Report |
测试报告 |
100 |
Size Measurement |
计算工作量 |
30 |
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
60 |
十三、总结
对于这次作业,更多的还是依赖于工作室的同学的帮助,这也反应了我和郑栋的技术还是太LOW,如果以后这样的作业经常有,我们总不能一直借鉴别人的代码,所以还是得提高自身的技术水平;
JAVA真的超级强大!虽然我的JAVA不好,但是我看了其他同学的博客,尤其是做的GUI形式的作业,我就感觉到JAVA的全面性,最后希望自己能够多主动一点去学习提高自身技术,希望在下次的
作业能全靠自己完成