欢迎来到潘紫盈的博客

北冥有鱼,其名为鲲。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。

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

寒假作业(2/2)

这个作业属于哪个课程 2021春软件工程实践|W班
这个作业要求在哪里 软工实践寒假作业(2/2)
这个作业的目标 对《构建之法》进行深入思考、学会用github管理代码、制定我的代码规范、学习使用PSP表格估计开发时间、完成词频统计程序的开发与测试
其他参考文献 CSDN、B站

目录:

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

要求:快速看完整部教材(教材开学会发,可以先看邹欣老师的博客园讲义),列出你仍然不懂的5到10个问题,发布在你的个人博客上。如何提出有价值的问题? 请看这个文章,以及在互联网时代如何提问题

问题1

我看了 第3章 的 过早扩大化/泛化

有些软件本来是解决一个特定环境下的具体问题,有的程序员一想, 我们能不能做一个平台,处理所有类似的问题,这样多好啊!这样的前景的确美妙,程序员的确需要这样的凌云壮志,但是要了解必要性、难度和时机。“画扇面”就是一个很好的例子。

有这个问题:软件进行扩大化/泛化的时机是什么时候,如何避免扩大化泛化?
暂时没有查到相关资料,

所以还是参考教材中的内容

Linux成功的例子:

I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for386(486) AT clones...

管理学大师彼得●德鲁克的忠告:

Those entrepreneurs who start out with the idea that they'll make it big - and in a hurry -can be guaranteed failure."

这应该是告诉我们,不要在一开始就定下太大的目标,一开始就想一步到位开发出很泛化的软件。要做出成功的泛化的软件,最初还得从一个解决特定问题的小软件做起,这个软件要更新迭代成泛化的软件是需要一定的时间的,要先定下小目标把软件一步一步地做出来,软件进行泛化的时机才会出现。

问题2

我看了 第4章的 代码复审

“复审的目的在于:

(1)找出代码的错误。(2)发现逻辑错误(3)发现算法错误

(4)发现潜在的错误和回归性错误(5)发现可能改进的地方

(6)教育开发人员

有这个问题:

代码复审有一个比较重要的目的就是找出代码的各种错误,那么代码复审对找出错误能起到多大的作用?这里说的错误是指逻辑错误、潜在的错误、回归性错误。

因为就我的观察和根据我自己写代码经历,找出bug往往是因为使用程序时发现结果不对,或者测试时结果错误,才会有针对性地找到代码中的bug并修正。

我的困惑是,如果安排测试人员多进行一些测试,是否会比进行代码复审来说更有效率地找出代码错误呢?

我查了资料,有这些说法

  • 代码复审可以在前期发现问题,提高软件质量,降低软件成本。
  • 把代码评审作为提交代码前的必要工序去执行,可以让很多问题(例如熬夜做了不靠谱的架构决策、实习生用了不合理的设计模式)在软件最终发布前就被发现。

教材中也提到了代码复审的一些其他好处,代码复审应该相当于多加了一层保险,与测试并不冲突,而且程序员水平有高低,人也都会有失误的时候,所以代码复审是能修正一些代码问题的。

问题3

我看了 第5章 团队和流程

TSP的原则

5、制定切合实际的计划和承诺,团队计划要由负责具体执行的角色来制定(而不是从上级而来)。

有这个问题:为了制定切合实际的计划,如何估计开发过程各个阶段所需的时间,从而做到更合理地安排任务和推进进度?

团队共同完成一个项目,需要提前计划好每个人完成各阶段任务的时间。根据我的实践,我得到的经验是有时候我是很难估计开发一个功能需要多长时间的,因为项目中往往会有一些没做过、不懂得怎么实现的功能,这就需要查找资料,进行学习,而且也无法预料到会出现多少问题,所以我对如何制定切合实际的计划、如何估计开发时间比较困惑。

阅读教材,发现第8章 8.6 计划和估计 有对这个问题的讲解

  • 首先,估计项目各类工作是有一定难度的。文中提出要找出估计后面的假设、考虑团队自身的能力、参考前人的经验、使用快速原型法等等,对计划和估计进行了详细的阐述。

阅读过后,我对如何估计项目各类工作有了进一步的了解。

本次作业中也要求使用PSP表格,估计将在程序各模块开发所需耗费的时间,以及完成整个项目所需的时间,应该会对我学习估计项目各类工作有帮助。

