• 结对项目之小学生四则运算系统网页版


    项目克隆地址:https://git.coding.net/Donsparks/Calculator.git

    线上测试地址:http://39.105.6.214:80/hxd_war/

    一、前言

    开篇之前,我想先介绍一下我的搭档,汪义华(以下以华仔称呼)。在我看来,华仔是我特别中意的一个搭档(至于为什么我将在下文告诉大家)。所以我觉得我们两个的合作,应该可以擦出思想的火花,我期待我们的结对项目的效果是1+1>2的。抱着这样一个信念,我们开始了我们的第一次合作...

    二、开始前PSP展示

    三、结对编程对接口的设计

    1、一些方法的了解:

    • 信息隐藏:指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。 信息隐藏在设计的所有层次上都有很大作用:从用具名常量代替字面常量,到创建数据类型,再到类的设计、子程序的设计以及子系统的设计等等。

    • 接口设计:面向接口编程是我们在编程时常用的一种手段

    • 松耦合度:这一概念看起来很抽象,但是我举个例子,就可以很轻松的理解这一概念:

    有一百人分成10个团队做开发 你写了一个类A,供其他人调用,怎么办? 简单的方法就是把这个类打成jar包,然后给他们 他们就A a = new A(); 然后调用a的方法。 但是有一天,A类升级了,怎么办? 再打jar包,再给其他9个组每个组发一份,告诉他们,替换一下以前的jar包。 有可能你的a中,方法签名还发生了变化,那么他们就得重新改代码来适应你新的jar包了。 如果这样的事频繁发生呢,那么你就等着挨骂吧。 这就是紧耦合,他们的程序紧密地耦合着你的程序。 如果是松耦合的话,我想我可能会定义一个接口给他们,然后IOC的方式将实现类给他们,最好是远程的通过webservice的方式进行调用,这样我的A更新了,只需要切换掉远程的A的实现类,他们根本啥也不知道,啥也不用改,就更新了功能,怎么样,是不是很方便? 这就是松耦合

    2、我们团队在编程过程中对以上方法的体现:

    • 在很多类的数据成员的定义上面,我们使用private去定义。这样一来就避免了全局数据可能带来的一些问题,避免了我们把类内数据误认为是全局数据并使用它,减少了很多编程风险。并且,通过对信息隐藏的理解,我发现了信息隐藏的独特的启发力,它能够激发我们设计出有效的方案。并且有助于我们设计类的公开接口。在设计的所有层面上,都可以通过询问该隐藏些什么来促成好的设计决策。 我觉得如果有一天我们能够养成问“我该隐藏些什么?”的习惯,就会惊奇的发现,有很多棘手的设计难题都会迎刃而解。

    • 至于接口部分,更是在我们编程过程中充分体现,比如在出题板块GetFomula中,其中运算式的生成和运算式结果的计算都运用了接口,增强了整个程序的可扩展性。

    • 耦合度部分,我们也是通过对不同方法的封装,降低了程序的耦合度,尽量保证了整个程序的松耦合。

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

    我们的计算模块主要有三部分: Command、GetFomula

    • Command类主要是接受从命令行传来的控制信息,并且能够从中提取出我们需要的参数。并针对一些情况进行了异常处理,反馈给用户合理的提示信息。

    • GetFomula类主要是产生算式,并且能够处理从命令行传来的各种参数,其中包括:quesNum(运算式个数),operNum(运算符个数),minNum(运算数范围最小值),maxNum(运算数范围最大值),ifSetArgsC(是否包含乘除法),ifSetArgsB(是否包含括号),从而达到产生不同要求的运算式效果。  并且在产生运算式之后,还可以用getResult()方法对运算式的结果进行运算,这里采用了逆波兰算法,通过将中缀表达式转换为后缀表达式,很好的实现了对于不同类型运算式的一个统一的算法。

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

    通过一番自主学习,我采用JProfiler对代码进行性能调教和分析:

    通过上面的能效分析图,可以看出,代码的性能并非特别完美,还有改进的空间。

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

    这里主要是针对Command类做的单元测试:

    Command类代码测试如下:

    import org.junit.Test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class CommandTest {
        @Test
        public void main() throws Exception {
            Command command = new Command();
            String[] args1 = {"-m", "1", "100", "-n", "7"};//正确参数类型
            String[] args2 = {};//缺少参数
            String[] args3 = {"-m", "1", "100"};//缺少部分参数
            String[] args4 = {"-m", "1", "10", "-n","10"};//参数范围错误
    
            List<String[]> list = new ArrayList();
            list.add(args1);
            list.add(args2);
            list.add(args3);
            list.add(args4);
    
            for (int i = 0; i < list.size(); i++)
                Command.main(list.get(i));
        }
    }

    对Command类进行代码覆盖率测试,结果如下:

    七、异常处理

    异常处理主要体现在参数的合法性和运算结果的正确性上面,包括对生成算式的个数,运算符的个数,运算数的范围等,都做了相应的异常处理:

      
    for(int i=0;i<args.length;i++){
    switch(args[i]){
    case "-n":{
    try{
    quesNum=Integer.parseInt(args[++i]);
    }catch(Exception ex){
    System.out.println("输入的题目数量不符合规定,请输入1-1000之间的整数");
    return;
    }
    if(quesNum<1||quesNum>10000){
    System.out.println("输入的题目数量不符合规定,请输入1-1000之间的整数");
    return;
    }
    break;
    }
    case "-m":{
    try{
    minNum=Integer.parseInt(args[++i]);
    maxNum=Integer.parseInt(args[++i]);
    }catch(Exception ex){
    System.out.println("输入的运算数范围不合理,请输入整数值");
    return;
    }
    if(minNum<1||maxNum<50||minNum>100||maxNum>1000){
    System.out.println("输入的运算数范围不合理,请将下界设置于为1-100之间的整数,上界设置为50-1000的整数");
    return;
    }
    if(maxNum<minNum){
    System.out.println("输入的运算数范围不合理,请设置上界参数大于下界参数");
    return;
    }
    break;
    }
    case "-c":ifSetArgsC=true;break;
    case "-o":{
    try{
    operNum=Integer.parseInt(args[++i]);
    }catch(Exception ex){
    System.out.println("输入的运算符个数不合理,请输入1-10之间的整数");
    return;
    }
    if(operNum<1||operNum>10){
    System.out.println("输入的运算符个数不合理,请输入1-10之间的整数");
    return;
    }
    break;
    }
    case "-b":ifSetArgsB=true;break;
    }
    }

    同样的,针对这些异常情况,我在测试类中设计了一些测试,从而验证这些异常的处理情况:

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

     (一)登陆注册部分:

    1、登陆界面:

    2、注册界面:

    (二)用户使用界面:

    1、主页面(生成题目):

     

    2、查看历史界面:

    3、上传题目界面:

    4、密码修改界面:

    代码展示:

    1、控制用户登录部分的servlet代码:

    @WebServlet( name = "UserServlet" ,urlPatterns = "/user",asyncSupported = true)
    public class UserServlet extends HttpServlet{
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
    
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //使客户端浏览器,区分不同种类的数据,并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。MIME映射策略就是在网页中使用哪个应用程序(即插件),打开哪种文件。另外还有使用权限问题。
            response.setContentType("text/html;charset=UTF-8");
            //设置对客户端请求进行重新编码的编码为utf-8
            request.setCharacterEncoding("utf-8");
            //服务器向客户端反馈的时候需要用流向客户端输出数据
            PrintWriter out = response.getWriter();
            //获取jsp页面发生的事件
            String state = request.getParameter("state");
    
            switch (state){
                case "toLogin":{
                    request.getRequestDispatcher(WebContents.LOGIN).forward(request,response);
                    break;
                }
                case "login":{
                    loginCheck(request,response);
                    break;
                }
                case "toRegister":{
                    request.getRequestDispatcher(WebContents.REGISTER).forward(request,response);
                    break;
                }
                case "register": {
                    register(request, response);
                    break;
                }
                case "toUpdatePassword": {
                    toUpdatePassword(request, response);
                    break;
                }
                case "updatePassword": {
                    updatePassword(request, response);
                    break;
                }case "exit":{
                    exit(request,response);
                    break;
                }
            }
        }

     2、控制用户做题部分的servlet代码:

    WebServlet( name = "PracticeServlet" ,urlPatterns = "/practice",asyncSupported = true)
    @MultipartConfig
    public class PracticeServlet extends HttpServlet{
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
    
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //使客户端浏览器,区分不同种类的数据,并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。MIME映射策略就是在网页中使用哪个应用程序(即插件),打开哪种文件。另外还有使用权限问题。
            response.setContentType("text/html;charset=UTF-8");
            //设置对客户端请求进行重新编码的编码为utf-8
            request.setCharacterEncoding("utf-8");
            //服务器向客户端反馈的时候需要用流向客户端输出数据
            PrintWriter out = response.getWriter();
            //获取jsp页面发生的事件
            String state = request.getParameter("state");
    
            switch (state){
                case "main":{
                    request.getRequestDispatcher(WebContents.MAIN).forward(request,response);
                    break;
                }
                case "create":{
                    create(request,response);
                    break;
                }
                case "check":{
                    check(request,response);
                    break;
                }
                case "download":{
                    download(request,response);
                    break;
                }
                case "begin":{
                    begin(request,response);
                    break;
                }
                case "toUpload":{
                    request.getRequestDispatcher(WebContents.UPLOAD).forward(request,response);
                    break;
                }
                case "upload":{
                    upload(request,response);
                    break;
                }
                case "beginSelf":{
                    beginSelf(request,response);
                    break;
                }
                case "checkSelf":{
                    checkSelf(request,response);
                    break;
                }
                case "history":{
                    history(request,response);
                    break;
                }
                case "onePractice":{
                    onePractice(request,response);
                    break;
                }
            }
        }

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

    上图是项目的结构,其实也可以说是一个web项目的标准结构。首先介绍一下com文件夹下面的子文件夹:1、dao层:主要是一些接口以及接口的实现类。2、entity(实体层):定义一些属性还有构造方法。3、servlet:主要控制页面之间的跳转。4、sql:用来存放sql语句,方便调用和更改。5、util:主要包含一些工具类,比如生成运算式、计算运算式的逻辑部分,都可以直接在这里调用。

    项目中前后端交互采用servlet实现(servlet代码在上面已经展示)。当用户发送一个请求,会进入到相应的servlet方法中,在servlet方法中获取对应的参数,调用接口中的方法,并对客户端做出响应。为了提高代码的安全性,jsp页面放在了web-INF目录下面,因此只有通过servlet才能访问到页面。servlet中包含了上传文件,下载文件,读取文件,生成题目,答题以及各个页面之间跳转的方法。

    十、结对过程的描述

    1、结对编程的历程:

    在和华仔写项目的过程中,我们前期的思路还是很明确的:一开始我想单独把Calculator作一个计算部分,也就是说这里不负责生成算式,而且生成算式本身也不是逆波兰算法的内容, 并且两者掺杂在一起,很容易混乱,所以我在这里单独用逆波兰算法,做了一个功能十分强大的逻辑运算部分,也就是可以运算所有的四则运算表达式(带加减乘除括号的)而且刚刚好,这个四则运算无法判断运算式的合理性,所以我只需要保证产生的四则运算式的合理性,就可以堪称一个完美的逻辑部分。并且,我可以顺便在产生四则运算式子的部分进行一些条件控制,从而使得满足项目的条件要求(这部分在MakeFormula中完成)最后还有一个产生formula的部分,也就是运算式,可以从MakeFormula中传过来。对了,我的所输入的一些命令行语句要能够传到MakeFormula中(这部分在Command中进行)最后的生成文件部分,可以在Command中调用MakeFile来完成。注意,你需要把表达式传到MakeFile中,至于表达式的结果,我觉得可以传到MakeFile中保存起来,为以后UI的设计埋好伏笔。

    但是后来中期出现了算法上面的能效问题,就导致我们的项目不能继续进行了。认真思考之后,我觉得不仅仅是算法上的问题吧,很大一部分原因在于我们的项目开发计划的不合理性。或许我们应该不在算法那一块纠结那么久,而是先做一个简易版本,然后把整个项目的框架做出来,至少得到一个像样的成品来。之后再回头去升级我们的算法,岂不是一件完美的事。正如助教说的,一个程序设计的基本原则:Make it run,make it run right,make it run fast.我觉得这是给我和华仔上的最好的一课,并且能够在这样一门课程的一次结对作业中,就能如此深刻的理解这一道理。我觉得对于我们来说是收获极大的,必将成为我们今后在学习,工作中的一笔宝贵财富。

    ............分界线.............

    经过了又一阶段的学习,我们决定放弃之前的GUI,转战web。然而并不是一切都那么顺利,在一次又一次的尝试,一次又一次的失败下。我终于有了一个属于自己的功能较为完善的web项目。当然在这一阶段,我必须要感谢我工作室小伙伴们对我的帮助,以及潜移默化的影响。当然,最最最要感谢的还是美丽的慧珍同学,给予了我最大的帮助。

    2、结对编程剪影:

    十一、结对编程的优缺点

    1、结对优缺点:

    优:能够锻炼合作和沟通能力,收获不一样的编程喜悦,这是自己一个人编程所体会不到的。

    缺:两个人编程习惯的不同,会导致开发过程中的一些阻碍和矛盾。

    2、我和我的搭档:

    华仔:

    优点:算法能力强;思考问题考虑周全;想法独到有见解

    缺点:做事有些急躁

    我:

    优点:乐观积极,不怕困难;沟通能力强;编程思路清晰

    缺点:做事不细心

    十二、完成后实际的PSP

  • 相关阅读:
    python 学习常见问题笔记
    mac os 使用 from scipy.misc import imread ImportError: cannot import name 'imread'
    mac解决安装提示“xxx软件已损坏,打不开,您应该将它移到废纸篓”的提示
    一文搞懂HMM(隐马尔可夫模型)
    docker从容器里面拷文件到宿主机或从宿主机拷文件到docker容器里面
    centos 7 安装python3 & pip3
    HTK HResults计算字错率(WER)、句错率(SER)
    sublime text3安装ConvertToUTF8
    intellij idea 解决2019年4月到期延期问题
    Python 3 教程
  • 原文地址:https://www.cnblogs.com/xdhou/p/8767896.html
Copyright © 2020-2023  润新知