201871010130-周学铭 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告

项目 内容
课程班级博客链接 18卓越班
这个作业要求链接 实验三结对编程要求
我的课程学习目标 体验软件项目开发中的两人合作,练习结对编程(Pair programming)。
掌握Github协作开发程序的操作方法。
这个作业在哪些方面帮助我实现学习目标 通过与结对方配合实现该项目,从而熟悉了结对编程的方法。同时也熟悉了通过Github协作开发程序的操作方法。
结对方学号-姓名 201871030110-何飞
结对方本次博客作业链接 结对方博客连接
本项目Github的仓库链接地址 github仓库链接

任务一:阅读《现代软件工程—构建之法》第3-4章内容,理解并掌握代码风格规范、代码设计规范、代码复审、结对编程概念

1.构建之法第三章总结:

  • 软件工程师的思维误区:
    • 分析麻痹:一种极端情况是想弄清楚所有细节、所有依赖关系之后再动手,心理上过于悲观,不想修复问题,出了问题都赖在相关问题上。
    • 不分主次,想解决所有依赖问题:另一种极端是过于积极,想马上动手修复所有主要和次要的依赖问题,然后就可以“完美地”达成最初设定的目标,而不是根据现有条件找到一个“足够好”的方案。
    • 过早优化:过早优化:既然软件是“软”的,那它就有很大的可塑性,可以不断改进。放眼望去,一个复杂的软件似乎很多模块都可以变得更好。一个工程师在写程序的时候,经常容易在某一个局部问题上陷进去,花大量时间对其进行优化;无视这个模块对全局的重要性,甚至还不知道这个“全局”是怎么样的。
    • 过早扩大化/泛化:
    • 画扇面——调侃目标和远景:

2.构建之法第四章总结

  • 代码规范:代码规范可以分为两个部分:
    • 代码风格规范:主要是文字上的规定。
      -原则:简明、易读、无二义性。
      -主要包含:缩进、行宽、括号、断行与空白的{}行、分行、命名、下划线、大小写、注释等。
    • 代码设计规范:牵涉到程序设计、模块之间的关系、设计模式等方方面面的通用原则。
      • 这部分主要描述了错误处理:参数的处理、断言以及如何处理C++的类。
  • 代码复审
    • 代码复审的定义:看代码是否在代码规范的框架内正确地解决了问题。
    • 形式
名称 形式 目的
自我复审 自己vs自己 用同伴复审的标准来要求自己。不一-定最有效,因为开发者对自己总是过于自信。如果能持之以恒,则对个人有很大好处
同伴复审 复审者vs开发者 简便易行
团队复审 团队vs开发者 有比较严格的规定和流程,适用于关键的代码,以及复审后不再更新的代码覆盖率高一有很多双眼睛盯着程序,但效率可能不高( 全体人员都要到会)

  • 目的
    • 找出代码的错误。
    • 发现逻辑错误。
    • 发现算法错误。
    • 发现潜在的错误和回归性错误。
    • 发现可能需要改进的错误。
    • 教育(互相教育)开发人员,传授经验,让更多的成员熟悉项目各部分的代码,同时熟悉和应用领域相关的实际知识。
  • 步骤
    • 代码必须成功地编译。
    • 程序员必须测试过代码。
    • 程序员必须提供新的代码,以及文件差异分析工具。
    • 在面对面的复审中,一般是开发者控制流程,讲述修改的前因后果。但是复审者有权在任何时候打断叙述,提出自己的意见。
    • 复审者必须逐一提供反馈意见。
    • 开发者必须负责让所有的问题都得到满意的解释或解答,或者在TFS中创建新的工作项以确保这些问题会得到处理。
    • 对于复审的结果,双方必须达成一致的意见。
  • 代码复审的核查表
    • 主要包括:概要部分,设计规范部分,代码规范部分,具体代码部分,效能,可读性,可测试性。
  • 结对编程
    • 好处:1.在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作解决问题的能力更强。两人合作,还有相互激励的作用,工程师看到别人的思路和技能,得到实时的讲解,受到激励,从而努力提高自己的水平,提出更多创意。
      2.对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
      3.在企业管理层次上,结对能更有效地交流,相互学习和传递经验,分享知识,能更好地应对人员流动。
    • 两人合作的各个阶段:萌芽阶段 → 磨合阶段 → 规范阶段 → 创造阶段 → 解体阶段。
    • 影响他人的方式:断言,桥梁,说服,吸引。
    • 正确地给予反馈:“三明治”方法
      • 先放一片面包:做好铺垫:强调双方的共同点,从团队共同的愿景讲起,让对方觉得处于一个安全的环境。
      • 然后再把肉放上:这时就可以把建设性的意见( Constructive Feedback )加工好,加上生菜、佐料等。
      • 然后再来一片面包:呼应开头,鼓励对方把工作做好。

