2020软件工程实践第4次作业-结对编程之实验室程序实现

这个作业属于哪个课程 软件工程2020秋学期
这个作业要求在哪里 结对编程之实验室程序实现
这个作业的目标 学习前端相关框架与技术,实战 GitHub 协作
学号 031802112, 031802113

基础信息

结对成员

GitHub 仓库地址

https://github.com/yellow-akihiro/031802112-031802113

分工

以下只是大致的分工,实际上每个部分都是两人协力完成的。

  • 黄泷

    用户界面、单元测试部分设计与编码,博客撰写

  • 黄明浩

    数据处理、树形结构部分设计与编码,博客撰写

PSP 表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
Estimate 估计这个任务需要多少时间 20 30
Development 开发
Analysis 需求分析(包括学习新技术) 480 560
Design Spec 生成设计文档 30 20
Design Review 设计复审 20 20
Coding Standard 代码规范(为目前的开发制定合适的规范) 10 10
Design 具体设计 30 40
Coding 具体编码 360 360
Code Review 代码复审 60 90
Test 测试(自我测试,修改代码,提交修改) 180 240
Reporting 报告
Test Report 测试报告 60 90
Size Measurement 计算工作量 10 15
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 90 120
合计 1350 1595

解题思路描述与设计实现说明

实现思路

  • 一开始想过手写树结构,初步构思了一下数据处理可以用字典结合数组,或者生成 JSON 文件,结点缩放就真没思路了。
  • 成品采用的是国内比较知名的 Element 组件库(基于 Vue.js)中提供的树形控件,在此基础上用 HTML 和 CSS 文件调整了页面布局和样式,在 JavaScript 文件中放各种数据处理和响应点击的函数。
  • 构建树的基本流程是:首先从输入框或文件内读取文本,将其按行分割;接着对每一行按冒号和顿号分割,按照导师、学生、技能三种类型分别加入到全局数组 data 中,全部处理完后进行关联关系的判断和调整;最后组件库自动生成合适的树结构,并实现结点缩放等功能。

图解说明

代码片段说明

这里展示用于处理数据和生成树的函数。

processInput() {
  this.data = [];
  this.treeCount = -1;
  id = 0;
  type = 0;
  root = null;
  if (this.inputData) {
    lines = this.inputData.split("\n"); // 拆分输入行
    flag = 0;
    for (line in lines) {
      parts = lines[line].split(":"); // 0 为数据类型,1 为数据内容
      if (parts[0] === "导师") { // 建立新树
        flag = 1;
        type = 0; // 重置学生类型
        if (root) {
          for (tree = 0; tree <= this.treeCount; tree++) {
            this.associate(this.data[tree], root);
          }
        }
        root = {
          id: id++, // 结点标识符
          label: parts[1], // 姓名或学生类别
          children: [], // 子节点列表
          level: 0, // 0 表示外层导师,1 表示学生类别,2 表示普通学生,3 表示学生导师
          info: null // 技能或工作经历
        };
        this.data.push(root); // 插入导师根节点
        this.treeCount++;
      } else if (this.treeCount >= 0) {
        if (parts[0].search(/博士生|硕士生|本科生/) != -1) { // 该行为学生
          newChild = {
            id: id++,
            label: parts[0],
            children: [],
            level: 1,
            info: null
          };
          this.data[this.treeCount].children.push(newChild); // 添加学生类型结点
          people = parts[1].split("、"); // 分隔学生姓名
          for (person in people) {
            newLeaf = {
              id: id++,
              label: people[person],
              children: [],
              level: 2,
              info: null
            };
            this.data[this.treeCount].children[type].children.push(newLeaf); // 添加学生叶节点
          }
          type++;
        } else if (parts.length === 2) {
          if (this.data[this.treeCount].label === parts[0]) {
            this.data[this.treeCount].info = parts[1];
          }
          for (itype = 0; itype < type; itype++) {
            for (person in this.data[this.treeCount].children[itype].children) {
              if (this.data[this.treeCount].children[itype].children[person].label === parts[0]) {
                this.data[this.treeCount].children[itype].children[person].info = parts[1];
              }
            }
          }
        } else if (parts[0] != "" && parts[0] != "\r") {
          temp = parseInt(line) + 1;
          this.$alert("请检查输入格式是否正确并尝试重新输入。第 " + temp + " 行可能有错。", '输入错误');
          flag = -1;
          break;
        }
      }
      else {
        break;
      }
    }
    if (flag == 0) {
      this.$alert("请检查输入格式是否正确并尝试重新输入。", '输入错误');
    }
    else if (flag == 1) {
      this.$message("树建立成功!");
    }
  }
}
  • 该函数实现了对输入数据的处理,并将处理结果插入全局数组以构建树结构。
  • 文本处理大量使用了 split 函数,用以按行、冒号和顿号分割各元素,方便转换为合适的结点。
  • 结点定义已在注释中给出,每个子节点都作为一个整体用 push 方法加入父节点的 children 数组中。
  • 能捕捉到几种简单的输入错误并报告用户。中间遇到错误直接终止。
  • 最后,每插入完一棵树后,调用 associate 函数尝试与已有的树建立关联。
