结对项目之小学生四则运算系统网页版项目报告
本篇博客结构一览:
一.前言(包括仓库地址等项目信息)
二.开始前PSP展示
三.结对编程对接口的设计
四.计算模块接口的设计与实现过程
五.计算模块接口部分的性能改进
六.计算模块部分单元测试展示
七.计算模块部分异常处理说明
八.界面模块的详细设计过程
九.界面模块与计算模块的对接
十.结对过程的描述
十一.结对编程的优缺点
十二.完成后实际的PSP
十三.项目总结与改进
一.前言
相比上次的个人项目,我觉得结对项目对于我来说一个更大的挑战。生活中大概经常有这样的情况,你以为一座山很高,自己根本无法看到山顶的风景,更无法翻越它,可是当以一步一步慢慢走,一点一点不放弃的缓缓前进时,你会发现,即使你走的很慢,你在不知不觉中已经走了很远很远,并且翻过了它。而当你翻越它时,你收获的也不仅仅是山顶的风景。这次结对编程就给我这样的感觉,收益颇多。当然,收获的远不止对代码的熟悉程度和更多的知识经验,还有结队过程中两人的合作与互帮互助精神,还有过程中对各种情况的应对和解决。
这次结对项目,我们制作的是网页版的四则运算系统。所以在结对的过程中,我们先一人负责前端(王文雨同学),一人负责后端(我)。然后再一起进行了两部分功能的对接与融合。以下是我们的一些项目信息:
代码仓库地址:https://git.coding.net/honey_xiaoxinxin/FreshCalculate.git
网页版可测试的URL地址:http://47.93.197.5:8080/FirstCalculate/index.jsp
项目代码说明:项目整体放在了Calculate文件夹下,里面包含命令行出题部分代码NewCalculate,以及网页版完整源代码(web文件夹下)。命令行测试入口为src下Command.java;在src下将其编译,即可输入数据进行测试运行。
二.开始前PSP展示
PSP |
任务内容 |
计划共完成需要的时间(min) |
Planning |
计划 |
20 |
Estimate |
估计这个任务需要多少时间,并规划大致工作步骤 |
10 |
Development |
开发 |
1200 |
Analysis |
需求分析 (包括学习新技术) |
60 |
Design Spec |
生成设计文档 |
20 |
Design Review |
设计复审 (和同事审核设计文档) |
10 |
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
10 |
Design |
具体设计 |
20 |
Coding |
具体编码 |
720 |
Code Review |
代码复审 |
30 |
Test |
测试(自我测试,修改代码,提交修改) |
330 |
Reporting |
报告 |
480 |
Test Report |
测试报告(包括博客) |
420 |
Size Measurement |
计算工作量 |
30 |
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
30 |
三.结对编程对接口的设计(
)1.首先我们明确了这些方法的具体含义:
(1)信息隐藏:信息隐藏指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。
(2)接口设计:面向接口编程是软件工程领域常用的设计手段
(3)松耦合度:基于面向接口编程,松耦合度是接口设计的目的,接口设计是松耦合度的实现手段
2.我们编程中的实现方法
(1)我们制作的是网页版,整体采用了MVC模型,M是模型层,V是视图层,C是业务逻辑层。在具体执行时,业务逻辑层并不需要知道方法内部是如何执行,实现的,只需要调用方法和实现接口就可以。从而实现了“将内部的算法实现封装起来,对外部只提供调用的接口,使得程序之间各个模块各司其职,互不影响”的效果。
(2)类的所有数据成员都是private,所有访问都是通过访问函数实现的。
private String studentId; private String perscore; private String avgtime; private String zttime; public String getStudentId() { return studentId; }
(3)进行了模块化编程,运算模块和界面模块相对较独立,尽量做到了低耦合度。
(4)人机交互逻辑集中到了一个单独的类中,方便以后的修改。
四.计算模块接口的设计与实现过程
1.类的组成
我的计算模块由两个类组成,一个是模块的入口类Command,另一个类即是生成符合命令行参数要求的算式题目的类,MakeQuestion类。
2.各个类的基本功能及包含的函数
在上次作业的基础上,按要求对这部分进行了相应的改进。
(1)Command类中包含了调用这部分的主函数,主要负责接收,判断和处理命令行输入的参数。实现了对是否输入了必要的参数-m,-n的判断;对每个参数范围是否符合要求的控制;对相应字母后是否有相应个数的参数的判断。并针对这些情况进行了合理的用户提示和异常的处理。
(2)MakeQuestion类中函数总览
a.实现命令行符合参数要求的最简单两个数四则运算题目的方法:MakeQuestion1(int min, int max, boolean multiply);
b.实现一个含规定运算符个数之内的四则运算题目的方法:MakeQuestion2(int min, int max, int p, boolean c, boolean b);
c.实现产生规定个数符合要求的四则运算式的方法:MakeQuestion3(int n, int min, int max, int o, boolean c, boolean b);
d.对每个式子运算的方法:solution(String question,int max,int min)。为了实现对负数的计算,在中缀转后缀的过程中对相应的判断做出了调整。
(3)MakeQuestion类中函数算法详情
a.和上次基础的四则运算一样,MakeQuestion1是用来形成最简单的两个数的四则运算的函数,与上次不同的是,这次题目中数值的大小及是否包含乘除法不再是我们确定的,而是通过用户输入确定的,所以这里也就有了min(题目中出现数字的下界),max(题目中出现数字的上界),multiply(是否包含乘除法)这些参数。根据min,max的范围使出现除法时,a始终是b的倍数,从而保证了可以整除。此方法也根据这些参数的要求产生组成题目的运算符和数字。
b. MakeQuestion2是对简单运算的组合,形成符合运算符要求的一个四则混合运算式。根据最大最小值max,min;运算符最多个数p;是否包含乘除法c;是否包含括号b,这些参数,使用if-else语句对各种情况进行了分析和判断,达到了生成符合要求运算式的效果。
c. solution函数是对每个运算式进行计算的函数,由于题目要求最大最小数值的约束范围不仅要在题目中保证,还要在运算过程中给予保证,所以需要在出完题后对题目进行运算,判断每一步的运算结果是否超出范围。由于我采用了中缀表达式转后缀表达式,再利用栈进行运算这种方法,所以实现以上要求,需要在从栈中弹出两个数进行运算,再把运算结果压入栈中时,判断此时的运算结果是否在min和max之间。同时,改进了中缀转后缀的过程。支持负数运算。当遍历qustion这个String时,如果遇到“-”,需判断其是减号还是负号,所以这里增加了判断,当其前面是左括号或者前面也是一个符号时,就把它当作负号,和其后的数字一起入number数字栈。(较满意的算法部分)
d. MakeQuestion3函数通过调用以上三个函数,生成规定数量要求的四则运算式。
3.函数间关系图展示
蓝色框代表类和方法,橘色框代表类和方法的作用
五.计算模块接口部分的性能改进
1.性能分析改进
我在运算模块的性能分析过程中,大约花费了2小时,首先经过性能分析知道了我有些资源在使用结束后是没有回收的,经过排查后,我发现了是在写入文件后没有关闭输出流,导致了资源没有完全回收。发现这一问题后,我针对它进行了改进,关闭了输出流。使项目的性能得到了提高。同时经过性能分析,我发现我整个程序消耗最大函数是出题过程中的MakeQuestion3,即出题过程中不仅要出题,还要判断题目是否符合要求,要调用调度场算法运算答案。因为这里要满足出题的时候必须在规定数字范围内,及整除等条件,不符合要求的算式都要重新出题。发现这一问题后,我也对我的程序进行了改进,将是否能整除这些判断在出两个数字的简单式子的时候直接进行了保证,使这里只需要判断每一步的运算结果是否在规定范围内,使整个程序的性能得到了提高。
2.性能分析截图
项目总体分析图,从内存,多线程,CPU等方面分析了计算模块的性能,截图如下:
性能分析过程截图:
对CPU占用情况进行分析,JProfiler提供不同的方法来记录访问树以优化性能和细节。以下是访问树 Call Tree视图:
以下是热点 Hot Spots视图,显示了消耗时间最多的方法的列表:
以下部分为内存视图及分析过程:
首次按F4,出现以下截图。可见有些资源没有回收,经排查,我发现是我没有关闭写入文件的输出流。解决过这个问题后,我再一次进行了性能分析。
再一次按F4后,如下图显示,资源全部被回收。证明没有资源泄露。程序性能良好。
经过以上的分析,让我了解了性能分析的过程,也知道了性能分析的重要性。
六.计算模块部分单元测试展示
1.分别对计算模块的Command.java和MakeQuestion.java进行了测试。写了如下两个单元测试类。
2.以下是测试类的部分代码
测试Command.java类:
@Test public void testMain() { String[] args = {"-n","10","-m","1","100","-o","5","-c","-b"}; String[] args1 = {"-o"}; String[] args2 = {"-n","-m","1","100"}; String[] args3 = {"-n","100000","-m","100"}; String[] args4 = {"-m","1000","44","-n"}; String[] args5 = {"-o","100"}; String[] args6 = {"-m","-3","1"}; new Command(); Command.main(args); Command.main(args1); Command.main(args2); Command.main(args3); Command.main(args4); Command.main(args5); Command.main(args6); }
测试MakeQuestion.java类:
@Test public void testMakeQuestion3() { new MakeQuestion().MakeQuestion3(1000, 1, 100, 3, true, true); new MakeQuestion().MakeQuestion3(10, 1, 100, 3, false, false); new MakeQuestion().MakeQuestion3(10, 5, 100, 1, true, true); new MakeQuestion().MakeQuestion3(10, 1, 100, 1, false, false); }
测试结果:
3.构造测试数据思路
(1)对于Command的测试,主要应保证参数的各种输入情况都有涉及。所以我就分别构造了:参数输入正确时的参数args数组;不包含必须输入的参数,并且-o参数后没有输入应有数字的args1数组;-n参数后不包含应有的数字的args2数组;及字母参数后数字范围有错的args3,args4,args5,args6。经过以上设计,最终Command类的测试覆盖率达96.4%。
(2)对于MakeQuestion方法的测试,为保证测试覆盖率,应保证各种类型的参数都输入执行。所以这里就构造了有乘除的,有括号的;没有乘除的,没有括号的;及不同运算符个数限制;不同数量;不同范围限制这些情况。经过以上设计,最终MakeQuestion类的测试覆盖率达94.4%。
4.测试覆盖率截图展示
以下分别为两个类测试覆盖率的截图展示以及部分代码执行情况展示:
由图可知,此次单元测试的覆盖率还是相对较高的,执行通过的绿色占大部分,但仍有一些呈现黄色和红色。我也会继续改进,争取再次提高测试覆盖率。
七.计算模块部分异常处理说明
1.没有输入必须的参数-m,-n
(1)我通过if判断对这种异常进行了处理。设计了两个参数ifm和ifn判断是否有-m,-n的输入。
if (ifn == false) { System.out.println("输入的参数中必须包含题目数量,请重新输入"); } else if (ifm == false) { System.out.println("输入的参数中必须包含对题目数值上下界的设定,请重新输入"); }
(2)单元测试样例
String[] args1 = {"-o"};
new Command();
Command.main(args1);
(3)错误对应场景:例如命令行没有输入-n或-m。发生错误时,会提示用户输入的参数中必须包含题目数量/对题目数值上下界的设定。请重新输入。
2.输入的参数超出指定范围
(1)对于这种情况我设计了参数ifrun对能获取到数字但是数字超出范围的情况进行了判断,出现异常时ifrun值为false。
if (n <= 0 || n > 10000) { ifrun = false; System.out.println("请输入1-10000的正整数作为题目个数的参数"); }
(2)单元测试样例
String[] args6 = {"-m","-3","1"}; new Command(); Command.main(args6);
(3)错误对应场景:例如命令行输入-n 10 –m -3 1。发生错误时,会提示用户各个参数应有的界限,请用户重新输入。
3.输入的字母后没有紧跟着对应数量的或对应形式的参数
(1)对于这种情况我用了try-catch语句判断处理异常。输入错误就会执行catch语句中内容,给出合理提示。
try { n = Integer.parseInt(args[i + 1]); } catch (Exception e) { // TODO: handle exception System.out.println("题目个数为1-10000的正整数,请正确输入"); } if(i < args.length-1){ i++; }else{ System.out.println("-n后必须要有一个正整数的输入,请重新输入"); } }
(2)单元测试样例
String[] args7={"-n","b","-m"}; new Command(); Command.main(args7);
(3)错误对应场景:例如-n –m 1 100的输入。发生错误时,会提示用户正确的输入形式,请用户重新输入。
4.出题后,写入文件时出错
对于这种情况我用了try-catch语句判断处理异常。输入错误就会执行catch语句中内容,给出文件出错提示。
八.界面模块的详细设计过程
1.大致设计
这次四则运算系统我们做的是网页版。所以界面模块我们首先分为了三个板块,用户的出题模块,用户上传题目进行做题的模块,各个用户做题记录模块。(附加功能,实现了多用户做题,及记录各用户成绩信息及最好成绩)
(1)用户出题板块有对各个参数的输入及判断。
(2)用户上传题目的板块包含文件上传及用户学号的输入。用户学号的输入是为了实现对各个用户成绩的记录,实现多用户。做题页面会呈现出全部题目,做完题后,会显示正确错误情况及所用时间。
(3)做题记录的模块包含各用户的所有做题记录和各用户最好成绩统计两个页面。每个页面都包含用户的学号,做题正确率,做题总时间和提交时间这些信息。
2.设计思路图
橘色方框代表所需页面,其他框代表所需功能
3.实现过程
这些页面具体是通过jsp页面,css,js来实现的。
(1)用户出题页面,这部分主要是前台使用form表单把用户输入的信息传到后台进行出题。与Command.java的命令行输入不同,这部分对用户输入信息的各种情况的判断,由前台的js判断。
var oBiaodan=document.getElementById('oBiaodan'); $("#yiding").click(function(){ var nummMax=document.getElementById('nummMax'); var numm=document.getElementById('numm'); var minn=document.getElementById('minn'); var maxx=document.getElementById('maxx'); if(numm.value==""){ alert("请填写题目数量!!!"); oBiaodan.onsubmit=function(){return false;} } if(numm.value<1||numm.value>10000){ alert("题目数量超出范围,范围为1-10000"); oBiaodan.onsubmit=function(){return false;} } if(minn.value==""||minn.value<1||minn.value>50){ alert("请填写正确范围!!!下线范围1-100,上限范围50到1000 "); oBiaodan.onsubmit=function(){return false;} } if(maxx.value==""||maxx.value<50||maxx.value>1000){ alert("请填写正确范围!!!下线范围1-100,上限范围50到1000 "); oBiaodan.onsubmit=function(){return false;} } });
(2)上传题目页面,同样是form表单,后台完成文件上传,并获取到各用户学号。这里文件上传是以enctype="multipart/form-data"形式提交到后台的。
<form action="AllServlet?flag=MakeAnswer" enctype="multipart/form-data" method="post"> <div> <div class="eoo">学号:</div> <input type="text" class="hhh" name="xh"> </div> <div> <div class="eoo vp" onclick="myfile.click();">选择文件</div> <input type="text" id="input1" class="mmm"> </div> <input type="file" id="myfile" name="file1" onchange="input1.value=this.value" style="display:none" > <input type="submit" id="otou" value="确定提交" class="eo"> </form>
(3)做题页面,做题页面时间的统计和答案对错的判断由前台完成。页面可以呈现出所有题目。通过js对答案对错进行了判断。后台把所有题目和所有题目答案放在list里传递到前台,前台再通过js对答案对错进行了判断。答题完毕后的信息框可以拖动,便于用户查看自己的答题情况。同时答题情况也将传递到后台,写入一个文件,为之后的查看成绩信息提供依据。
tiJiao.onclick=function(){ var yes=0; var no=0; //判断回答是否正确 for(var i=0;i<huida.length;i++){ anwser[i].style.display="inline-block"; if(huida[i].value==daan[i].innerHTML){ yesno[i].innerHTML="√"; yesno[i].style.color="#1DF126"; yes++; }else{ yesno[i].innerHTML="×"; yesno[i].style.color="red"; no++; } } clearInterval(timert); oBody.style.display="block"; tiJiao.style.display="none"; allnum.value=daan.length; allyes.value=yes; emm[0].innerHTML=daan.length; emm[1].innerHTML=yes; emm[2].innerHTML=no; if(theTime.innerHTML=="计时区"){ emm[3].innerHTML="您没有计时"; alltime.value=0; }else{ emm[3].innerHTML=theTime.innerHTML; alltime.value=theTime.innerHTML; } }
(4)统计题目页面由后台判断要显示的是全部用户的成绩还是各个用户的最好成绩。通过参数k,对文件中的成绩信息进行处理。k=0时,将文件信息全部输出,k=1时,将文件信息进行排序,去重复处理,输出各个用户的最好成绩。jsp页面显示各个用户的做题记录信息。
Servlet页面:
int k=Integer.parseInt(request.getParameter("k")); if(k==0){ request.setAttribute("studentInf", studentInf); request.setAttribute("k", 0); request.getRequestDispatcher("/rank.jsp").forward(request, response); }else{ Collections.sort(studentInf, new Comparator<Student>(){ public int compare(Student o1, Student o2) { if(Double.valueOf(o1.getPerscore())<Double.valueOf(o2.getPerscore())){ return 1; } if(o1.getPerscore() == o2.getPerscore()){ return 0; } return -1; } }); for(int i = 0 ; i < studentInf.size() ; i++) { for(int j = i+1 ; j < studentInf.size() ; j++) { if(studentInf.get(j).getStudentId().equals(studentInf.get(i).getStudentId())){ studentInf.remove(j); j--; } } } for(int i = 0 ; i < studentInf.size() ; i++) { System.out.println(studentInf.get(i).getStudentId()); } request.setAttribute("studentInf1", studentInf); request.setAttribute("k", 1); request.getRequestDispatcher("/rank.jsp").forward(request, response);
jsp页面:
<c:if test="${k=='0'}"> <c:set var="startIndex" value="${fn:length(studentInf)-1 }"></c:set> <c:forEach items="${studentInf}" var="t" varStatus="status"> <ul class="ti"> <li>${studentInf[startIndex - status.index].studentId}</li> <li>${studentInf[startIndex - status.index].perscore}%</li> <li>${studentInf[startIndex - status.index].avgtime}</li> <li style="width: 350px;"> ${studentInf[startIndex - status.index].zttime} </li> </ul> </c:forEach> </c:if> <c:if test="${k=='1'}"> <c:forEach items="${studentInf1}" var="t" > <ul class="ti"> <li>${t.studentId}</li> <li>${t.perscore}%</li> <li>${t.avgtime}</li> <li style="width: 350px;"> ${t.zttime} </li> </ul> </c:forEach> </c:if>
九.界面模块与计算模块的对接
1.整体对接方式
由于我们制作的是网页版,所以对接主要体现在jsp页面和servlet页面的交互和信息传递,后台从jsp页面获取到需要的参数,在servlet页面对获取到的参数进行处理,并调用计算模块的方法实现相应功能。
2.对接详情
(1)出题。获取到参数后,调用MakeQuestion3方法,进行出题,出完题后将题目写入文件,并进行文件下载,使用户把文件下载到本地。
String path = this.getServletContext().getRealPath("/"); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss"); String newName = "Makequestion"+sdf.format(new Date())+".txt"; String ResultName=path +"document/"+newName; PrintStream ps = new PrintStream(ResultName);// 创建一个打印输出流 System.setOut(ps);// 把创建的打印输出流赋给系统。即系统下次向 ps输出 question.MakeQuestion3(n, min, max, o, c, b); //下载 SmartUpload smartUpload1 = new SmartUpload(); smartUpload1.initialize(getServletConfig(), request, response); smartUpload1.setContentDisposition(null);//取消默认打开方式 try { smartUpload1.downloadFile(ResultName); } catch (Exception e) { response.getWriter().print("<script language='javascript'>alert('出题文件下载失败')</script>"); if(e.getMessage().indexOf("系统找不到指定的路径。") != -1){ response.getWriter().print("<script language='javascript'>alert('出题文件下载失败:文件不存在!')</script>"); } e.printStackTrace(); }
(2)做题页面。用户上传题目后,将所有题目和计算所得的答案封装到list集合中,传递到前台。
try{ InputStreamReader read = new InputStreamReader( new FileInputStream(file),encoding);//考虑到编码格式 BufferedReader br = new BufferedReader(read);//构造一个BufferedReader类来读取文件 String s = null; List<String> questionList=new ArrayList<String>(); List<String> answerList=new ArrayList<String>(); while((s = br.readLine())!=null){//使用readLine方法,一次读一行 Answer answer=new Answer(); questionList.add(s); answerList.add(answer.solution(s)); } br.close(); request.setAttribute("questionList", questionList); request.setAttribute("answerList", answerList); request.getRequestDispatcher("/answer.jsp").forward(request, response); }catch(Exception e){ e.printStackTrace(); }
(3)各用户做题记录显示页面。本页面的对接已经在第八点第四条给出。jsp页面根据不同的k值,利用jstl表达式和el标签显示。进行<c:if>判断,显示出不同情况。
3.项目截图展示
主页面:
出题功能展示:
答题页面展示:
用户成绩记录页面:
主页面提供两种选择:
各用户全部成绩记录页面,按时间倒序,最新的在最上面:
各用户最好成绩统计页面:
十.结对过程的描述
1.我们的结对大约分为两个阶段,由于我们编写的是网页版,两个人擅长的领域不相同,所以第一阶段主要是一人负责前端,一人负责后端,这一过程我们相当于是分开各自编程的,所以这里部分对驾驶员和领航员这种模式的切换和运用不够明显,每人负责自己的那部分代码的编写;第二部分,进入合页面和两个模块的对接过程,我们两人一起合作,在这一阶段也充分体验了结对编程两个人一起的高效之处。我们两个人不断切换驾驶员领航员角色,比较顺利的完成了不同板块的对接与项目的测试。同时也共同完成了整个项目的性能分析和单元测试的覆盖率分析。
2.结对照片展示
十一.结对编程的优缺点
1.结对编程的优缺点
优点:充分锻炼了两个人的合作,沟通交流能力。两个人之间可以相互交流学习,从而互相取长补短。后期两人共同对接模块时,充分结合了两人的优点,使对接较为顺利的完成。
缺点:需要一段磨合时间,刚开始时效率不高。互相熟悉了解代码风格之后渐入佳境。
2.结对个人优缺点
王文雨 优点:(1)代码能力强,完成前端页面速度快,质量高。做事不拖沓,有自己的规划和节奏
(2)细心。对细节的处理很好,使得用户的体验更好
(3)耐心。当出现问题时能够和我一起分担,一起修改
缺点:对java语言熟悉度不够高
我的 优点:(1)编程思路清晰,能够尽自己最大的能力实现自己能做到的最好
(2)耐心,不惧怕问题的出现,出现问题时不骄不躁,能静下心来处理解决问题
(3)有上进心。在完成基本功能的前提下,根据时间估计,尽力完成了部分附加功能
缺点:一些部分对细节的处理不够精致。以后应该更细腻一些
十二.完成后实际的PSP
PSP |
任务内容 |
计划时间(min) |
完成时间(min) |
Planning |
计划 |
20 |
30 |
Estimate |
估计这个任务需要多少时间,并规划大致工作步骤 |
10 |
10 |
Development |
开发 |
1200 |
2700 |
Analysis |
需求分析 |
60 |
40 |
Design Spec |
生成文档 |
20 |
20 |
Design Review |
设计复审 |
10 |
20 |
Coding Standard |
代码规范 |
10 |
15 |
Design |
具体设计 |
20 |
60 |
Coding |
具体编码 |
720 |
1800 |
Code Review |
代码复审 |
30 |
45 |
Test |
测试 |
330 |
700 |
Reporting |
报告 |
480 |
840 |
Test Report |
测试报告(包括写博客) |
420 |
780 |
Size Measurement |
计算工作量 |
30 |
30 |
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
30 |
30 |
通过以上PSP可知,我们将大量时间都投入到了此次结对编程中。单从代码的开发就花费了45小时左右。这部分时间主要花费在代码的开发和测试阶段。此次对项目进行了单元测试和性能分析,这两部分也让我学到了很多。相比上次,我花费在写博客上的时间也大量增加,一共用了13小时左右完成博客。写博客的过程也并不是一气呵成,而是写写停停,边写博客边发现问题,边写博客边思考,边进一步改进了自己的项目。写博客的过程中,也让我对接口的设计有了更好的理解。
十三.项目总结与改进
这次结对编程我收获很多。从项目角度看,我们完成了所有基本功能,能够实现规定要求的出题,和带负数的计算,同时实现了允许多用户做题,并能记录所有用户的全部成绩和最好成绩。我对项目整体还是比较满意的。但我们的项目仍然存在很多不足之处。比如另外一个附加功能(能够支持多语言)没能实现,还有由于对每一步运算结果的范围限制,导致我们的程序出现括号的概率变小。接下来的时间我们一定会继续改进我们的项目,争取能呈现出更好的效果。从整体过程看,和文雨的合作十分愉快,她是一个很棒的结对伙伴,相信我们通过此次结对,对对方都有了更深的了解,促进了我们之间的友谊。