2020软件工程第四次作业(结对编程)
2020软件工程第四次作业(结对编程)
-
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/SE2020 这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/SE2020/homework/11277 这个作业的目标 学习前端网页制作的基础知识,以及资料查找效率与学习方法提升 结对同学博客链接 https://www.cnblogs.com/czy22655/p/13799947.html 本作业博客链接 https://www.cnblogs.com/blogwuhe/p/13795619.html GitHub地址 https://github.com/rebuilder945/031802127-031802106 学号 031802127 031802106
一、具体分工
- 我负责编码部分,队友负责编写测试用例并测试,以及完善页面效果。
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 120 |
Estimate | 估计这个任务需要多少时间 | 60 | 40 |
Development | 开发 | 300 | 400 |
Analysis | 需求分析 (包括学习新技术) | 60 | 120 |
Design Spec | 生成设计文档 | 60 | 50 |
Design Review | 设计复审 | 50 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 120 | 120 |
Coding | 具体编码 | 800 | 690 |
Code Review | 代码复审 | 100 | 100 |
Test | 测试(自我测试,修改代码,提交修改) | 100 | 100 |
Reporting | 报告 | 200 | 250 |
Test Report | 测试报告 | 60 | 60 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 50 | 60 |
2 | 合计 | 2020 | 2210 |
三、解题思路描述与设计实现说明
解题思路:
主要分为两大部分:
-
格式化用户输入为Json对象
-
将所得到的Json对象可视化
首先将最终完整的程序流程展示如下:
思考过程:
- 网页知识学习问题:
我们都是没有前端经验的人,一开始将太多时间纠结于网页的实现过程了,我们花了大量时间来学习前端的知识。但发现这样实际是事倍功半的,且不说学习js、css、html的难度,也不谈学习的时间成本,关键在于先学后做的方式,使得解题实现过程中的很多问题没法在前面暴露出来,而前面学习过程中又无法有针对性地预测并学习这些未接触过、无法获知的问题,导致时间浪费,于是这样的方式显得愚蠢。所以我们在了解了基本网页知识后,就有针对性的寻找解题的途径。
- 树图的实现方式的寻找:
一开始是一头雾水,要自己一点点地画出来吗?那样的工作量以及学习成本,我们是没有办法完成的。bootstap、jquery等等又不知从何下手。问题关键在于将数据可视化处理成一棵树结构,那么有没有什么类似的库可以做到呢?
我们查询资料的方向变成了“如何用js实现树形图”,最终在网络上找到了一个类似的项目(点击查看),可以将根据json文件内容生成树形图,仔细研究该项目的代码,发现生成树图的核心代码来自一个文件:d3.js,由于该文件代码量达到近1w行,于是猜测这就是我们所寻找的js数据可视化库,摘取相关代码测试并查找资料后验证了我们的猜想,于是一切变得简单起来了。
实现过程:
我们引入d3.js库,将该项目的一部分实现树形图的代码封装成函数draw()以及display(),改变读入json的方式,并做适当修改,重用到我们的程序之中。
因此接下来的工作就是解析用户输入。
解析用户输入:
将所给字符串格式化为json格式,需要依赖js中的对象,将文本内容所表示的含义结构化成对象,即可转化为json对象。
实现的函数为strParse(),代码如下:
function strParse() {
var tree = {
"name": '师门树',
"children": [
]
};
var teacher = {
"name": '',
"children": [
]
};
var degree = {
"name": '',
"children": [
]
}
var person = {
"name": '',
"children": [
]
}
var job = {
"name": '',
"children": [
]
}
var year = {
"name": '',
"children": [
]
}
//tree.children.push(teacher);
//tree.children.push(teacher);
//alert(JSON.stringify(tree));
var name = new String;
name = document.getElementById("textbox").value;
var num = 0;
var StuParts = [];
var teachers = name.split(/\n\n\n/g); //按导师分块
for (var i in teachers) {
StuParts[num] = teachers[i].split(/[\n, \n\n]/g); //导师
//内按行分块
delItem("", StuParts[num]); //取消空行
num++;
}
for (var i in StuParts) {
var k = -1;
var degrees = "";
var years = "";
var jobs = "";
var name = "";
var persons = [];
//parse persons first to be selected
for (var j = 1; j < StuParts[i].length; j++) {
k = -1;
jobs = "";
name = "";
if (!(StuParts[i][j][0] >= '0' && StuParts[i][j][0] <= '9')) {
while (StuParts[i][j][++k] != ":") //person name parse
{
name += StuParts[i][j][k];
}
person.name = name;
while (++k < StuParts[i][j].length) //job parse
{
if (StuParts[i][j][k] != '、') {
jobs += StuParts[i][j][k];
} else {
jobs = {
"name": jobs
};
person.children.push(jobs); //job insert
jobs = "";
}
}
jobs = {
"name": jobs
};
person.children.push(jobs);
jobs = "";
persons.push(person); //person union
var person = {
"name": '',
"children": [
]
}
}
}
//the above OK
//then parse degrees
for (var j = 1; j < StuParts[i].length; j++) {
k = -1;
degrees = "";
years = "";
name = "";
if (StuParts[i][j][0] >= '0' && StuParts[i][j][0] <= '9') {
while (StuParts[i][j][++k] != "级") { //years
years += StuParts[i][j][k];
}
while (StuParts[i][j][++k] != ":") { //degrees
degrees += StuParts[i][j][k];
}
year.name = years;
degree.name = degrees;
//person parse and insert year
while (++k < StuParts[i][j].length) {
if (StuParts[i][j][k] != '、') {
name += StuParts[i][j][k];
} else {
var flag = true;
for (var o in persons) {
if (persons[o].name == name) {
year.children.push(persons[o]);
flag = false;
break;
}
}
if (flag == true) {
person = {
"name": '',
"children": [
]
}
person.name = name;
year.children.push(person);
}
name = "";
}
} {
var flag = true;
for (var o in persons) {
if (persons[o].name == name) {
year.children.push(persons[o]);
flag = false;
break;
}
}
if (flag == true) {
person = {
"name": '',
"children": [
]
}
person.name = name;
year.children.push(person);
}
name = "";
}
//year insert degree and degree insert teacher
var flag = true;
for (var o in teacher.children) { //judge if degree `d in teacher
if (teacher.children[o].name == degree.name) {
teacher.children[o].children.push(year);
flag = false;
break;
}
}
if (flag == true) {
degree.children.push(year);
teacher.children.push(degree)
}
year = {
"name": '',
"children": [
]
}
degree = {
"name": '',
"children": [
]
}
}
}
var x = StuParts[i][0].indexOf(":");
var teacherName = "";
while (++x < StuParts[i][0].length) {
teacherName += StuParts[i][0][x];
}
teacher.name = teacherName;
tree.children.push(teacher);
teacher = {
"name": '',
"children": [
]
};
}
ijson = JSON.stringify(tree);
sessionStorage.setItem("x", ijson);
location.reload();
}
最后须将解析后的对象tree存入页面缓存sessionStorage,以使刷新网页时能够保留tree数据,再次加载html能够更新所需树形图。
对于题目中所提到的多棵树并存,由于是以对象的形式创建的,根节点是师门树,因此问题是一致的,多棵树都归根于师门树,这样整个解题过程就基本完成了,剩下的就是对页面的使用体验优化,以及bug修改。可以点这里查看项目在这个时候的版本:
此时页面如下(页面经缩放):
页面优化:
-
我们专注于页面用户体验,对页面输入做了美化,也修改了一些bug,最终的版本点这里
-
最终页面效果显示如下(页面经缩放):
四、附加特点设计与展示
支持动态添加任意节点信息:
在已显示父节点情况下,在目录结构中添加其的子节点,可以是任何信息描述,用以扩充信息;
- 意义:方便用户补充遗漏信息,即使输入失误,也可以很快地重新修改;
演示如下:
- 在目录结构中点击添加节点:
- 点击树图节点可以缩放显示/隐藏节点:
支持上传txt格式文件作为数据输入:
将文件拖入框内即可,点击生成;
- 意义:提供多种方式以适应不同用户输入习惯以及使用需求,对于长文本可采用文件输入,对于短文本采用文本框输入。
演示如下:
- 点击输入数据按钮,跳出输入数据框(可拖动):
- 拖动文件上传数据:
五、目录说明和使用说明
目录说明:
- 目录结构如图所示:
-
其中main文件夹中为主页面及相关js库,UniTest文件夹下为单元测试程序以及相关库。
-
其中lookme.md为版本迭代说明,readme.md为使用说明。
使用说明:
1、下载压缩包;
2、直接打开main/main.html即可;
3、按照页面提示操作(直接输入数据或者上传txt文件),具体如下:
- 生成:
- 将文本复制粘贴进框内,或上传txt文件(将文件拖入相应框内),若同时进行,则按txt文件内容生成;
- 点击生成按钮;
- 生成树图;
- 查看树图:
- 点击树图节点以缩放查看父节点或节点信息
- 添加节点:
-
在目录结构框中,鼠标移至要添加节点的父节点位置,出现加号按钮,点击
-
在弹出的消息框中输入要加入的节点的名称,点击确定
-
可以看到新节点加入
注意:不可在父节点未在树图中显示情况下添加其子节点,这样做是无效的
六、单元测试
测试工具及使用方法:
-
使用Qunit-2.11.3(点击查看详细介绍)进行测试,Unitest/Qunit-2.11.3.js以及Unitest/Qunit-2.11.3.css为相关文件;
-
Qunit使用方法:将js与css文件引入测试程序Unitest.html,将待测函数strParse()引入,即可使用Qunit框架进行单元测试,并生成可视化数据,详细代码见下方;
-
采用基本的判定覆盖进行单元测试,由于draw()和display()是实现网页显示的函数,无返回结果,且程序核心函数为strParse(),只要该函数返回json对象结果正确,网页显示即可正确。故仅对strParse()函数进行测试;
-
为尽可能考虑所有输入情况,设置10个测试用例以及相应测试预期值,并保存在Unitest/testCase.json文件中,方便用户设计测试用例,由单元测试程序Unitest.html读取并测试。
主要代码:
<script>
$.getJSON("./testCase.json", function (data) { //从testCase.json中获取10个测试用例并测试
var testCase1 = data.testCase[0];
var testCase1_Expected = JSON.stringify(data.testCase_Expected[0]);
var testCase2 = data.testCase[1];
var testCase2_Expected = JSON.stringify(data.testCase_Expected[1]);
var testCase3 = data.testCase[2];
var testCase3_Expected = JSON.stringify(data.testCase_Expected[2]);
var testCase4 = data.testCase[3];
var testCase4_Expected = JSON.stringify(data.testCase_Expected[3]);
var testCase5 = data.testCase[4];
var testCase5_Expected = JSON.stringify(data.testCase_Expected[4]);
var testCase6 = data.testCase[5];
var testCase6_Expected = JSON.stringify(data.testCase_Expected[5]);
var testCase7 = data.testCase[6];
var testCase7_Expected = JSON.stringify(data.testCase_Expected[6]);
var testCase8 = data.testCase[7];
var testCase8_Expected = JSON.stringify(data.testCase_Expected[7]);
var testCase9 = data.testCase[8];
var testCase9_Expected = JSON.stringify(data.testCase_Expected[8]);
var testCase10 = data.testCase[9];
var testCase10_Expected = JSON.stringify(data.testCase_Expected[9]);
QUnit.test("strParse() test", function (assert) {
assert.equal(strParse_Test(testCase1), testCase1_Expected);
assert.equal(strParse_Test(testCase2), testCase2_Expected);
assert.equal(strParse_Test(testCase3), testCase3_Expected);
assert.equal(strParse_Test(testCase4), testCase4_Expected);
assert.equal(strParse_Test(testCase5), testCase5_Expected);
assert.equal(strParse_Test(testCase6), testCase6_Expected);
assert.equal(strParse_Test(testCase7), testCase7_Expected);
assert.equal(strParse_Test(testCase8), testCase8_Expected);
assert.equal(strParse_Test(testCase9), testCase9_Expected);
assert.equal(strParse_Test(testCase10), testCase10_Expected);
})
})
</script>
Qunit还包含其他测试函数及测试方法,此处仅使用assert()函数进行断言测试,其他从略。根据输入值经过函数得到返回值,检测与预期值是否相符即可。
测试结果:
七、Github的代码签入记录截图
- 每一次commit是以文件夹上传的,在里面都包含了一个lookme.md文档,用以记录当次上传的更新与待解决问题,详细见之。
八、遇到的代码模块异常或结对困难及解决方法
文本解析问题:
将文本转换成纯字符串硬解析,还是有什么取巧的办法(库)呢?在思考过后,还是决定硬解析,即strParse()函数。在解析过程中发现,文本框直接读入的字符串比较特殊,一开始缺乏对多余的'\n' 以及 “” 的考虑,浪费很多时间;
- 解决办法:将异常字符单独对其进行处理;
txt文件上传解析问题:
txt文件上传的内容,一开始以为和文本框输入一样,事实并非。因而bug总是让人认识到自己还是太年轻,在一次次调试bug中成长。期间发现txt上传的内容转化为字符串后,其中的换行符变为了 "\\r\\n" ,这里的调试时间也较长;
- 解决办法:异常字符单独处理;
页面更新问题与json传输方式问题:
由于用户需要在输入完成后,点击生成按钮来解析并生成,故需要页面的更新。这也困扰我良久,是只更新树图部分,还是刷新整个页面呢?由于我对js尚不熟悉,尝试多次仅更新树图部分失败后,决定采用页面刷新的方式。然而,要如何将解析后的数据在刷新过程中保存下来呢?一开始考虑存到本地文件,刷新时读取,相关操作的浏览器不兼容问题导致无法真正存入本地文件;
- 解决办法:最后采用将解析后的数据存入页面缓存(只在退出页面时释放),并刷新的方式,来获取页面信息的更新。
九、评价你的队友
- 值得学习的地方:他对于问题的解决有独到的方式,有时候不失为一种解决办法——向专业人士请教,这是我常常忽略的;
- 需要改进的地方:要更加注重实践,善用搜索引擎,积极动手编码。
十、总结
- 能力有限,没时间继续完善,这是个遗憾。但最重要的收获,是资料查询效率以及学习能力的提升。