associate(node, root) {
  console.log(node);
  if ((node.level == 2) && node.label == root.label) { // 找到了关联结点(名字相同且为学生)
    node.id = root.id;
    node.level = 3;
    node.children = root.children;
    if (root.info) {
      node.info = root.info;
    }
    // 找到学生导师原本的根结点并删除
    index = this.data.findIndex(d => d.id === root.id);
    this.data.splice(index, 1);
    this.treeCount--;
    return 1;
  }
  for (child in node.children) {
    if (this.associate(node.children[child], root) == 1) {
      return 1;
    }
  }
}
  • 使用搜索的方式查找已有的树中是否有与新插入树的导师名字相同的学生。如有,则用新树替换已有树的学生节点。
  • 该功能尚未经过充分测试,可能有一些 bug。

附加特点设计

设计独到之处

  • 实现了对单个结点的简单增删改查和拖动(暂不支持新增关联树)
  • 支持上传 txt 文本文件作为输入(只支持 UTF-8 编码文本)

实现思路

查找和文件输入功能基本上是使用的现成代码。这里给出链接:

这里以修改为例讲一下结点增删改功能的实现思路。

  • 首先在 HTML 文件中自定义结点呈现方式,放上修改功能的按钮。
  • 按下按钮会打开一个对话框,可以输入姓名和其他资料。
  • 捕获按下确定按钮的事件,按传入的结点参数修改相应信息,树结构会自动更新。

代码片段说明

modify(data) {
  this.temp = data;
  this.actionType = 1; // 表示修改信息
  this.form.name = data.label; // 以当前值作为对话框中信息的默认值
  this.form.info = data.info;
  if (data.level != 1) { // 该结点是人
    this.dialog1Visible = true; // 打开对话框
  }
  else { // 该结点是学生类型
    this.dialog2Visible = true;
  }
},

该函数在点击修改按钮时调用,会将相应的结点传入。

handleClose1() { // 针对人结点
  this.dialog1Visible = false;
  if (this.actionType) { // 表示修改信息
    this.temp.label = this.form.name;
    this.temp.info = this.form.info;
    this.$message('修改成功!');
  } else {
    newChild = {
      id: id++,
      label: this.form.name,
      children: [],
      level: 2,
      info: this.form.info
    };
    this.temp.children.push(newChild);
    this.$message('添加成功!');
  }
},

该函数在按下对话框确定按钮时调用,负责根据输入内容进行导师和学生结点的插入或修改。

实现成果展示

基础界面

  • 家族树以类似文件目录树的结构呈现。
  • 支持关联树的嵌套,但要求子树放在后面输入。

