软工实践寒假作业(2/2)

这个作业属于哪个课程 2021春软件工程实践¦S班 (福州大学)
这个作业要求在哪里 软工实践寒假作业(2/2)
这个作业的目标 阅读《构建之法》并提出问题、使用GitHub提交项目、完成WordCount程序并测试
作业正文 正文
其他参考文献 博客园、CSDN、简书、百度百科

GitHub链接
项目链接

part1:阅读《构建之法》并提问

问题1

在书中61页,作者用魔方的例子讲诉了“精通”二字的含义。

但我们真的有必要精通一门编程语言吗,以下摘自知乎平台的一份回答

精通一门语言的最直接方法是研究和解析目标语言的编译原则。想精通太难,而且作为一个大学毕业的学生,执着于精通一门语言其实意义并不是很大。

在我过去三年的学习中,不要说精通,就连掌握一门语言的标准都没有达到。拿java来说,作为这次博客作业的编程语言,我花了不少时间去重新学习了map,hashmap,treemap三种数据结构对应的方法,以及他们之间的关系。而精通java不仅要了解底层实现,还要有广阔的技术视野,想必要花很多时间精力去学习。
我认为不必追求精通一门编程语言,但要不断扩展自己的“学习区”,强化基础知识,打牢基础。

问题2

在书中93页,作者主张在最外层,即行为和后果层给与他人反馈。作者的理由是前两层反馈难以改变,而最外层的行为和后果是可以被改正和弥补的。
此外,作者也提到三明治式的反馈是容易被人接受的。

但我认为这种方式有时不足以引起他人的重视。虽然三明治式的反馈确实让人容易接受,但也容易让别人不以为然,认为他犯的的错误只是小问题,拿过去的一次合作经历说,有些队员喜欢“划水”,队长不断地催促他们完成自己的任务,最后上交的成果并不好。如果给与他人的反馈,大多都在评论别人三种层次中的行为后果层,他或许只是这一次改正,但再次出现问题时,他还是会重蹈覆辙,所以我会考虑进一步在行为动机层上告诉他问题所在,这样会不会取得更好的效果呢?

问题3

在看第八章需求分析的时候,我突然萌生了一个问题,这里的需求与软件的发展有冲突怎么办?

例如手机上有一个夸克浏览器,过去我因为其简洁的界面一直把它当作手机浏览器的首选,但是在以后的更新中,夸克浏览器不但内置了语音助手,云存储,甚至有了它自己的ui。当然,浏览器多功能化是它的发展趋势,但与此同时也会与用户需求相矛盾。在视频网站bilibili上我也多次看到,许多up主吐槽夸克浏览器“忘记初心”,而且卸载了该浏览器。如果说软件的发展会影响对该软件的需求,那我们应该怎样平衡这种冲突呢?

问题4

26页讲述了单元测试的一些标准,好的单元测试在覆盖率,或是一些边缘,异常情况下,都要有好的表现。正如书中所说,代码覆盖率100%也不等同于100%正确。

但单元测试可否帮助提高程序的性能呢?还是说单元测试仅仅是用于验证正确性?
单元测试中有时间工具,可判断程序运行时间,有时我也会借此来估计程序的运行速度是否达到预期,而且在单元测试中,一些问题可能是由于数据结构不合理引发的,这时可以通过优化数据结构,在提高正确性的同时也提高了程序性能。

问题5

在49页,作者提到了软件工程师成长的第一点,是积累软件开发相关的知识,提升技术技能。其中包括一些编程语言,设备驱动程序,内核调试器的掌握以及对某一开发平台的掌握。

但是经过了大学三年,我发现自己好像学的太少了。对设备驱动程序和开发平台没有过多的了解,太久没用的编程语言也会忘记一些知识。所以我对于第一阶段的学习还远没有结束,我也不清楚自己要掌握了哪些具体技术之后,才算完成了初级软件工程师的第一阶段,以及我需要花费大概多少时间来学习这些技能。毕竟大四面临毕业,怎样在有限的时间内让自己在软件工程的领域内掌握足够的知识?

附加题

蓝牙的名字来源于10世纪丹麦国王Harald Blatand——英译为Harold Bluetooth(因为他十分喜欢吃蓝梅,所以牙齿每天都带着蓝色)。在行业协会筹备阶段,需要一个极具有表现力的名字来命名这项高新技术。行业组织人员,在经过一夜关于欧洲历史和未来无限技术发展的讨论后,有些人认为用Blatand国王的名字命名再合适不过了。Blatand国王将现在的挪威,瑞典和丹麦统一起来;他的口齿伶俐,善于交际,就如同这项即将面世的技术,技术将被定义为允许不同工业领域之间的协调工作,保持着个各系统领域之间的良好交流,例如计算,手机和汽车行业之间的工作。名字于是就这么定下来了。

