目录:
一.前言(包括仓库地址等项目信息)
二.开始前PSP展示
三.结对编程对接口的设计
四.计算模块接口的设计与实现过程
五.计算模块接口部分的性能改进
六.计算模块部分单元测试展示
七.计算模块部分异常处理说明
八.界面模块的详细设计过程
九.界面模块与计算模块的对接
十.结对过程的描述
十一.结对编程的优缺点
十二.完成后实际的PSP
一、 前言
仓库地址:
https://git.coding.net/wanghz499/2016012032partnerWork.git
可测试的url地址:
http://39.105.6.214/myWeb_war/
在这里我要发自真心的说一说我的结对伙伴——王慧珍同学。两个人合作,更多的是需要时间去沟通,去磨合,但是因为我个人的原因,使得我们组讨论,合作的时间变得更少。可是慧珍没有因此懈怠,而且理解、支持她的队友,让我觉得很感动。在这个过程中,我觉得我们不只是合作伙伴的关系,很多方面我要向我的队友学习,不仅仅是专业技能。相信有这样的队友,会让我不断进步,也希望我持续的努力,能让我们团队变得更加强大。那这次结对项目,我们做的是网页版的四则运算系统,实现了作业中的基本要求,并且可供多个用户使用,同时记录多个用户的做题记录与最好成绩。
二、 十二PSP
PSP |
任务内容 |
计划时间(min) |
完成时间(min) |
Planning |
计划 |
40 |
60 |
Estimate |
估计这个任务需要多少时间,并规划大致工作步骤 |
30 |
70 |
Development |
开发 |
30*60 |
50*60 |
Analysis |
需求分析 |
60 |
45 |
Design Spec |
生成文档 |
0 |
0 |
Design Review |
设计复审 |
60 |
80 |
Coding Standard |
代码规范 |
10 |
20 |
Design |
具体设计 |
10*60 |
13*60 |
Coding |
具体编码 |
20*60 |
40*60 |
Code Review |
代码复审 |
40 |
60 |
Test |
测试 |
40 |
70 |
Reporting |
报告 |
4*60 |
8*60 |
Test Report |
测试报告 |
0 |
0 |
Size Measurement |
计算工作量 |
30 |
60 |
Postmortem& ProcessImprovement Plan |
事后总结, 并提出过程改进计划 |
60 |
2*60 |
三、 接口设计
1、Information Hiding
信息隐藏:指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。
那么在这次四则运算的过程中,我们也体现出了Information Hiding 这点。对于用户来说,出题模块和计算模块里比如存储运算符的map容器、存储题目的容器、文件的获取等都是向外隐藏的,不可随意访问修改。并且像习题的具体属性等都采用java的private 关键字修饰,只向外提供方法,这都做到了信息隐藏。例如:
public class Practice { private int id; private String wrongProblem; private String correctAnswer; private String contentUser; private String filePath; private String createType; private String when; private int correctCount; private int wrongCount; private String timeLong; private int userId;
public class Problem { private int id; private String content;
2、Interface Design
接口设计:面向接口编程是常用的编程手段,这次在做的过程中所设计的接口为出题和计算模块,并且打包成jar包,增强了程序的可维护性。
3、Loose coupling
松耦合度:耦合的强度依赖于:(1)一个模块对另一个模块的调用;(2)一个模块向另一个模块传递的数据量;(3)一个模块施加到另一个模块的控制的多少;(4)模块之间接口的复杂程度。等等。为了降低耦合度,便将各大模块进行了封装,这样相对比较独立
四、 计算模块接口的设计与实现过程
计算模块接口一共设计了两个类:Creat类和Calculator类,分别产生符合条件的运算式和对运算式进行运算。
Creat类:产生符合条件的算式数组,产生单条算式的方法(包括操作符的个数,是否加括号等),判断式子是否符合要求,产生操作符的下标数组。
Calculator类:对产生的式子进行计算。在此涉及到运算符的优先级判断以及堆栈,其中关键的算法是中缀表达式转后缀表达式和计算后缀表达式的值。大致思路为结合调度场算法和逆波兰表达式求值:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号则栈顶元素一次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。再从左到右遍历后缀表达式的每个数字和字符,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈进行运算,运算结果进栈,一直到最终获得结果。
具体计算类函数间关系图如下:
所有类如下:
五、计算模块接口部分的性能改进
起初的计算模块只是稍微改了一下第一次个人作业的代码,多接收了几个参数而已。后来发现当限制的数值范围比较小的时候,控制台报栈溢出异常。去网上搜才发现是递归调用过度,线程已满导致程序崩溃。后来检查代码的确好几处都用了递归,比如当前生成的式子不满足条件时就再递归调用生成式子的createProblem方法,再如运算符下标数组只要全部一样(即式子的运算符全一样),也会再递归调用index()方法重新生成一个下标数组,总之多处用到了递归。当数值范围比较小时,生成的式子大多是不满足条件的,于是会频频递归产生新式子,当运算符个数比较少时,下标数组也很容易一样,会频繁递归调用index()方法,最终导致程序跑不了。
在知道是递归调用过度的原因后,根据报错信息得知index()方法是程序中消耗最大的函数,于是修改了index()方法,使其不使用递归。修改思路:当下标数组的前n-1个都一样时,第n个一定与前n个不一样,这样就保证了下标数组至少有2个不同,即保证了一条式子至少有2中运算符。经过这次栈溢出异常后,理解了递归要慎用,虽然它简单,但它及可能会拖慢程序速度或使程序崩溃,我也是第一次意识到代码性能分析的重要性。下面是效能分析的图。
六、计算模块部分单元测试展示
单元测试思路:
Creat类负责接收Command类传来的各种条件参数,产生符合的题目数组。测试的函数有generate(),createProblem(),index(),构造测试数据时主要是按照函数的参数列表,根据参数个数和类型构造合适的测试数据。起初代码覆盖率只有70%多,原因是很多if语句没被执行,后来我又构造满足if判断条件的参数进行测试,代码覆盖率便逐步提升。最终我把各种if语句的条件都测试到了,代码覆盖率达到了100%:
具体代码和覆盖率如下:
import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class CreateTest { private Create create; @Before public void setUp() throws Exception { create = new Create(); } @Test public void generate() throws Exception { create.generate(10,10,100,3,true,true); create.generate(10,100,10,12,true,true); create.generate(10,10,1000,12,true,true); create.generate(10,1,10000,8,true,true); create.generate(10,101,1000,8,true,true); create.generate(0,1,1000,8,true,true); create.generate(10,1,1000,8,true,true); create.generate(10,1,1000,8,false,true); create.generate(10,1,1000,8,true,false); create.generate(10,1,1000,8,false,false); } @Test public void createProblem() throws Exception { create.generate(10,1,1000,8,true,true); create.generate(10,1,1000,8,false,true); create.generate(10,1,1000,8,true,false); create.generate(10,1,1000,8,false,false); } @Test public void index() throws Exception { create.index(3,4); } }
七、计算模块部分异常处理说明
1.通过case语句对异常进行处理。设置两个参数m、n,判断是否有-m,-n的输入。
catch (ArrayIndexOutOfBoundsException e) { //未输入数字时args[i+1]会数组越界 System.out.println("未输入题目数量!"); return;
catch (ArrayIndexOutOfBoundsException e) { System.out.println("未分别输入数值上下界!");
单元测试:
String[] args1 = {"-o"}; new Command(); Command.main(args1);
2.命令行输入了-m,-n,但是超出了用户参数中对题目数量和题目数值上下界的设定,便提醒重新输入。
case "-n": { try { n = Integer.parseInt(args[i + 1]); if (n < 1 || n > 10000) { System.out.println("对不起,题目数量只能是1-10000!"); return; //结束运行 } } catch (ArrayIndexOutOfBoundsException e) { //未输入数字时args[i+1]会数组越界 System.out.println("未输入题目数量!"); return; }catch (NumberFormatException e) { //输入非数字字符等 System.out.println("对不起,题目数量只允许输入1-10000的数字!"); return; //结束运行 } break; }
单元测试:
String[] args6 = {"-m","-3","1"}; new Command(); Command.main(args6);
3.参数格式有误
if (n == 0 && downBound == 0 && upBound == 0) { System.out.println("参数格式有误!"); return; } else if (n == 0) { System.out.println("未输入题目数量!"); return; } else if (downBound == 0 || upBound == 0) { System.out.println("未分别输入数值上下界!"); return;
单元测试:
String[] args7={"-n","b","-m"}; new Command(); Command.main(args7);
4.出题后,写入文件出错或者文件已存在。
try{ File file = new File("../result.txt"); if (file.exists()) { //如果文件已存在,则删除文件 file.delete(); } if(file.createNewFile()){ FileOutputStream txtfile = new FileOutputStream(file); PrintStream p = new PrintStream(txtfile); for (String s : a) { int index = s.indexOf("="); p.println(s.substring(0,index)); } txtfile.close(); p.close(); System.out.println("文件创建成功!");
八、界面模块的详细设计过程
根据作业要求来说,我们大致设计了4个板块,出题大厅,历史答题,用户上传,密码修改。
在出题大厅这个板块,由系统自动生成题目,题目文件生成以后,用户需点击下载,下载完成以后便可以开始答题,进行计时。答完题提交以后由后台进行判断,显示错题,并给出正确答案及答对数量。
历史答题这个板块可以看见每次的做题时间,总练习数以及历史最好成绩。点击时间便可以跳转到另一个详情页面,可以看见本次答题情况,正误的题目、数量、用时,以及错题卡片。
用户上传这个板块,用户点击上传便可浏览自己的电脑进行选择想要上传的文件,只不过只支持utf-8编码的.txt文件。上传之后便可进行答题。
修改密码板块,用户可对密码进行修改,修改完毕以后又跳回主页面。
以下载题目为例:
前端页面:
<%-- Created by IntelliJ IDEA. User: dengx Date: 2018/4/5 Time: 16:32 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>下载题目</title> <link rel="stylesheet" href="../../../css/style.css" media="screen" type="text/css" /> </head> <body style="background: url(../../../image/bg3.jpg);background-size: cover;"> <%--<div style="text-align:center;clear:both;">--%> <%--<script src="/gg_bd_ad_720x90-2.js" type="text/javascript"></script>--%> <%--<script src="/follow.js" type="text/javascript"></script>--%> <%--</div>--%> <ul class="left"> <li class="active"><a href="${pageContext.request.contextPath}/practice?state=main"><font style="font-size: 14pt">出题大厅</font></a></li> <li><a href="${pageContext.request.contextPath}/practice?state=history"><font style="font-size: 14pt">历史答题</font></a></li> <li><a href="${pageContext.request.contextPath}/practice?state=toUpload"><font style="font-size: 14pt">上传题目</font></a> </li> <li><a href="${pageContext.request.contextPath}/user?state=toUpdatePassword"><font style="font-size: 14pt">修改密码</font></a></li> <li><a href="${pageContext.request.contextPath}/user?state=exit"><font style="font-size: 14pt">退出账号</font></a></li> </ul> <center style="margin-top: 30px;"> <div class="content"> <br><br><br><br><br> 题目文件已生成,<a href="${pageContext.request.contextPath}/practice?state=download&filepath=${filepath}"><button class="btn orange">点击下载</button></a><br> 下载完后您可以,<a href="${pageContext.request.contextPath}/practice?state=begin&n=${n}"><button class="btn orange">开始答题</button></a> </div> </center> </body> </html>
生成题目的servlet:
/** * 下载题目文件 * @param request * @param response * @return * @throws ServletException * @throws IOException */ private HttpServletResponse download(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException { try { String filepath = request.getParameter("filepath"); File file = new File(filepath); String filename = file.getName(); // 获得文件名 String ext = filename.substring(filename.lastIndexOf(".") + 1).toUpperCase();// 获得文件的后缀名 // 以流的形式下载文件。 InputStream fis = new BufferedInputStream(new FileInputStream(filepath)); byte[] buffer = new byte[fis.available()]; fis.read(buffer); fis.close(); // 清空response response.reset(); // 设置response的Header response.addHeader("Content-Disposition", "attachment;filename=" + new String("result.txt".getBytes())); response.addHeader("Content-Length", "" + file.length()); OutputStream toClient = new BufferedOutputStream( response.getOutputStream()); response.setContentType("application/octet-stream"); toClient.write(buffer); toClient.flush(); toClient.close(); } catch (IOException ex) { ex.printStackTrace(); } return response; }
九、界面模块与计算模块的对接
在界面与计算模块的链接中,前台向后台发送request请求,后台由servlet接受前台请求做出响应response。在网页版中,对接主要体现在jsp页面和servlet页面的交互和信息传递,后台从jsp页面获取到需要的参数,在servlet页面对获取到的参数进行处理,并调用计算模块的方法实现相应功能。
servlet计算模块:
/** * 生成题目 * @param request * @param response * @throws ServletException * @throws IOException */ private void create(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException { int n = Integer.parseInt(request.getParameter("n")); int downBound = Integer.parseInt(request.getParameter("downBound")); int upBound = Integer.parseInt(request.getParameter("upBound")); int MulDiv = Integer.parseInt(request.getParameter("hasMulDiv")); int Bracket = Integer.parseInt(request.getParameter("hasBracket")); int largeOperatorCount = Integer.parseInt(request.getParameter("largeOperatorCount")); RequestDispatcher rd; if(downBound>=upBound){ request.setAttribute("msg","数值下界不能大于上界!"); rd = request.getRequestDispatcher(WebContents.MAIN); rd.forward(request,response); } boolean hasBracket = false; boolean hasMulDiv = false; if(Bracket==1){ hasBracket=true; } if(MulDiv==1){ hasMulDiv=true; } //将生成的result.txt放在web/file下 Create create = new Create(); String[] result = create.generate(n,downBound,upBound,largeOperatorCount,hasMulDiv,hasBracket); //将题目(不含答案)存入session String[] question = new String[n]; for(int i=0;i<n;i++){ int index = result[i].indexOf("="); question[i]=result[i].substring(0,index); } request.getSession().setAttribute("question",question); //将正确答案存入session String[] realAnswer = new String[n]; for(int i=0;i<n;i++){ int index = result[i].indexOf("="); realAnswer[i]=result[i].substring(index+1); } request.getSession().setAttribute("realAnswer",realAnswer); MakeFile2 makeFile2 = new MakeFile2(); String path = request.getSession().getServletContext().getRealPath("file")+"/"; File f = new File(path); //文件夹也是个文件 if(!f.exists()){ f.mkdirs(); //如果该文件夹不存在,则创建该文件夹 } String fileName = UUID.randomUUID()+"result.txt"; File file = makeFile2.creatFile(result,path+fileName); //将该练习存入数据库 String relativePath = "../../../file/"+fileName; User user = (User) request.getSession().getAttribute("user"); int userId = user.getId(); String creatType = "系统产生"; PracticeDao practiceDao = new PracticeDaoImpl(); Practice practice = new Practice(relativePath,creatType,userId); practiceDao.insertPractice(practice); Practice practice1 = practiceDao.selectPracticeByPath(relativePath); request.getSession().setAttribute("practiceId",practice1.getId()); request.setAttribute("filepath",path+fileName); request.getSession().setAttribute("file",file); request.setAttribute("n",n); rd = request.getRequestDispatcher(WebContents.DOWNLOAD); rd.forward(request,response); }
界面模块:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%-- Created by IntelliJ IDEA. User: dengx Date: 2017/12/27 Time: 9:53 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>出题大厅</title> <link rel="stylesheet" href="../../../css/style.css" media="screen" type="text/css" /> </head> <body style="background: url(../../../image/bg3.jpg);background-size: cover;"> <%--<div style="text-align:center;clear:both;">--%> <%--<script src="/gg_bd_ad_720x90-2.js" type="text/javascript"></script>--%> <%--<script src="/follow.js" type="text/javascript"></script>--%> <%--</div>--%> <ul class="left"> <li class="active"><a href="${pageContext.request.contextPath}/practice?state=main"><font style="font-size: 14pt">出题大厅</font></a></li> <li><a href="${pageContext.request.contextPath}/practice?state=history"><font style="font-size: 14pt">历史答题</font></a></li> <li><a href="${pageContext.request.contextPath}/practice?state=toUpload"><font style="font-size: 14pt">上传题目</font></a> </li> <li><a href="${pageContext.request.contextPath}/user?state=toUpdatePassword"><font style="font-size: 14pt">修改密码</font></a></li> <li><a href="${pageContext.request.contextPath}/user?state=exit"><font style="font-size: 14pt">退出账号</font></a></li> </ul> <center style="margin-top: 30px;"> <div class="content"> <%--<font style="font-size: 25pt; filter: shadow(color=#9370db); width: 100%; color: whitesmoke; line-height: 150%; font-family: 幼圆">出题大厅</font>--%> <%--<HR style="FILTER: progid:DXImageTransform.Microsoft.Shadow(color:#987cb9,direction:145,strength:15)" width="80%" color=darkgrey SIZE=2>--%> <br><br><br> <div style="margin-left: -500px;font-size: 15pt;font-family: 幼圆">嗨~${user.username}!一起来做题吧!</div> <form action="${pageContext.request.contextPath}/practice?state=create" method="post"> <table style="margin-top: 30px;border:1px solid #3b4249; padding:20px;box-shadow:10px 10px 5px #888888;"> <tr><td >题目数量:</td><td><input type="number" min="1" max="10000" name="n" style="border: 1px solid #3b4249;background-color: transparent;width: 156px;" placeholder="1-10000之间"/></td></tr> <tr><td>数值范围:</td><td><input placeholder="1-100" type="number" min="1" max="100" name="downBound" style="border: 1px solid #3b4249;background-color: transparent;width: 65px;"/> ~ <input placeholder="50-1000" type="number" min="50" max="1000" name="upBound" style="border: 1px solid #3b4249;background-color: transparent;width: 70px;"/></td></tr> <tr><td>是否有乘除:</td><td>是<input type="radio" value="1" name="hasMulDiv" /> 否<input type="radio" value="0" name="hasMulDiv" checked="checked" /></td></tr> <tr><td>是否有括号:</td><td>是<input type="radio" value="1" name="hasBracket" /> 否<input type="radio" value="0" name="hasBracket" checked="checked"/></td></tr> <tr><td>允许最大运算符数:</td><td><select name="largeOperatorCount"> <option value="1" selected="selected">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> <option value="8">8</option> <option value="9">9</option> <option value="10">10</option> </select></td></tr> </table> <button class="btn orange">开始出题</button> </form> ${msg} </div> </center> <%--<script type="text/javascript">--%> <%--var text = document.getElementById("a");--%> <%--text.onkeyup = function(){--%> <%--this.value=this.value.replace(/\D/g,'');--%> <%--if(text.value>100){--%> <%--alert("a");--%> <%--}--%> <%--}--%> <%--</script>--%> </body> </html>
功能展示:
出题界面:
文件下载:
答题界面:
历史答题:
文件上传:
十、结对过程的描述
因为我前一阵忙着其他事情要离开学校,团队项目还交的更急,所以在离开学习学校的前几天带着团队抓紧完成了团队的第一次博客,后一天一大早就要出发了,前一天还战斗到凌晨两点,真的是很佩服我自己了。所以呢,这次结对项目,真的要感谢我的搭档慧珍,帮我分担了很多。不仅仅是我的好伙伴,一次合作,更让我佩服,向优秀的慧珍看齐。面谈变成了网谈,但是她却在耐心的沟通交流,认认真真的在为我们的项目努力,让我感触颇深。
照片展示:
十一、结对编程的优缺点
1.结对编程的优缺点
优点: 两个人合作,沟通是必不可少的,所以会锻炼合作双方的交流能力,及时应变的能力。
俗话说多一个人多一双眼,结对可有效的减少bug,增加代码的质量。
两个人互相讨论,互相带动,也能互相学习,一起进步。
缺点: 每个人工作风格和性格几乎都不同,所以很容易产生摩擦,矛盾。
如果其中一个人消极懈怠,那么很可能会影响队友,从而拉低团队的效率。
容易产生依赖思想,那么就没有得到锻炼。
2.结对个人优缺点
王慧珍 优点(1)做事非常认真,很有责任感。
(2)专业能力过硬,能带动身边的人,很正能量,
(3)做事不拖沓,很踏实,可靠。
缺点 可能在平常生活中还需要多多表达自己的想法,让更多人感受到自己的魅力、
邓旭 优点(1)比较开朗活泼,做什么事比较好商量。
(2)有上进心,向优秀看齐。
(3)能为他人着想,会换位思考。
缺点 有时候不够主动,做事不够干脆,韧劲不够。
来源:https://www.cnblogs.com/dengxu/p/8765712.html