上传文件作为输入

  • 信息输入框下方有选择文件的按钮,可以上传本地文件作为输入。其他操作与在文本框中输入完全相同。
  • 目前仅支持 UTF-8 编码的 txt 格式文本文件。

查看技能及工作经历

  • 每个导师或学生节点前都有个头像图标,若有技能或工作经历信息则显示为蓝色,点击会弹出对话框显示相关信息。

搜索

  • 树结构上方有搜索框,可以搜索人名找到其自身和导师的结点。

结点增删改

  • 除了一般学生结点没有添加子结点按钮外,每个结点右侧都有数据修改、添加和删除的按钮,可以更方便地操作数据。
  • 点击修改或添加会弹出如上对话框,若是修改操作则默认填入当前信息。

仓库目录说明和使用说明

目录说明

  • README.md

    仓库文件和运行方式的说明。

  • background.jpeg

    网页的背景图片。

  • index.html

    网页主页面。

  • project.css

    CSS 文件,包含网页中用到的部分自定义样式。

  • project.js

    JavaScript 文件,包含存储结构和方法的定义。

使用说明

网页运行方式:

  1. 使用 git clone https://github.com/yellow-akihiro/031802112-031802113.git 命令将仓库克隆至本地,或在 GitHub 仓库中点击 Code->Download ZIP 命令下载仓库压缩包后解压。

  2. 请使用版本较新的浏览器打开 index.html。

    (经测试,最新版的 Google Chrome、Microsoft Edge(基于 Chromium)、Firefox 都可以正常显示网页。)

注意:为省去安装麻烦,本项目引用了部分在线库,因此在测试时请务必联网。若因网络不畅,出现界面加载失败或错位等问题,可耐心等待一段时间后再试。

具体输入格式在网页中有进行说明,基本上按照样例即可,对空行数没有硬性要求。

测试

选用工具和简要说明

对于单元测试工具的选择,看见助教给的参考资料里大部分都是 Mocha(感觉在疯狂暗示,哈哈哈),所以我们就选了 Mocha (摩卡)。

在我们本次作业中,由于大部分都是调用 Element 自带的控件,所以可测试的函数较少,我们把测试重心放在了对于输入数据处理的 processInput() 函数上。

有一说一,感觉这次的单元测试比个人编程恶心。在我们的代码中函数 processInput() 是直接操作的全局变量 data 而非传参,因此不太好做单元测试,只能把重要功能简单修改后单独生成一个 js 文件。(下回一定记得低耦合!)不过后面的 processinput.test.js 编写还是挺友好的。

另外,对于输入错误处理和其他一些不太方便测试的功能,我们是直接在页面中输入观察页面反馈,并且输出一些关键信息到浏览器的控制台查看。

单元测试教程链接

https://www.cnblogs.com/huanglong1123/p/13798771.html

代码说明

该测试代码主要是通过 processInput() 函数对于测试数据 message 进行处理。在重写 processInput() 函数时,为了方便测试,使函数返回一个测试数组。

从结果上看,是完全正确的,说明 processInput() 函数问题不大。

为了实现 Mocha 测试结果的 HTML 形式,可把我恶心坏了(也可能是我不太聪明的原因),还好最后搞出来了(虽然看上去很 low),厚着脸皮展示一下。

测试数据构造思路

对于测试数据的构造,主要根据原 processInput() 函数引用的参数来确定。因为文本输入的自由度偏大,比较难在函数中清晰地区分出合法与非法输入。处理非法输入也不是该函数的重点。因此我们只是构造了多组规模略有不同的合法数据

(目前的版本应该能够对大部分不正确的输入给出弹窗报错。而如果项目更加正式,或开发时间更长,我们可能会对常见的输入错误(如不必要的空格、标点全半角错误等)进行专门的处理,提供更加人性化、更加精确的提示,在可能的情况下帮助用户修改。)

