2020软件工程第四次作业(结对编程)

2020软件工程第四次作业(结对编程)

一、具体分工

  • 我负责编码部分,队友负责编写测试用例并测试,以及完善页面效果。

二、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文件),具体如下:

  • 生成:
  1. 将文本复制粘贴进框内,或上传txt文件(将文件拖入相应框内),若同时进行,则按txt文件内容生成
  2. 点击生成按钮;
  3. 生成树图
  • 查看树图:
  1. 点击树图节点以缩放查看父节点或节点信息
  • 添加节点:
  1. 目录结构框中,鼠标移至要添加节点的父节点位置,出现加号按钮,点击

  2. 在弹出的消息框输入要加入的节点的名称,点击确定

  3. 可以看到新节点加入

    注意:不可在父节点未在树图中显示情况下添加其子节点,这样做是无效的

六、单元测试

测试工具及使用方法:

  • 使用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尚不熟悉,尝试多次仅更新树图部分失败后,决定采用页面刷新的方式。然而,要如何将解析后的数据在刷新过程中保存下来呢?一开始考虑存到本地文件,刷新时读取,相关操作的浏览器不兼容问题导致无法真正存入本地文件;

  • 解决办法:最后采用将解析后的数据存入页面缓存(只在退出页面时释放),并刷新的方式,来获取页面信息的更新。

九、评价你的队友

  • 值得学习的地方:他对于问题的解决有独到的方式,有时候不失为一种解决办法——向专业人士请教,这是我常常忽略的;
  • 需要改进的地方:要更加注重实践,善用搜索引擎,积极动手编码。

十、总结

  • 能力有限,没时间继续完善,这是个遗憾。但最重要的收获,是资料查询效率以及学习能力的提升。
posted @ 2020-10-11 23:36  吴何  阅读(156)  评论(0编辑  收藏  举报