问题4

我看了16章的 迷思之四:创新者都是一马当先

大部分成功的创新者都不是先行者

有这个问题:为什么大部分成功的创新者都不是先行者?

我之前一直以为第一个创新的人理所当然地会在领域中成功,因为他掌握了先机。

我查了资料,有这样的说法:

  • 许多先行者被模仿者和追赶者超越。模仿也可以被看做是创新的一个必经阶段,而创新的本质,也大多不是基于新技术,而是为了更贴近消费者,更为满足消费者的需求。比如说,模仿策略已经被娃哈哈视为致胜法宝,首先,它不需要去试探所谓“创新”的产品能不能创造出新的消费者,其次,它能在先行者的基础之上,根据市场需求进行有效的改进,再次,它能发挥自己所长,对先行者的软肋进行攻击。

问题5

我看了16章的 迷思之五:要成为领域的专家,才能创新

70%的创新者说,他们最成功的创新,是在他们的拿手领域之外发现的

有这个问题:为什么领域外的创新者反而会比领域的专家更有创意?

按照常理来说,领域内的人对该领域更了解,应该更容易创新,而领域外的人做出的创新肯定也有一部分,但是我以为从概率上看应该还是少数,不知道这个数据是如何统计出来的。

我查了资料,有这些说法:

  • 专家们对自己的领域太过了解熟悉,便很难去突破那些习惯、那些固有的想法做法。
  • 领域内的专家在专业知识上肯定是非常严谨的,因此有些与专业知识相悖的创意是会被否决的。

从作者给出的事例中,我也可以找到一些原因:

  • 领域专家往往在研究一些高深的技术,反而会注意不到一些实现简单的创新想法。

  • 领域专家对该领域内的一些判断非常自信,但是创新带来的影响是有时候是难以用既有的知识估计的。

附加题

大家知道了软件和软件工程的起源,请问软件工程发展的过程中有什么你觉得有趣的冷知识和故事?

程序中的bug的名称源自于"虫子"
在程序中bug一词用于技术错误。这一术语最初由爱迪生在1878年提出的,但当时并没有流行起来。
在这的几年之后,美国上将Grace Hopper在她的日志本中,写下了她在Mark II电脑上发现的一项bug。
不过实际上,她说的真的是“虫子”问题,因为一只蛾子被困在电脑的继电器中,导致电脑的操作无法正常运行。

bug的名称的由来很有趣,我想正是因为用“虫子”来形容软件中的漏洞很贴切,这个偶然使用的名称才会广为流传。

Part2:WordCount编程

在大数据环境下,搜索引擎,电商系统,服务平台,社交软件等,都会根据用户的输入来判断最近搜索最多的词语,从而分析当前热点,优化自己的服务。首先当然是统计出哪些词语被搜索的频率最高啦,请设计一个程序,能够满足一些词频统计的需求。

Github项目地址

WordCount项目

PSP表格

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

解题思路描述

(即刚开始拿到题目后,如何思考,如何找资料的过程。)

先考虑最难的词频统计怎么做,再根据题意考虑另外3个功能。

如何从文件输入、输出到文件?

上网查找资料,复习Java的文件输入输出,选择合适的输入输出流。按照题目要求的格式输出。

用什么数据结构来记录结果?

我认为使用Map很合适,key是单词,value是单词出现次数,符合统计词频的要求。

如何得到文本中的每一个单词?

最先想到的是很普通的方法,每次读一个字符,对字符进行判断,从而正确地对文本进行分割,找到合法的单词。但是这种方法比较麻烦,我想应该会有更简单的方法,就上网查找了资料,发现可以用String的split方法,配合正则表达式分割字符串。我接着查找了正则表达式的相关资料,学习了它的一些基础用法。因为题目中说非字符数字符号是分割符,所以使用正则表达式匹配到所有非字符数字符号,使用split方法就可以得到只由字母和数字组成的字符串。(这里把中文也当成分割符了,不过因为助教老师说输入不会出现中文,所以不会有影响。)

如何判断单词是否为合法单词?

使用正则表达式,匹配到至少以4个英文字母开头,后几位是字母和数字的单词。因为非字母数字符号都是分割符,所以认为合法单词应只包含数字和大小写字母。

另外3个功能比较简单,重要的是要根据题目的要求完成。

如何统计文件的字符数?