GitHub 代码签入记录

地址:https://github.com/yellow-akihiro/031802112-031802113/commits/main

不得不吐槽一句 GitHub 把仓库默认分支从 master 改成 main 了(据说是 BLM 运动的影响?),害我们找了好一会儿 bug……

困难及解决办法

问题 1

  • 问题描述

    两人都没有接触过前端开发,只是之前听说要做小程序看了一点点 HTML、JavaScript 和 CSS 的教程,感觉无从下手。

  • 做过哪些尝试

    通览基础教程后我们认为手写树形结构不现实,效果也不会好。后来查找了解了各种能实现可视化树的前端库和框架,包括 D3.js、zTree、Element、Ant Design 等。

  • 是否解决

    是。选择了基于 Vue.js 的 Element。因为觉得大众化的组件库教程应该比较完善,而且粗看了一下去年 Z 班好像用的人不多。

  • 有何收获

    提升了快速查找、筛选资料的能力,同时让我们更加意识到遇事不放弃、不畏难,方可取得成功。

问题 2

  • 问题描述

    看似正确的代码在网页上点击却没有反应。

  • 做过哪些尝试

    反复检查代码,查找类似功能博客上其他人的写法,对可疑代码搜索相应语言的手册。

  • 是否解决

    是。后来发现大部分的错误在 Chrome 的控制台内都有体现,查找对应代码修改即可解决。还可以用 console.log 输出变量信息查看。

  • 有何收获

    对用到的语言和框架更熟悉了一些,并且学会了利用浏览器控制台进行简单的调试。

问题 3

  • 问题描述

    网页布局和样式不尽如人意。

  • 做过哪些尝试

    阅读 CSS、HTML、Vue.js、Element 的教程,搜索相关博客阅读。

  • 是否解决

    基本解决。例如元素相对页面大小按比例固定放置的需求,使用 CSS 的 height 和 position:fixed 实现。加粗导师姓名的需求,用 Vue.js 的 v-if 完成。也有一些想法由于时间因素没能实现,比如将按钮设置为鼠标滑过时才显示。

  • 有何收获

    对各种语言在前端开发中的分工更加清楚了。积累了一些关于页面布局的碎片知识(拿去微调了一下博客……)。

问题 4

  • 问题描述

    难以实现关联树的查找和整合。

  • 做过哪些尝试

    直接编写代码,发现结点结构不太合适。修改后发现对结点赋值始终失败,改了半天终于实现了一个单层的嵌套。本想作罢,突然想起来有个东西叫递归……

  • 是否解决

    是。(不过实现得晚,可能 bug 比较多)

  • 有何收获

    意识到不碰算法题久了,不管编程能力还是思路都比较生疏,需要多加练习。另外对 JavaScript 函数的参数传递有了一定的认识。

问题 5

  • 问题描述

    文件上传功能无法识别文件编码,传入 GB 码文件可能造成乱码。

  • 做过哪些尝试

    搜索博客和 HTML5 教程。

  • 是否解决

    否。由于这个功能做得比较晚而且优先度不高,根据查到的资料看也比较麻烦,选择放弃,赶快去写博客了。

  • 有何收获

    呃……学会取舍?

问题 6

  • 问题描述

    在 html 单元测试方面,我们并没有学习和操作过,以及对于单元测试函数的编写及其意义的不明白。

  • 做过哪些尝试

    有一些人说 mocha 别在全局环境下配置,有些人又说 mocha 最好在全局环境下配置,综合来看,各有各的好处。最后的解决办法也就是面向百度去学习和操作,多个教程之间来回切换。就很痛苦。

  • 是否解决

    应该算是解决了,但只是实现了单元测试的基本功能,mocha 的异步测试等还未测试,但是已经有了基本的了解。

  • 有何收获

    感觉经过这一次和上一次的单元测试方面,我们更加清楚了单元测试的重要性,以及在编码时就要考虑对未来单元测试人员友好一点。