任务二:两两自由结对,对结对方《实验二 软件工程个人项目》的项目成果进行评价。

  • 结对方信息:
结对方学号-姓名 201871030110-何飞
结对方博客 实验二:博客
结对方github地址 实验二:项目地址

  • 分工:
姓名 工作任务
何飞 前端的设计与实现
周学铭 后端及算法的实现

  • 博客点评:
博客地址 实验二:博客
博文结构 博文结构清晰,版式整洁,错落有致,具有段落感。同时将一些重要的地方用高亮显示了出来,具有观看体验。
博文内容 对于博文内容,这里按每个任务模块进行点评。
任务1:优点是对于每个点评给出了超链接,且定位到了该评论的位置,极为方便;缺点是最好能在超链接前标注一下点评对象的姓名等信息。
任务2:最好能够将《 构建之法》PSP那两章进行提炼概括。
任务3:在需求分析上,对0-1背包给出了自己的解释,相信博主一定对该问题有了较为深入的理解。同时对于任务三功能本身,博主完成度极高,而且用了GUI人机交互界面,具有很好的用户体验,完成的极为出色。但缺点在于回溯算法没有实现。
博文结构与PSP中“任务内容”列的关系 博主的博文撰写流程是按照PSP的主要流程一步一步走下来的,具有较好的整体构思。
PSP数据的差异化分析与原因探究 博主实际完成时间远大于计划完成时间,主要是在具体编码,代码复审与事后总结这三个阶段。结合我自己的开发经验,主要原因可能在于对PSP具体流程的不熟悉,自我估计不足等。也有可能是在开发过程中遇到了一些预料之外的突发情况等
  • 代码核查表:
