• 结对项目——网页版四则运算项目报告


    结对项目——网页版四则运算项目报告

     


     

    前言

    这次我们做的是网页版的四则运算,一人负责前端(我),一人负责后端(马福孝)。这次的结对项目对于我来说是一个很好的复习css,js的机会,很好地练习了ajax技术,对前后端分离也有了很好的理解。学到了这么多也算是对我这一周不眠不休码代码的最好回报了。

     


     

    一、代码仓库地址

    1. 代码仓库地址(马福孝):https://git.coding.net/mafx8859/ArithWeb.git(以马福孝版为准)
    2.   吴建瑜:https://git.coding.net/wujy123/twoperson_calculate.git
    3. 远程服务器测试路径(直接点击测试):http://47.93.197.5:8080/ArithmeticWeb1.0/
    4. 本地未编译后测试路径:http://localhost/ArithmeticWeb/WebRoot/calcalute.html
    5. 本地编译后测试路径:http://localhost/ArithmeticWeb1.0/

    二、psp展示(计划完成与实际完成)

    PSP2.1

    任务内容

    计划共完成需要的时间(min)

    Planning

    计划

    30

    ·        Estimate

    ·   估计这个任务需要多少时间,并规划大致工作步骤

    30

    Development

    开发

    48*60

    ·        Analysis

    ·         需求分析 (包括学习新技术)

    10*60

    ·        Design Spec

    ·         生成设计文档

    80

    ·        Design Review

    ·         设计复审 (和同事审核设计文档)

    100

    ·        Coding Standard

    ·         代码规范 (为目前的开发制定合适的规范)

    30

    ·        Design

    ·         具体设计

    2*60

    ·        Coding

    ·         具体编码

    32*60

    ·        Code Review

    ·         代码复审

    20

    ·        Test

    ·         测试(自我测试,修改代码,提交修改)

    30

    Reporting

    报告

    45

    ·         Test Report

    ·         测试报告

    10

    ·         Size Measurement

    ·         计算工作量

    20

    ·         Postmortem & Process Improvement Plan

    ·         事后总结, 并提出过程改进计划

    15


    三、接口设计

    (1)Information Hiding:去查阅了相关资料,信息隐藏(Information Hiding)是指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。这样做的好处一是可以隐藏复杂度,这样你就不用再去应付它,除非你要特别关注的时候;二是可以隐藏变化源:这样当变化发生时,其影响就能被限制在局部范围内。复杂度的根源包括复杂的数据类型、文件结构、布尔判断以及晦涩的算法等等。

    我们将多处用到的类,方法放在外联而不是内嵌,避免发生一处更改,处处更改的情况发生。在做题模块中,计算方法,用到的栈,判断优先级的图等都是对用户不可见的,前端只需要访问即可。在出题界面也是如此:存题目的list容器,存储运算符的数组对前端也不可见。

    部分代码如下:

    function getGradpm(){
        var filename=document.getElementById('filename_id').value;
         $(function(){
               $.ajax({
                    url:"AtithControlle",/*请求的地址*/
                    data:"flag=getGradpm&fileName="+filename,/*数据携带*/
                    type:"get",/*请求的方式*/
                    dataType:"json",
                    success:function(result){/*请求后得到的结果*/
                     console.log(result);
                     buildList1(result);
                      
                    }
               });
            
            });

    (2)Interface Design:

    前端的接口设计我参考了这篇博客:https://www.cnblogs.com/xiaohuochai/p/6644558.html

    我运用了大量的ajax来完成前端的异步接口,基本实现了前后台分离,增强了程序的可扩展性,可维护性。

    (3)Loose Coupling:

    关于松耦合我参考了这篇博客,对我启发很大:https://www.cnblogs.com/laxcus/p/5681611.html

    软件工程中对象之间的耦合度就是对象之间的依赖性。指导使用和维护对象的主要问题是对象之间的多重依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。

    我们在程序设计中将判断正确率模块,出题范围模块等均封装到不同的方法中,来降低程序的耦合度。

     

     


     

     

    四、计算模块接口的设计与实现过程

     

    1.  首先在前端页面利用js限定题目的出题范围:

     

    要求如下:

    -n 设定题目数量 【必须】

       正常的-n 参数范围为 1 ~ 10000

     -m [lower] [upper] 设定题目数值的范围 【必须】

      正常的-m 下界参数范围为 1到100,上界参数范围为 50到1000

    -o 设定题目中最多几个运算符 【非必须,默认为1】

     正常的-o 参数范围为 1 ~ 10

     -c 设定题目中是否有乘除法 【非必须,默认没有】

      -b 设定题目中是否有括号 【非必须,默认没有】

     如果生成的算式有括号,括号数量必须小于等于算式的运算符个数。-n 设定题目数量 【必须】

     

    用js的正则表达式可以实现这个功能,部分代码如下(完整代码请见main.js):

    //数量框
    count.onblur = function() {
            if(this.value.length == 0) {
                countb.innerHTML = '<i class="no"></i>输入不能为空值!'
            }
            //验证非0正整数   验证1-10000的正整数
            else if((/^+?[1-9][0-9]*$/.test(this.value))&&this.value<10000){
                countb.innerHTML = '<i class="ok">题目数量格式正确</i>'
            } else {
                countb.innerHTML = '<i class="no"></i>输入必须为1-10000整数'
            }
        }
        //范围框,小范围为1-100  
    rangeLow.onblur=function(){
        if(this.value.length ==0){
            rangebL.innerHTML = '<i class="no"></i>输入不能为空值!'
        }
        else if((/^+?[1-9][0-9]*$/.test(this.value))&&this.value<100){
                rangebL.innerHTML = '<i class="ok">题目下界输入正确</i>'
            } else {
                rangebL.innerHTML = '<i class="no"></i>输入下界为1-100整数'
            }
    }

    2.  做题时,利用ajax技术通过后台给的接口获取到算式和答案。将答案显示在页面中,用数组保存答案。当用户点“我要做题”时,用事件监听函数动态生成input框,并触发计时器开始计时;当 用户点提交时,用数组获取到用户的答案与正确答案一一比对来算正确率,同时停止计时器。

    利用ajax获取数据部分代码:

    function getGradpm(){
        var filename=document.getElementById('filename_id').value;
         $(function(){
               $.ajax({
                    url:"AtithControlle",/*请求的地址*/
                    data:"flag=getGradpm&fileName="+filename,/*数据携带*/
                    type:"get",/*请求的方式*/
                    dataType:"json",
                    success:function(result){/*请求后得到的结果*/
                     console.log(result);
                     buildList1(result);
                      
                    }
               });
            
            });

    显示:

    function buildList(result){
        $.each(result,function(index,item){
        var i=0;
        if(i==0){
           document.getElementById("filename_id").value=item.filename;
           document.getElementById("user_id").value=item.zcm;
        }
        var td1="<td>"+item.atith+"</td>";
        var td2="<td><input type='hidden' name='userSolve'/><input type='hidden' name='rightSolve' value='"+item.result+"'/></td>";
          //alert(td2);
          $("<tr></tr>").append(td1).append(td2)
                        .appendTo("#atithList");
        });
        }

     3.  判断是否正确:

    // 获取答案
     $(function(){
               $.ajax({
                    url:"AtithControlle",/*请求的地址*/
                    data:"flag=getGradpm&fileName="+filename,/*数据携带*/
                    type:"get",/*请求的方式*/
                    dataType:"json",
                    success:function(result){/*请求后得到的结果*/
                     console.log(result);
                    MyrightString=rightString;                  
                    }
               });        
            });
     // 判断用户答案与正确答案是否相同
     function tijiao(){
    for(var i=0;i<yhda.length;i++){
          if(yhda[i].value==daan[i].innerHTML){
             count++;
          }else{
            nocount++;
          }
       }
     }

    4.  通过setimeout生成计时器:

    function timedCount()
    {
        var usersolves=document.getElementsByName("userSolve");
        for(var i=0;i<usersolves.length;i++){
            usersolves[i].type="text";
        }
        document.getElementById('time_id').value=c+"秒";
        c=c+1;
        t=setTimeout("timedCount()",1000);
    }

    五、计算模块接口部分的性能改进

    在性能改进中大致时间表如下:

    项目 用时
    使用JProfiler工具分析调试 2(h)
    查找影响性能的程序模块 1(h)
    性能改进 2(h)

     

    改进调试思路:首先进行内存资源监控,当程序刚进入运行时,对当前状态进行Mark,观察不同类的内存变化,一段时间后按下F4进行资源回收,此时发现在自己的向本地文件写入模块中有资源无法回收,经过进一步调试发现是对IO资源没有close,进而调用close()对IO资源进行关闭。同时在资源监测过程中发现出题模块对资源的占有较多,以及耗时较大,这里所改进的思路是将原来通过循环产生合法的运算式的方式改进成一次性输出合法运算式。性能分析图如下:

    Mark:

    再次回收:

    内存分析:

     


     六、计算模块部分单元测试展示

    部分单元测试代码如下:

    import static org.junit.Assert.*;
    
    import java.io.IOException;
    
    import org.junit.Test;
    
    public class getResultTest {
        DealAtith da=new DealAtith();
        @Test
        public void test1() {
            assertEquals(17,da.getResult("6+3-2+10", true));
            assertEquals(17,da.getResult("6+3-2+10", false));
            
        }
        @Test
        public void test2() {
            assertEquals(18,da.getResult("(6+4)÷2+10", true));
            assertEquals(18,da.getResult("6+4÷2+10", false));
            
        }
        @Test
        public void test3() {
            assertEquals(260,da.getResult("(6+4)÷2*50+10", true));
            assertEquals(260,da.getResult("(6+4)÷2*50+10", false));
            
        }
        @Test
        public void test4() {
            da.creatAtith(10, 4, 1, 100, true, true);
            da.creatAtith(10, 4, 1, 100, false, false);
            da.getBl("9+6*(2+10)");
            da.getBl("9+6*(2+10)-(10÷2-2)");
            try {
                da.printOperForm();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                System.out.println("打印函数抛出异常");
            }
            
        }
    
    }

    覆盖率截图:

    在计算模块中设计到对个条件的判断分支,其中分支主要存在于,对运算符优先级的判断、对括号的判断等,同样在由中缀缀表达式获取后缀表达式的方法中同样存在以上分支的判断,因此为了增加测试的覆盖率,首先从简单的加减法运算数据入手,接着再加入乘除法,最后引入带有括号的运算数据。这样逐渐提高了测试的覆盖率。


    七、计算模块部分异常处理说明

    在计算模块主要涉及到异常处理问题有参数合法性和运算结果的正确性,其中参数合法性包括对生成运算式个数的参数的合法性判断以及异常处理、运算符个数参数合法性判断以及异常处理、运算数范围参数合法性判断以及异常处理等

    (1)对-m、-n参数的整体为空值是的测试,测试其为空时,程序对异常处理的友好性。

    @Test
        public void testcommand1() {
            String[] args={};
            Command.main(args);
        }

    (2)对运算数范围参数-m的合法性异常处理的JUNit测试代码以及参数构造思路为:

    ①选择合理的参数范围测试其在参数合理的条件下程序的正确性:

    @Test
        public void testcommand3() {
            String[] args={"-m","1","100","-n","10"};
            Command.main(args);
        }

    ②构造不合理参数进行测试,测试其在出现异常时友好的异常处理功能:

    @Test
        public void testcommand3() {
            String[] args={"-m","1","40","-n","10"};
            Command.main(args);
        }

    (3)对生成运算式个数参数-n的合法性异常处理的JUNit测试代码以及参数构造思路为:

    ①构造用户传入的-n参数是合法的,用于测试在参数合法的条件下程序的正确性。

        @Test
        public void testcommand4() {
            String[] args={"-m","1","40","-n","10"};
            Command.main(args);
        }

    ②构造不合理的参数用于测试当参数不合法是,程序对异常处理的友好性。

    @Test
        public void testcommand3() {
            String[] args={"-m","1","100","-n","20000"};
            Command.main(args);
        }

    (3)对计算模块运算结果的测试,其参数构造思路是从简单的运算式开始入手,逐渐构造叫复杂的运算式,测试计算程序的运行结果的正确性:

    复制代码
    DealAtith da=new DealAtith();
        @Test
        public void test1() {
            assertEquals(17,da.getResult("6+3-2+10", true));
            assertEquals(17,da.getResult("6+3-2+10", false));
            
        }
        @Test
        public void test2() {
            assertEquals(18,da.getResult("(6+4)÷2+10", true));
            assertEquals(18,da.getResult("6+4÷2+10", false));
            
        }
        @Test
        public void test3() {
            assertEquals(410,da.getResult("(6+4)÷2*50+10", true));
            assertEquals(410,da.getResult("(6+4)÷2*50+10", false));
            
        }
        @Test
        public void test4() {
            da.creatAtith(10, 4, 1, 100, true, true);
            da.creatAtith(10, 4, 1, 100, false, false);
            da.getBl("9+6*(2+10)");
            da.getBl("9+6*(2+10)-(10÷2-2)");
            try {
                da.printOperForm();
            } catch (IOException e) {
                System.out.println("打印函数抛出异常");
            }
            
        }

    八、界面模块的详细设计过程 

    考虑到是合作项目,我前期做了基本的页面传给后台,用git进行版本控制,这样前后台就能同时推进项目进度。部分代码如下:

    function checkform() {
            console.log("hhhh");
        for(var i = 0; i < Allb.length; i++) {
            //所有的都遍历一遍没有问题后再return,在循环里面return的话就在第一个就退出循环了
            //if(/<i class="no"></i>/.test(this.innerHTML)){
            //u6b63表示正确的正  
            if(!(/[u6b63]/.test(Allb[i].innerHTML))){
                console.log('提交失败');
                alert('提交失败');
                return false;
            }
        }
        console.log("yes");
        return true;
    }
    <label>
                    <span class="form-group tishi" style="letter-spacing: 4px;">题&nbsp;目&nbsp;数&nbsp;量</span>
                    <!-- 用required和星号来设置必填项 -->
                    <input type="text" name="count" id="count" placeholder="必须为1-10000整数" required />
                </label>
                <b id="countb"></b>
            </div>
            <div class="detail">
                <label>
                    <span class="form-group tishi" style="letter-spacing: 2px;">算式数值范围</span>
                    <input type="text" name="rangeLow" id="rangeLow" required/>
                    <span>——</span>
                    <input type="text" name="rangeHigh" id="rangeHigh" required/>
                </label>
                <b id="rangebL"></b>
                <b id="rangebH"></b>
            </div>
    
            
                <div id="js-example-change-value" class="detail">
                    <span class="tishi">运算符的最大数目</span>
                    <input type="range" min="1" max="10" value="1" data-rangeslider name="opeMax">
                    <output></output>
               
            <!-- <input type="number" value="10"> <button class="button button-small">确定</button> -->
                </div>

    完成基本逻辑后,我又花费了很长的时间在用户体验优化和页面美观上,最终部分页面及功能如下

    中英文转换:

    我要出题:

    当必填项空白点提交时:

    当用户填写范围有误时,会提醒用户,并且无法提交,运算符最大数目用滑块来限制大小为1-10,优化用户体验;是否包含乘除法和括号均默认为否

    填写范围正确要求为:题目数量为1-10000整数;题目下界为1-100;题目上界为50-1000;题目下界小于上界

    只有当提交正确时才可以提交并下载。

    我要做题页面:

    上传学号注册码后,可自己提交题目,也可以选择已提交过的题目来做:

     做题页面为:

    用户通过点击开始答题,显示input框让用户输入答案,同时计时器开始计时;点提交答案后,计时器停止,一一比对答案计算正确率。

     

     


     

     

    九、界面模块与计算模块的对接

    对接方面,我们运用了大量的ajax技术来实现对接,避免套页操作。后台传json字符串给前台,前台接收后来实现展示和判断等功能。

    传数据部分代码:

    function delete_giveMoney(id){
        //alert("jj")
               $(function(){
                    $.ajax({
                    url:"/AccountBook/books/delete",/*请求的地址*/
                    data:"id="+id,/*数据携带*/
                    type:"get"/*请求的方式*/
                    });
        
              });
    }

    js负责限定参数范围:

    rangeLow.onblur=function(){
        if(this.value.length ==0){
            rangebL.innerHTML = '<i class="no"></i>输入不能为空值!'
        }
        else if((/^+?[1-9][0-9]*$/.test(this.value))&&this.value<100){
                rangebL.innerHTML = '<i class="ok">题目下界输入正确</i>'
            } else {
                rangebL.innerHTML = '<i class="no"></i>输入下界为1-100整数'
            }
    }
    //范围框上界
    rangeHigh.onblur=function(){
        if(this.value.length ==0){
            rangebH.innerHTML = '<i class="no"></i>输入不能为空值!'
        }
        else if((/^+?[1-9][0-9]*$/.test(this.value))&&this.value<1000&&this.value>50){
                rangebH.innerHTML = '<i class="ok">题目上界输入正确</i>'
            } else {
                rangebH.innerHTML = '<i class="no"></i>输入上界为50-1000整数'
            }
    }

    判断正误:

    // 获取答案
     $(function(){
               $.ajax({
                    url:"AtithControlle",/*请求的地址*/
                    data:"flag=getGradpm&fileName="+filename,/*数据携带*/
                    type:"get",/*请求的方式*/
                    dataType:"json",
                    success:function(result){/*请求后得到的结果*/
                     console.log(result);
                    MyrightString=rightString;                  
                    }
               });        
            });
     // 判断用户答案与正确答案是否相同
     function tijiao(){
    for(var i=0;i<yhda.length;i++){
          if(yhda[this].value==daan[this].innerHTML){
             count++;
          }else{
            nocount++;
          }
       }
     }

    计时器:

    function timedCount()
    {
        var usersolves=document.getElementsByName("userSolve");
        for(var i=0;i<usersolves.length;i++){
            usersolves[i].type="text";
        }
        document.getElementById('time_id').value=c+"秒";
        c=c+1;
        t=setTimeout("timedCount()",1000);
    }

    完整代码可见代码仓库。


    十、结对的过程

    我们写的是网页版,前期我们互不干扰,独立完成所需的功能;进入合页面和两个模块的对接过程后,我们两人一起合作,体验到了结对编程两个人一起的高效之处。两个人不断切换驾驶员领航员角色,比较顺利的完成了不同板块的对接与项目的测试。同时也完成了整个项目的性能分析和单元测试的覆盖率分析。

    值得一提的是,这次的合作作业我们尝试使用了git的版本控制,果然比qq互相发文件压缩包有效多了:


    十一、结对编程优缺点

    结对编程优缺点都是存在着的,首先优点在于:

    (1)程序员互相帮助,互相教对方,可能得到能力上的互补。

    (2)可以让编程环境有效地贯彻Design。

    (3)增强代码和产品质量,并有效的减少BUG。

    (4)降低学习成本。一边编程,一边共享知识和经验,有效地在实践中进行学习。

    (5)在编程中,相互讨论,可能更快更有效地解决问题。

    缺点在于:

    (1)对于有不同习惯的编程人员,可以在起工作会产生麻烦,甚至矛盾。

    (2)有时候,程序员们会对一个问题各执己见(代码风格可能会是引发技术人员口水战的地方),争吵不休,反而产生重大内耗。

    (3)两个人在一起工作可能会出现工作精力不能集中的情况。程序员可能会交谈一些与工作无关的事情,反而分散注意力,导致效率比单人更为低下。

    (4)结对编程可能让程序员们相互学习得更快。有些时候,学习对方的长处,可能会和程序员们在起滋生不良气氛一样快。比如,合伙应付工作,敷衍项目。

    (5)面对新手,有经验的老手可能会觉得非常的烦躁。不合适的沟通会导到团队的不和谐。

    (6)新手在面对有经验的老手时会显得非常的紧张和不安,甚至出现害怕焦虑的的精神状态,从而总是出现低级错误,而老手站在他们后面不停地指责他们导致他们更加紧张,出现恶性循环。最终导致项目进展效率低下,并且团队貌合神离。

    (7)有经验的人更喜欢单兵作战,找个人来站在他背后看着他可能会让他感到非常的不爽,最终导致编程时受到情绪影响,反而出现反作用。

    马福孝的优点:1.后台技术知识扎实,同时对单元测试,效能分析等有提前接触的经验。

                           2.强大的代码能力,对前台技术亦有涉猎,我们在合作时顺畅不少。

                           3.强大的学习能力,独立做出GUI图形界面版四则运算和网页版四则运算后台

                缺点:想了半天没想出来,和大佬的这次合作十分愉快。

    吴建瑜的优点:1.前端技术比较熟悉,也接手过几个学校老师的项目

                           2.追求完善,在时间足够的前提下尽力完善前端页面的效果。

                          3.主动解决问题

                缺点:对java语言不怎么熟悉

                          前期技术难点耗费时间较长


    十二、psp实际花费时间

    PSP2.1

    任务内容

    实际完成需要的时间(min)

    Planning

    计划

    20

    ·        Estimate

    ·   估计这个任务需要多少时间,并规划大致工作步骤

    20

    Development

    开发

    53*60+30

    ·        Analysis

    ·         需求分析 (包括学习新技术)

    9*60

    ·        Design Spec

    ·         生成设计文档

    60

    ·        Design Review

    ·         设计复审 (和同事审核设计文档)

    130

    ·        Coding Standard

    ·         代码规范 (为目前的开发制定合适的规范)

    30

    ·        Design

    ·         具体设计

    2*60

    ·        Coding

    ·         具体编码

    38*60

    ·        Code Review

    ·         代码复审

    20

    ·        Test

    ·         测试(自我测试,修改代码,提交修改)

    30

    Reporting

    报告

    43

    ·         Test Report

    ·         测试报告

    8

    ·         Size Measurement

    ·         计算工作量

    15

    ·         Postmortem & Process Improvement Plan

    ·         事后总结, 并提出过程改进计划

    20

                     


    总结:

    本次作业完成了所有基本功能,能够实现规定要求的出题,计算等功能,同时实现了允许多用户做题,并能记录所有用户的成绩的附加功能二。

    但由于技术所限,项目仍然有很多不足之处,如附加功能一(能够支持多语言)没能实现。接下来的时间我们会继续改进我们的项目,争取能呈现出更好的效果。

     

  • 相关阅读:
    谈谈对程序猿的管理
    OFMessageDecoder 分析
    [LeetCode-21]Construct Binary Tree from Preorder and Inorder Traversal
    leetcode第一刷_Rotate Image
    [二次开发]dede文章页面怎样显示作者的头像
    MapReduceTopK TreeMap
    安卓3d引擎
    LeetCode::Sort List 具体分析
    杨帆之工作日志-2014.6.24
    CF1109F Sasha and Algorithm of Silence's Sounds
  • 原文地址:https://www.cnblogs.com/wujy123/p/8761675.html
Copyright © 2020-2023  润新知