part2:WordCount编程

Github项目地址

GitHub链接
项目链接

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时 实际耗时
Planning 计划 1h 0.5h
• Estimate • 估计这个任务需要多少时间 1h 0.5h
Development 开发 8h 8h
• Analysis • 需求分析 (包括学习新技术) 3h 2h
• Design Spec • 生成设计文档 15min 15min
• Design Review • 设计复审 30min 1h
• Coding Standard • 代码规范 (为目前的开发制定合适的规范) 30min 30min
• Design • 具体设计 1h 2h
• Coding • 具体编码 4h 4.5h
• Code Review • 代码复审 30min 15min
• Test • 测试(自我测试,修改代码,提交修改) 1h 1.5h
Reporting 报告 3h 4h
• Test Repor • 测试报告 15min 15min
• Size Measurement • 计算工作量 0.5h 15min
• Postmortem & Process Improvement Plan • 事后总结, 并提出过程改进计划 0.5h 0.5h
合计 25h 26h

解题思路

  • 输入方式:命令行
  • 输入内容:两个文本的路径(相对路径或绝对路径),其中,前一个文本为待读取文本,后一个文本为待输出文本
  • 输出内容:待输出文本中的内容,包括:
  1. 统计字符数
  2. 统计单词数
  3. 统计有效行数
  4. 统计最多的10个单词及其词频

首先我先不考虑用命令行的输入方式,而是手动输入两个文件的路径。输入文件路径后,下一步要读取文本并对文本进行分析统计,读取文本不难,可创建一个用于打开文件并分析文件的类,在该类中写一些静态的方法(实践中发现创建静态方法并不是最优选择)。下面对4种统计要求进行分析:

  1. 统计字符数:可在打开文件读取内容时顺带执行
  2. 统计单词数:使用正则表达式匹配(实践中发现效率不高。已舍弃)
  3. 统计有效行数:使用正则表达式匹配(实践中发现效率不高。已舍弃)
  4. 统计最多的10个单词及其词频:利用统计单词数时的数据进行排序

最后将统计结果按照规定格式写进输出文本即可。

代码规范制定链接

代码规范

设计与实现过程

  • Wordcount
    用于获取命令行的文本路径,主函数的运行以及调用Lib类。
  • Lib
    用于读写文本并对文本内容进行分析统计。

Lib类中的函数

  • readFileByChars() —— 打开文件并统计字符数,单词数,有效行数,并保存单词及其频率