思路是写一个循环,用一个输入流每次读1个字符,记下总共读了多少个。(因为输入的都是ascii字符,所以不用再判断字符类型)

如何统计文件的有效行数?

写一个循环,用一个输入流每次读1行,使用String的trim方法去掉这一行头尾的空白字符,如果字符串长度变成0,说明它是空白字符串,否则有效行数+1。

如何统计文件的单词总数?

在做单词的词频统计时,已经可以通过一些操作得到文件的单词列表,把得到文件的单词列表的代码抽取出来作为一个函数,在此处调用函数获得单词列表,列表的长度即为单词总数。

代码规范制定链接

我的代码规范

设计与实现过程

计算模块接口的设计与实现过程。 设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(展示出项目关键代码),并解释思路,以及独到之处。

有Lib和WordCount两个类。

Lib类中有实现功能的函数,WordCount类用来接收命令行参数和使用Lib类。

Lib类有10个函数,函数的功能和函数之间的调用关系在如下的注释中说明:

/*构造函数,传入输入输出文件名*/
public Lib(String input,String output)

//主要功能
/*获取单词列表*/
public List<String> getWordList(String inputFile)
/*统计单词数,调用getWordList获取单词列表*/
public int countWords(String inputFile)
/*统计词频,调用getWordList获取单词列表*/
public ArrayList countWordFrequency(String inputFile)
/*按value排序Map,供getWordList函数调用*/
public ArrayList sortMap(Map<String,Integer> oldmap)
/*统计字符数*/
public int countChars(String inputFile)
/*统计有效行数*/
public int countLines(String inputFile)

//控制
/*开始计算*/
public void startCount()
/计算结果输出到文件
public void outputResult()
/*输出频率最高的10个单词和出现次数,用于测试*/
public void printWords(ArrayList<Map.Entry<String,Integer>> list)

关键代码

1、getWordList函数

算法的关键和独到之处是用正则表达式分割单词,匹配合法单词。

思路是一行一行读取文件,使用正则表达式,以分割符来分割出单词,把合法的单词放入单词列表。

private static final Pattern pattern = Pattern.compile("[a-zA-Z]{4}[a-zA-Z0-9]*");

BufferedReader br = new BufferedReader(new FileReader(inputFile));
String line = null;
//一行一行读取文件
while((line = br.readLine()) != null){
    //分割符:空格,非字母数字符号,以分割符来分割出单词
    String[] words = line.split("[^a-zA-Z0-9]");
    //单词:至少以4个英文字母开头
    for (String word : words){
        if (word.length() >= 4) {
            //正则表达式判断单词是否合法
            if (pattern.matcher(word).matches()){
                //统一转小写
                wordList.add(word.toLowerCase());
            }
        }
    }
}       

2、countWordFrequency函数

/*统计词频,只输出出现最多的10个*/
public ArrayList countWordFrequency(String inputFile)
{
    List<String> wordList = getWordList(inputFile);
    //key 单词  value 出现次数
    Map<String, Integer> words = new TreeMap<String,Integer>();

    //如果有这个单词 count ++
    for (String word : wordList){
        if (words.containsKey(word))
            words.put(word,words.get(word)+1);
        else {
            //如果map里没有这个单词,添加进去,count=1
            words.put(word, 1);
        }
    }
    //按值进行排序,sortMap是自定义的用来按value排序的函数
    ArrayList<Map.Entry<String,Integer>> list = sortMap(words);

    return list;
}

3、其他函数

其他函数的编写都参照解题思路中的描述。

countWords函数

List<String> wordList = getWordList(inputFile);
//单词列表的长度
int count = wordList.size();

countChars函数

BufferedReader br = new BufferedReader(new FileReader(inputFile));
//每次读一个字符
while ((br.read()) != -1)
    count++;

countLines函数

BufferedReader br = new BufferedReader(new FileReader(inputFile));
String line = "";
while((line = br.readLine()) != null){
    //判断是否为空白行
    if (!(line.trim().isEmpty()))
        count++;
}

性能改进

  • BufferedReader和BufferedWriter通过缓存机制增强了读取和存储数据的效率。

  • 使用正则表达式的预编译功能,可以有效加快正则匹配速度。
    即Pattern要定义为static final静态变量,以避免执行多次预编译。

性能测试

性能改进后程序执行速度有所提升,以下为测试的数据量以及花费的时间:

3000个单词(3 0000个字符) 约200ms