说明 内容
概要部分
代码符合需求和规格说明么? 基本符合,但部分功能完成度较差。
代码设计是否考虑周全? 考虑周全
代码可读性如何? 采用模块化编程,代码可读性好。
代码容易维护么? 对于不同的功能模块,分别存储在不同的类中,维护较为容易。
代码的每一行都执行并检查过了吗? 是的
设计规范部分
设计是否遵从已知的设计模式或项目中常用的模式? 采用了抽象工厂设计模式(对象创建型模式)
有没有硬编码或字符串/数字等存在? 没有,采用的都是符合命名规范的变量名
代码有没有依赖于某一平台,是否会影响将来的移植? 没有
开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现? 能调用
有没有无用的代码可以清除? 没有
代码规范部分
修改的部分符合代码标准和风格吗? 符合规范
具体代码部分
有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常? 没有处理异常值
参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度是以0开始计数还是以1开始计数? 没有
边界条件是如何处理的? switch语句的default分支是如何处理的?循环有没有可能出现死循环? 采用哨兵处理边界;没有使用switch语句;循环不会出现死循环。
有没有使用断言( Assert)来保证我们认为不变的条件真的得到满足? 程序较为简单,所以没有使用断言。
数据结构中有没有用不到的元素?
效能
代码的效能(Performance)如何?最坏的情况是怎样的? 运行效率较低。
代码中,特别是循环中是否有明显可优化的部分(C++中反复创建类,C#中 string的操作是否能用StringBuilder来优化)?
对于系统和网络的调用是否会超时?如何处理? 没有对网络的调用
可读性
代码可读性如何?有没有足够的注释? 关键语句都有注释,可读性高
可测试性
代码是否需要更新或创建新的单元测试? 不需要

  • 日志数据:

  • 实验测试:

    • 数据读取:
    • 散点图:
    • 排序:
    • 动态规划(小数据):
    • 动态规划(大数据):

任务三、任务四

  • PSP流程:

    PSP2.1 任务内容 计划完成需要的时间(min) 实际完成需要的时间(min)
    Planning 计划 60 60
    · Estimate · 估计这个任务需要多少时间,并规划大致工作步骤 60 60
    Development 开发 2340 2580
    · Analysis · 需求分析 (包括学习新技术) 60 60
    · Design Spec · 生成设计文档 30 30
    · Design Review · 设计复审 (和同事审核设计文档) 60 60
    · Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 30
    · Design · 具体设计 60 60
    · Coding · 具体编码 1800 2040
    · Code Review · 代码复审 180 120
    · Test · 测试(自我测试,修改代码,提交修改) 120 180
    Reporting 报告 210 210
    · Test Report · 测试报告 60 60
    · Size Measurement · 计算工作量 30 30
    · Postmortem & Process Improvement Plan · 事后总结 ,并提出过程改进计划 120 120
  • 需求分析:

    • 平台基础功能:实验二中的任务3
    • D{0-1}KP实例数据集存储到数据库:
    • 平台可动态嵌入任何一个有效的D{0-1}KP 实例求解算法,并保存算法实验日志数据:
    • 人机交互界面要求为GUI界面:
    • 设计遗传算法求解D{0-1}KP:
  • 软件设计说明:

    • GUI界面:
      • 前端界面:主要采用Springboot+layui。
        • 前端的GUI使用的是layui的框架,前端界面的显示是通过layui提供的模板,以及themeleaf、js等要素构成。
          • themeleaf提供了强大的模板支持,本次实验中,themeleaf出演的角色是抽取公共的核心的DOM元素,在其它的前端页面通过调用的方式进行插入DOM节点,大大节约了开发的成本,提高了代码的可重用性;而layui主要是提供各种各样的优化的按钮、文本等之类的组件。
          • layui是国人构建的一个前端框架,由于开发者是做后端的程序员,所以,layui提供的接口是与后端进行了无缝衔接,同时,它通过简单的定制,便可使得页面渲染的效果极佳。
          • layui前端元素的显示是通过设置其class属性,达到显示元素的效果较为美观,通过layui封装的js,即可实现数据的异步传输(常见的网络通信有AJAX、axios、表单等,其中,前两项属于异步通信),它接收的服务器返回的参数必须是JSON格式,因此,它的js文件可能封装了AJAX,它要求后端返回的json数据必须是数组格式(key为data)、数据的长度(key为count)、数据的额外信息(key为msg)、数据成功与否(key为code),数据域(data为json对象或者hashmap)。layui的封装的js用于请求后台的数据,并显示在界面上(layui是通过模块化加载的,因此,需要引入响应的模块)。
        • 本次项目的前端需要引用的js文件是通过cdn去加载的,因此,项目的运行需要在网络允许的范围内才能使用。
      • 后台功能:主要包括路由转发以及数据库的操作。
        • 路由的实现:主要是通过controller进行处理和转发,只需要将路径写对即可。
        • 数据的加载与显示:主要是通过从数据库中获取数据,构造json对象,返回到前端即可,需要注意的是,所有的服务器返回的数据必须是键值对的形式,最好是json(json只是键值对的一种,比如hashmap也是键值对的形式)
    • 遗传算法:
      • 算法介绍:是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。该算法通过数学的方式,利用计算机仿真运算,将问题的求解过程转换成类似生物进化中的染色体基因的交叉、变异等过程。是一种近似算法。
      • 算法流程:
        • 种群初始化:通过随机函数为初始种群赋初值。即用一长串的01串来表示种群的选择,1:选了该组数据;0:不选该组数据,所以我们可以生成一个随机数,然后对它进行去模来进行初始化。
        • 个体评价:计算初始化种群适应度,首先对每个染色体其初值进行遍历,因为这里是按组为单位的,每组数据下面还有三种选择,所以需要对这三种选择继续进行随机选择。需要注意的是因为这三种选择概率不同,所以对它们赋值的概率也不同。同时在进行选择的时候,还要对背包的约束条件进行判断,即是否超出了背包初始的容积。最后返回当前的最优值并累积种群中各个个体的累积概率。
        • 选择运算:这里使用的是赌轮选择策略,其依据是上一步中各种群中各个个体的累积概率。
        • 交叉运算:这里仍然需要使用随机数,使用的方法为:两点交叉算子。
        • 变异运算:依旧需要随机函数,但使用的方法为:多次对换变异算子。
  • 项目测试:

    • GUI界面:

    • 数据库:

      • SQL语句:
      • 基本表:
      • 日志文件:
      • 数据集有效信息:
    • 实验二任务3的移植:

      • 数据集的上传:


      • 数据集的查看:

      • 散点图:

      • 数据排序:

      • 动态规划算法:


      • 回溯算法(-1表示数据量过大,无法在短时间内完成):

    • 遗传算法:

      • 本地测试:
        • "id-kp1-10.txt"数据集的第0组数据
          • 初始化种群:
          • 迭代后的种群:
          • 最优解求解(其中最佳编码为最优解,从前往后分别为各组数据,二分量中前一个表示1:选择该组数据,0:不选该组数据;后一个表示选择了该组数据的哪一个变量)
          • 数据集:
          • 答案验证:价值:1329+998+1657+1375+971+815+1360+1948=10453
            重量:1321+1098+1781+1362+903+894+1241+1545=10145<10149
        • "id-kp1-10.txt"数据集的第1组数据

          动态规划计算出来的最优解为70106,误差值为791,误差值为1%,准确率可以接受。
      • web测试:


    • 日志:

  • 提交记录

  • 软件实现及核心功能代码展示:

    • BacktrackProgramming.class:完成了回溯算法。
private void dfs(int x){
    back_count++;
    if(back_count>INF){
        res=-1;
        return ;
    }
    if(x>=row) {
        return ;
    }
    else {
        if(weight[x+1][2]<=back_weight) {
            back_weight-=weight[x+1][2];
            back_value+=value[x+1][2];
            if(res<back_value) {
                res=back_value;
            }
            dfs(x+1);
            back_weight+=weight[x+1][2];
            back_value-=value[x+1][2];
        }
        dfs(x+1);
        if(weight[x+1][0]<=back_weight) {
            back_weight-=weight[x+1][0];
            back_value+=value[x+1][0];
            dfs(x+1);
            if(res<back_value) {
                res=back_value;
            }
            back_weight+=weight[x+1][0];
            back_value-=value[x+1][0];
        }
        if(weight[x+1][1]<=back_weight) {
            back_weight-=weight[x+1][1];
            back_value+=value[x+1][1];
            if(res<back_value) {
                res=back_value;
            }
            dfs(x+1);
            back_weight+=weight[x+1][1];
            back_value-=value[x+1][1];
        }
    }
}
  • DynamicProgramming.class:完成了动态规划算法。
private Long knapSack(int[] weight, int[] profit, int C)
{
    int n = profit.length/3;//profit一定是3的倍数
    int[][] maxvalue = new int[n + 1][C + 1];//价值矩阵
    long before=System.currentTimeMillis();
    for (int i = 0; i < maxvalue.length; i++) {
        maxvalue[i][0] = 0;
    }
    for (int i = 0; i < maxvalue[0].length; i++) {
        maxvalue[0][i] = 0;
    }
    for (int i = 1; i < maxvalue.length; i++) {//不处理第一行
        for (int j = 1; j <maxvalue[0].length; j++) {//不处理第一列
            //处理每一个项集
            int index=(i-1)*3;//计算当前的索引值,这里以项集为单位进行计算
            ArrayList<Integer> item=new ArrayList<>();
            if (j<weight[index]&&j<weight[index+1]&&j<weight[index+2])
            {
                maxvalue[i][j]=maxvalue[i-1][j];
                continue;
            }
            if(j>=weight[index])
                item.add(Math.max(maxvalue[i-1][j],profit[index]+maxvalue[i-1][j-weight[index]]));
            if(j>=weight[index+1])
                item.add(Math.max(maxvalue[i-1][j],profit[index+1]+maxvalue[i-1][j-weight[index+1]]));
            if(j>=weight[index+2])
                item.add(Math.max(maxvalue[i-1][j],profit[index+2]+maxvalue[i-1][j-weight[index+2]]));

            item.sort((Integer o1, Integer o2)->{
                if (o1>o2) return -1;
                else if (o1==o2) return 0;
                else return 1;
            });
            maxvalue[i][j]=item.get(0);
        }
    }
    long after=System.currentTimeMillis();
    this.setOptimalSolution(maxvalue[n][C]);
    return (after-before);
}
  • GeneticProgramming.class:完成了遗传算法。
// 初始化种群
private void initGroup() {
    int k, i;
    for (k = 0; k < scale; k++)// 种群数
    {
        // 01编码
        for (i = 0; i < LL; i++) {
            oldPopulation[k][i] = random.nextInt(65535) % 2;
        }
    }
}

private best_one evaluate(int[] chromosome) {
    // 010110
    int vv = 0;
    int bb = 0;
    int str[]=new int[LL];
    // 染色体,起始城市,城市1,城市2...城市n
    for (int i = 0; i < LL; i++) {
        if (chromosome[i] == 1) {
            int temp=random.nextInt(65535) % 64;
            if(temp<2) {
                vv += v[i][temp];
                bb += b[i][temp];
                str[i] = temp + 1;
            }
            else{
                vv += v[i][2];
                bb += b[i][2];
                str[i] = 3;
            }
        }
        else {
            str[i]=0;
        }
    }
    if (bb > pb) {
        // 超出背包体积
        best_one x =new best_one();
        x.x=0;x.y=str;
        return x;
    } else {
        best_one x =new best_one();
        x.x=vv;x.y=str;
        return x;
    }
}

// 计算种群中各个个体的累积概率,前提是已经计算出各个个体的适应度fitness[max],作为赌轮选择策略一部分,Pi[max]
private void countRate() {
    int k;
    double sumFitness = 0;// 适应度总和

    int[] tempf = new int[scale];

    for (k = 0; k < scale; k++) {
        tempf[k] = fitness[k];
        sumFitness += tempf[k];
    }

    Pi[0] = (float) (tempf[0] / sumFitness);
    for (k = 1; k < scale; k++) {
        Pi[k] = (float) (tempf[k] / sumFitness + Pi[k - 1]);
    }
}

// 挑选某代种群中适应度最高的个体,直接复制到子代中
// 前提是已经计算出各个个体的适应度Fitness[max]
private void selectBestGh() {
    int k, i, maxid;
    int maxevaluation;
    int max_str[] = null;

    maxid = 0;
    maxevaluation = fitness[0];
    for (k = 1; k < scale; k++) {
        if (maxevaluation < fitness[k]) {
            maxevaluation = fitness[k];
            max_str=fitness_str[k];
            maxid = k;
        }
    }

    if (bestLength < maxevaluation) {
        bestLength = maxevaluation;
        best_str=max_str;
        bestT = t;// 最好的染色体出现的代数;
        for (i = 0; i < LL; i++) {
            bestTour[i] = oldPopulation[maxid][i];
        }
    }

    // 复制染色体,k表示新染色体在种群中的位置,kk表示旧的染色体在种群中的位置
    copyGh(0, maxid);// 将当代种群中适应度最高的染色体k复制到新种群中,排在第一位0
}

// 赌轮选择策略挑选
private void select() {
    int k, i, selectId;
    float ran1;
    for (k = 1; k < scale; k++) {
        ran1 = (float) (random.nextInt(65535) % 1000 / 1000.0);
        // System.out.println("概率"+ran1);
        // 产生方式
        for (i = 0; i < scale; i++) {
            if (ran1 <= Pi[i]) {
                break;
            }
        }
        selectId = i;
        copyGh(k, selectId);
    }
}

private void evolution() {
    int k;
    // 挑选某代种群中适应度最高的个体
    selectBestGh();
    // 赌轮选择策略挑选scale-1个下一代个体
    select();
    float r;

    // 交叉方法
    for (k = 0; k < scale; k = k + 2) {
        r = random.nextFloat();// /产生概率
        // System.out.println("交叉率..." + r);
        if (r < Pc) {
            // System.out.println(k + "与" + k + 1 + "进行交叉...");
            OXCross(k, k + 1);// 进行交叉
        } else {
            r = random.nextFloat();// /产生概率
            // System.out.println("变异率1..." + r);
            // 变异
            if (r < Pm) {
                // System.out.println(k + "变异...");
                OnCVariation(k);
            }
            r = random.nextFloat();// /产生概率
            // System.out.println("变异率2..." + r);
            // 变异
            if (r < Pm) {
                // System.out.println(k + 1 + "变异...");
                OnCVariation(k + 1);
            }
        }

    }

}


// 两点交叉算子
private void OXCross(int k1, int k2) {
    int i, j, flag;
    int ran1, ran2, temp = 0;

    ran1 = random.nextInt(65535) % LL;
    ran2 = random.nextInt(65535) % LL;

    while (ran1 == ran2) {
        ran2 = random.nextInt(65535) % LL;
    }
    if (ran1 > ran2)// 确保ran1<ran2
    {
        temp = ran1;
        ran1 = ran2;
        ran2 = temp;
    }
    flag = ran2 - ran1 + 1;// 个数
    for (i = 0, j = ran1; i < flag; i++, j++) {
        temp = newPopulation[k1][j];
        newPopulation[k1][j] = newPopulation[k2][j];
        newPopulation[k2][j] = temp;
    }

}

// 多次对换变异算子
private void OnCVariation(int k) {
    int ran1, ran2, temp;
    int count;// 对换次数
    count = random.nextInt(65535) % LL;

    for (int i = 0; i < count; i++) {

        ran1 = random.nextInt(65535) % LL;
        ran2 = random.nextInt(65535) % LL;
        while (ran1 == ran2) {
            ran2 = random.nextInt(65535) % LL;
        }
        temp = newPopulation[k][ran1];
        newPopulation[k][ran1] = newPopulation[k][ran2];
        newPopulation[k][ran2] = temp;
    }
}

private void solve() {
    int i;
    int k;

    // 初始化种群
    initGroup();
    // 计算初始化种群适应度,Fitness[max]
    for (k = 0; k < scale; k++) {
        best_one temp= evaluate(oldPopulation[k]);
        fitness[k]=temp.x;
        fitness_str[k]=temp.y;
    }

    // 计算初始化种群中各个个体的累积概率,Pi[max]
    countRate();

    for (t = 0; t < MAX_GEN; t++) {
        evolution();
        // 将新种群newGroup复制到旧种群oldGroup中,准备下一代进化
        for (k = 0; k < scale; k++) {
            for (i = 0; i < LL; i++) {
                oldPopulation[k][i] = newPopulation[k][i];
            }
        }
        // 计算种群适应度
        for (k = 0; k < scale; k++) {
            best_one temp= evaluate(oldPopulation[k]);
            fitness[k]=temp.x;
            fitness_str[k]=temp.y;
        }
        // 计算种群中各个个体的累积概率
        countRate();
    }
}
  • InitAnalysis.class:文件读取存储模块。
public boolean getAllParameterByFile(String filePath) throws IOException {
    //若目录不存在,则创建目录
    File file=new File(filePath);
    String data= FileUtils.readFileToString(file,"UTF-8");
    //初步格式化件
    String getTextByDeleteWhiteSpace=StringUtils.deleteWhitespace(data);//删除所有的空格
    if (getTextByDeleteWhiteSpace.charAt(2)=='*') {
        int getFirstDKPIndex = getTextByDeleteWhiteSpace.indexOf("DKP");//获得首次出现DKP的位置
        String formatPrefixText = getTextByDeleteWhiteSpace.substring(getFirstDKPIndex + 3);//删除冒号以前的字符
        int getLastFullIndex = formatPrefixText.lastIndexOf('.');//获得最后一次出现.的位置
        int getLastCommaIndex = formatPrefixText.lastIndexOf(',');//获得最后一次出现,的位置
        int index = getLastFullIndex > getLastCommaIndex ? getLastFullIndex : getLastCommaIndex;//获得格式化后的文本
        String formatSuffixText = formatPrefixText.substring(0, index);
        getProfitAndWeight(formatSuffixText);//获取profit和weight
        return true;
    }else{
        return false;
    }
}
  • JDBCUtils.class:数据库连接模块。

  • 结对的过程

    • 首先我们对项目进行了详细的分工,并对项目使用的框架及架构进行了探讨。
    • 其次对项目的布局进行了设计。
    • 之后我们各自完成了其负责的部分,并在qq等平台上进行联系与沟通。









  • 总结:

    • 在组员积极交流且按照极限编程的方式进行项目开发是可以带来1+1>2的效果。
    • 结对编程要特别注意代码的规范问题,以及数组的下标。(比如我原本的程序,数组的下标是从1开始的;而我的结对方其数值的下标是从0开始的。)
    • 结对编程时要多保持交流,积极反馈当前遇到的问题与解决措施。
    • 但如果两个人在一起工作时,其中一个人想偷懒去干别的,那么就会拖延工作进度。
    • 两人互相监督工作,可以增强代码和产品质量,并有效的减少BUG。
    • 在编程中,相互讨论,可以更快更有效地解决问题,互相请教对方,可以得到能力上的互补。
    • 由于时间紧张,web界面还没有将解向量的数据输出到前端界面。
posted @ 2021-04-13 23:10  z_thorn  阅读(331)  评论(10编辑  收藏  举报