利用FileReader逐个读入字符并统计字符数

            fr = new FileReader(inputFilePath);
            int ch;
            while ((ch = fr.read()) != -1) {
            charsNumber++;

由于单词至少有4个字母开头,且之后可跟数字,所以可理解为单词的开头是4个字母,单词是由于非字母数字结束的(单词不会因为数字而结束,因为小于4个字母开头构不成单词),基于此设计算法。
单词的判断算法:

  1. 设定一个暂存字符串,规定暂存字符串只有三种类型:为空,由1~3个字母组成,为一个单词。(判断暂存字符串是否为单词的方法:长度是否大于三)
  2. 不断读入字符,判断该字符的类型。
  3. 字符类型为字母,加入暂存字符串。
  4. 字符类型为非字母非数字,清空暂存字符串,判断暂存字符串是否为单词,若是则记录。
  5. 字符类型为数字,判断暂存字符串是否为单词,若不是则清空暂存字符串,若是则加入暂存字符串。
  6. 读入字符结束,判断暂存字符串是否为单词,若是则记录。


添加单词进hashmap(需判断其是否存在)

          if (tempWord.length() > 3) {
              //若没有则添加,若有则加一
              word = tempWord.toString().toLowerCase();
              wordsCount.merge(word, 1, Integer::sum);

由于换行一定有一个回车键,只需判断在两个回车键之间是否出现过非空字符,基于此设计算法。
有效行的判断算法:

  1. 设定一个布尔数a为假,代表该行没有非空字符。
  2. 不断读入字符,判断该字符的类型。
  3. 若字符不为回车,也不为空白字符,且该行没有非空字符,则将a置为真。
  4. 若字符为回车,判断该行有无非空字符,若有则有效行加一,不管结果如何a都置为假。
  5. 读入字符结束,判断该行有无非空字符,若有则有效行加一。
          if (ch == 10) {
              if (isValued) {
                  lineNumber++;
              }
              isValued = false;
          } else {
              if (!isValued && !isBlankCharFuc(ch)) {
                  isValued = true;
              }
          }
  • countWords() —— 对单词按照频率进行排序
    利用stream可以同时实现:
  1. 按频率排序
  2. 再按key值大小排序
  3. 截取前十个元素
        wordsCount = getWordsCount()
                .entrySet()
                .stream()
                .sorted(Map.Entry
                        .<String, Integer>comparingByValue()
                        .reversed()
                        .thenComparing(Map.Entry.comparingByKey()))
                .limit(10)
                .collect(Collectors
                        .toMap(Map.Entry::getKey
                                , Map.Entry::getValue
                                , (e1, e2) -> e2
                                , LinkedHashMap::new));
  • writeFile() —— 写入文件
    遍历hashmap
        for (Map.Entry<String, Integer> entry : wordsCount.entrySet()) {
            outputString.append(entry.getKey())
                    .append(": ")
                    .append(entry.getValue())
                    .append('\n');
        }

利用BufferedOutputStream写入文件

  fos = new FileOutputStream(outputFilePath);
            bos = new BufferedOutputStream(fos);
            //设置编码为utf-8
            byte[] bytes = outputString.toString().getBytes(StandardCharsets.UTF_8);
            bos.write(bytes, 0, bytes.length);
  • getCharsNumber() —— 获取字符数
  • getWordNumber() —— 获取单词数
  • getLineNumber() —— 获取有效行数

性能改进

  • 一开始我想用静态方法实现这些函数,但是每个方法都要传递参数,有的甚至要四五个参数,例如写文件时要给函数文件路径,单词数,有效行数等等,而且静态函数有时需要返回多个值,综上考虑后舍弃静态方法,而是在类中添加了若干成员变量。
  • 将打开读取文件与统计字符,统计单词,统计有效行一起执行,做到只读取一次文件内容。之前我是把文件内容存进String中,后来想到,万一这个String很大很大,在统计单词与有效行的时候需要一遍又一遍地遍历,十分花时间,干脆只读取一次。
  • 舍弃正则匹配有效行数与单词数。利用算法可以在读取字符时顺带统计。
  • hashmap排序的stream用法。

单元测试

测试统计字符数函数

由于不考虑中文,仅用0~127号ascii码测试,结果为128,符合预期

        StringBuilder text= new StringBuilder();
        for(int i=0; i<128; i++) {
            text.append((char) i);
        }

测试统计单词与提取频率最高的10个单词函数

测试统计单词:设计了一个字符串,其中包含5个合法单词,并让字符串重复添加五遍,总单词数为25,符合预期

        StringBuilder text= new StringBuilder();
        String words="123a;a15;A81qqs;axSx1651;1888asadw;QDWQ156;1535;awwwwc;ACWa;acwa\n";

        for(int i =0; i < 5; i++) {
            text.append(words);
        }

测试提取频率最高的10个单词

测试了单词全小写,相同频率单词的排序,结果符合预期

String[] answerKey={"acwa", "awwwwc", "axsx1651", "qdwq156"};
        Integer[] answerValue = {10, 5, 5, 5};
        int i=0;
        for (Map.Entry<String, Integer> entry : testLib.wordsCount.entrySet()){
            Assert.assertEquals(entry.getKey(), answerKey[i]);
            Assert.assertEquals(entry.getValue(), answerValue[i]);
            i++;
        }

测试有效行函数

测试包括多行空行,单行空行,单行中仅包含空白字符,结果为4,符合预期

String text="\ncs\n61561\n\n\n156\n\t\n615\n\n";

覆盖率截图

提高覆盖率方法:可通过对各个函数都进行测试,有分支的情况如if需要将每种情况测试一遍。

异常处理说明

包含两个部分:命令行传递参数不为2,文件读写错误

  • 若参数不为2则退出程序:
        if (args.length != 2) {
            System.out.print("该程序只接受两个参数,待读取文件与待写入文件,程序关闭。。。");
            System.exit(0);
        }
  • 待读写文件不存在则显示错误信息
  catch (IOException e) {
                e.printStackTrace();
                System.exit(0);
            }

心路历程与收获

  • 这次作业相较于上次作业来说,工作量明显增加了,作业的要求写得十分详细,但即便如此我做起来仍然有些费劲,感觉把java重新学了一遍。
  • 之前我没有这么系统的用github提交一个项目,这次作业加深了我对项目管理的认识。
  • 重新读了一遍《构建之法》,对软件工程的理解更加深刻了,但还没有用于实践,希望能够尽快学以致用。
  • 我之前用的ide是eclipse,现在换成了idea,对很多功能不是很了解,例如JUnit4的使用,通过这次作业让我学习很多。
posted @ 2021-03-05 19:03  福贵的老牛  阅读(97)  评论(6编辑  收藏  举报