3 0000个单词(30 0000个字符) 约600ms

30 0000个单词(300 0000个字符) 约2s

300 0000个单词(3000 0000个字符) 约13s

alt test1

alt test2

alt test3

alt test4

单元测试

用4个函数分别测试4个功能,另外1个函数测试大量数据。

单元测试4个功能都使用了10个测试用例,均通过了测试。是因为写法都差不多,所以以下的代码只列出第1个测试样例。

1、testCountWords

/*测试有几个单词*/
@Test
public void testCountWords(){
    Lib lib = new Lib("input.txt","output.txt");
    Assert.assertEquals(5,lib.countWords("input1.txt"));
}

2、testCountChars

/*测试有几个字符*/
@Test
public void testCountChars() {
    Lib lib = new Lib("input.txt","output.txt");
    Assert.assertEquals(88,lib.countChars("input1.txt"));
}

3、testCountWordFrequency

因为这里输出的结果比较复杂,所以不使用Assert.assertEquals,而是直接打印出来观察是否正确。

/*测试词频统计*/
@Test
public void testCountWordFrequency(){
    Lib lib = new Lib("input.txt","output.txt");
    System.out.println("input1.txt");
    lib.printWords(lib.countWordFrequency("input1.txt"));
}

4、testCountLines

/*测试有几行*/
@Test
public void testCountLines(){
    Lib lib = new Lib("input.txt","output.txt");
    Assert.assertEquals(4,lib.countLines("input1.txt"));
}

5、大量数据

/*测试大量数据*/
@Test
public void TestMassiveData()
{
    try {
        BufferedWriter bw = new BufferedWriter(new FileWriter("massinput.txt"));

        //(共30个有效字符、3个单词) * n
        for (int i=0; i<1000000;i++)
        bw.write("aabbccDDEfg,hijlmn123 \nINPUT\r\n");
        bw.close();

        Lib lib = new Lib("massinput.txt","massoutput.txt");
        //计算
        lib.startCount();
        //输出
        lib.outputResult();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

测试数据

1、普通的数字、空格、字母组成的文件

One1 t2wo thrEe3  FOUR4
5fiVe 6six  seV7en eigh88t
ninE999 ten10 1111
file123 123file

2、有其他ascii码作分隔符,包括回车

onee,twoo:three3 FOUR4
FIve!sixx(Seven*eigh88t
nine999\tenn

3、有空白行(空格,tab,回车\r\n,以及它们的组合)


1111
   

      
aaaa

.


4、合法单词判断

onee,ee two...ooo three[e]e fourrrr fiveeee

5、大量数据

6、空白文档

7、只含空行

8、同一个单词大小写

abCdEFg abcdefg Bcdefg ABCDEFG bCDEfG

9、频率相同的单词,优先输出字典序靠前的单词

windows95,windows98,windows2000
abcdi abcdf abcdg abcde  
abcd1 abcd12 abcd11 abcd10

10、文章末尾换行

11、超过10个单词

abcd efgh ijkl mnop qrst uvwx yzzz
apple banana chicken donuts eges
file goal high image join  banana chicken 
chicken donuts eges
file goal high image join  banana chicken

测试覆盖率

alt test

优化:没有覆盖到的语句都是catch语句、空行、注释行,应该不需要优化了。

异常处理说明

1、使用输入输出流、文件流时,必须try catch处理异常,否则编译不通过。

2、main函数中判断了args数组的length

if (args.length >= 2)
{//略
}
else
    System.out.println("错误,请输入两个文件的名字");

心路历程与收获

在这次的项目中,我复习了如何用github管理代码,也学到了不少新知识。我知道了原来不同的语言有不同的代码规范,并且制定了自己的Java代码规范。我也接触到了PSP表格这种估计开发时间的方法,在项目开发的过程中开始有意识地估计和记录各项工作的时间。我还学会了编写构建之法中提到的单元测试,我想单元测试对于复杂的项目是很重要的。在完成词频统计程序的开发的过程中,我学会了简单使用正则表达式,复习巩固了一些Java的知识,也锻炼了思考的能力。

对于我来说,这次的作业难在接触到的新东西比较多,有需要学习的新知识,会遇到一些问题,文档的写作也和以往的作业比较不同,希望经过这一次的练习,我能在以后的作业中做得更快更好。

posted @ 2021-03-04 18:19  盘子79  阅读(131)  评论(2编辑  收藏  举报