评价你的队友

对黄泷:

  • 值得学习的地方

    执行力很强,不轻言放弃,什么活都能干。(我不想干的事都完成得非常好……)而且特别好沟通。你永远可以相信 HL!

  • 需要改进的地方

    真的想不出啥啊……当然技术能力我们都需要进步。硬要说的话,建议在不太重要的目标耗去太多时间时适当考虑放弃,没必要过于纠结。

对黄明浩:

  • 值得学习的地方

    挺佩服队友的学习能力,对于新知识的快速吸收和实践。还有就是他对于功能的提出以及一些非常棒的 idea。

  • 需要改进的地方

    他需要改进的地方同样也是我需要改进的地方,可能我们都需要去接触和提高于计算机方面的知识,如果不能做到面面俱到,那起码对于某个方面多多少少了解一些。无论是现在还是以后,都要不断的更新扩展知识,不局限于课内所学所教的。

总结

黄泷:

对于本次作业,更让我清楚了自己的弱小(就是菜),特别是前端开发。

对于网页设计方面,经过本次学习,不仅对 html、css、js等前端基础功有了大致了解,还对 d3、ztree、element 等组件库有了较充分的学习理解,特别是 element。

对于个人学习方法方面,以前我都是对于新知识进行较系统的学习,大概就是先学习理论知识,再去实践操作。但在本次作业的过程中,由于对前端知识的一无所知,几乎从 0 学习的情况下,发现套用以前的学习方法,不是很行。然后逐渐变成了一边写代码,一边学需要的知识,感觉效率变高了许多。奇妙的学习方法又增加了。

总的来说,本次结对作业虽然队友是个大佬让我轻松了很多,但是还是学习了许多实践开发方面的知识,无论是方法还是想法,都扩宽了我以前看问题的视角。感觉与一名合格的程序猿又更近了一步。

黄明浩

这几次作业,都可以说是在短时间内“面向百度编程”、“挑战不可能”,而且结果相对还算不错。偶尔也会想:现在这么忙,是不是平常没事就不学技术了,等到接到项目再“做中学”就行了呗?看起来都来得及嘛。但其实仔细回想这次作业,恐怕并不是这样。因为我完全不了解前端,因此到作业完成了,我依然不理解在我的项目中,哪一些功能是原生 HTML、JavaScript 实现的,哪些是 Vue.js 实现的,哪些又是 Element 实现的。相应地,查找资料时,我也只能是随缘乱查,查到了很幸运,查不到可能又浪费几个小时。也就是说,因为没有系统学习技术,导致对自己的能力(包括开发和学习)没办法给出准确的估计,从而实现的时候变数很大。

另外这几次作业下来,我不太习惯的一点就是明明程序能跑了,看着 bug 也不多了,为什么任务还没有结束?要写博客、写 PSP 表格、写文档、写单元测试……真有些扫兴,不像 OJ 上 AC 那样有最纯粹的快感。但多写几次慢慢也习惯了,而且也体会到了它们确实是必要的。一个相对复杂的软件项目,不写注释、不做模块化、不做单元测试,或许自己是爽了,其他开发和维护人员呢?这回在网上查找可用的库,看到有的代码似乎很契合但不带注释又特别长,只能不爽地另寻他法。队友看我的代码,问了一连串“这是啥”,我想他的心情和我差不多吧。这次进度分配我们倒是做得还不错,实际开发阶段基本上每天都会列出几个估计当天可以完成的功能目标并加以实现。(不过 PSP 表格感觉真的不是非常好用……)

接下来的作业,应该基本上都是团队项目了。显然更加复杂,各方面的难度也高得多。希望我能凭借这几次作业的经验,以更加积极的心态面对吧。加油!

posted @ 2020-10-11 23:55  HL1123  阅读(257)  评论(0编辑  收藏  举报