寒假作业2/2

基本描述



这个作业属于哪个课程 2021春软件工程实践|W班 (福州大学)
这个作业要求在哪里 寒假作业2/2
这个作业的目标 通过编写上传简单的WordCount程序来熟悉github的代码管理,细读《构建之法》
其他参考文献

阅读《构建之法》

问题一

IT专业的大学毕业生找工作 时声称:我精通Java,会用C++写“Hello World”程序,我懂软件工程,我画了很 多图,写了很多文档,最后得了很高的分数……这些同学是真的懂软件工程,是 一个合格的软件工程师么?
————《构建之法》第一章

Question:对于一个IT专业的应届毕业生要达到什么标准,掌握什么技能才能算是一个软件工程师,才可以符合企业的技术标准?
《华为人》中这样写道:程序员在英文里对应有三个单词:Coder、Programmer和Software Engineer,我觉得这三个词,生动形象地描述了程序员所需要经历的三个阶段,或者说三个境界。

Coder:只要求能够熟练使用编程环境,精通几种编程语言、开发框架和开发库,擅长写代码就可以了。这个阶段的程序员能够按照既定的设计完成编码。

Programmer:要求在coder的基础之上,精通设计模式、算法实现和编码技巧,并具备熟练应用的能力,这个阶段的程序员能够独立编码解决现实问题。

Software Engineer:要求在掌握业务知识的前提下,理解为什么这么实现,在综合考虑架构实现,权衡开发成本后,为解决业务问题提出最优方案,并能与业务人员顺畅沟通,让业务人员理解方案。编码工作达到这个阶段,才能称得上是真正的程序员,才真正实现了从工作到职业的转变。

我认为,虽然我还未接触过真正的IT行业工作者,但在网络上也可以看到各式各样的有关IT行业的引导视频,仅仅只具备这几项技能是远远不够软件工程师的标准。作为一名软件工程师,不仅仅技术上需要有足够的经验积累,同时也要具备虚心学习,善于团结合作等个人品质,大学所教授的课程都仅仅只是入门级别的知识,规模较大的IT公司都会组织对应届生的培训,所以,要成为一个软件工程师,我们仍需更多更广泛的学习,实践,总结。


问题二

软件工程师的工作就是写代码,相关专业的练习也是以阅读代码,写代码为主, 那么代码量和工程师的水平是线性的关系么?
————《构建之法》第三章

Question:代码量是否与工程师的水平是线性关系,是什么最大程度上影响着工程师的水平?
衡量一个软件工程师的标准是有4个方面,其中包括:代码量和任务点,时间,质量,是否按时交付。而我认为,这其中最重要的,是思维,一个软件工作者对整个软件工程的规划和逻辑思维。就像从代码看出一个程序员水平的并不是行数,而是它的算法设计与思维逻辑。在很短代码中把本来应该用很长的代码来表达的东西用思维把整个效率都提高了,这就是专业。

对于这一点,我自身深有感触,之前由于参加比赛自学了java框架,但是直到现在我都没能够下定决心督促自己学习新框架,新技术,写代码也是在重复之前的代码,所以,在我的观念里,代码量只能是一个辅助标准而绝不是决定性因素以及与工程师的水平有着线性关系,事实上,代码量多的人也存在着许多水平仍然在初级阶段,千篇一律,没有良好的自学能力或者优秀的开发团队或公司的启发引导,代码量再多,没有在技术上学习创新,也只是在原地踏步,没有水平上的增长精进。


问题三

  1. 谁来做代码复审?即最有经验、熟悉这一部分代码的人。对于至关重要的代码,我们要请不止一个人来做代码复审。
    ————《构建之法》第四章

Question:那么是否存在一个单独的代码复审部门,如果没有,那么多次的代码复审是否会占用其他开发者的时间,降低开发效率?
网络上有这样的一个说法,代码复审一般是同一项目的开发者进行的,有的公司还会集中开发团队进行代码复审,这样的代码复审嫩能够改善和保证代码质量,预防 bug。此外还有益于制定团队代码规范,形成团队技术氛围,加深技术团队成员沟通,老带新互助成长等等。

我个人也是比较认同的,团队的代码复审并不会影响效率,反而是提高效率,磨刀不误砍柴工,没有代码复审,一个开发团队就会失去开发效率。


问题四

6.1.1 敏捷开发原则 1. 尽早并持续地交付有价值的软件以满足顾客需求 2. 敏捷流程欢迎需求的变化,并利用这种变化来提高用户的竞争优势 3. 经常发布可用的软件,发布间隔可以从几周到几个月,能短则短
————《构建之法》第四章

Question:敏捷开发要怎么兼顾质量和速度,敏捷开发的意义又是什么?
1.质量:在项目中梦寐以求的代名词是质量。不像传统的瀑布模型,等到开发完成才开始测试,可是在敏捷开发中,我们随着需求的准备便开始进行测试。因此,测试集成贯穿整个开发周期,使得工作产品像开发一样去定期检查。这允许工作所有者有必要时做出适当调整,以及及早的给产品团队检查出任何质量问题。

2.市场速度:由于传播速度快,我们能更快地响应市场,因此有更高收入。这一切增加客户满意度的关键因素是敏捷应用开发。

3.额外的优点:敏捷开发最好的一点就是它很有趣。整个团队都积极的参与,使得整个工作空间和氛围均因为这种积极参与和互相之间的协作配合而变得更有意思。有很多有趣的方式比如用计划扑克牌游戏和卡片来评估任务,采用生动新颖的任务面板来讨论工作的进展, 用全新的方式来管控例会以及许多敏捷项目中其他更有趣的东西。

实现敏捷开发确实有他一定意义,查阅资料后,我觉得敏捷开发应当为当前的开发潮流。首先,较短的开发周期能让新颖的点子付诸实现不过时,其次,要想在制定的时间内完成任务每个开发人员就必须不断关注技术和设计,就会越开发越快,整个团队的生产效率就会得到提升;然后,业务人员和技术人员能够每天在面对面合作讨论,能够加强整个开发团队的凝聚力。


问题五

一个团队经历了计划/设计/开发等阶段,达成代码完成(Code Complete)这一 目标,似乎后面的事情就水到渠成了。其实不然,软件生命周期的最后阶段往往 是最考验团队的,不但考验团队项目管理水平、应变能力,也考验团队的“血 型”。原计划的软件发布时间快到了,但是软件还是有各种问题,怎么办?
————《构建之法》第十五章

Question:既然不存在0bug的软件,那么软件在达到什么条件下可以发布,为什么有的bug无需处理?
软件测试上线有一定的标准,首先要查看自己的软件定位,不能在主功能都无法流畅运行的情况下就进行发布,其次定义四类错误,在错误修复率达到标准才可上线

A类错误 B类错误 C类错误 D类错误
<4%

A级:不能完全满足系统要求,基本功能未完全实现;或者危及人身安全。系统崩或挂起等导致系统不能继续运行。

B级:严重地影响系统要求或基本功能的实现,且没有更正办法(重新安或重新启动该软件不属于更正办法)。使系统不稳定、或破坏数据、或产生错误结果,或部分功能无法执行,而且是常规操作中经常发生或非常规操作中不可避免的主要问题。

C级:严重地影响系统要求或基本功能的实现,但存在合理的更正办法(重新启动该软件不属于更正办法)。系统性能或响应时间变慢、产生错误的中间结果但不影响最终结果等影响有限的问题。

D级:使操作者不方便或遇到麻烦,但它不影响执行工作功能或重要功能。界面拼写错误或用户使用不方便等小问题或需要完善的问题.

有的bug无需处理的原因可能是该bug的出现概率极小,且对整个软件的影响力度很小,开发者可以在后期的更新中进行优化处理,但仍然好奇是否有更加官方的解释。

有趣的冷知识


第一名程序员是位女性
也许最令人难以置信的是,历史上第一名程序员是位女性。她的名字是Ada Lovelace。在1843年,这位英国数学家Ada Lovelace,翻译了意大利工程师Luigi Menabreaw撰写的分析引擎文章。在翻译过程中,她把自己的理解都批注到每篇文章下,而这举动加快了计算机编程技术的发展。在这之后,她又设计出了第一种能够利用分析引擎计算伯努利数的算法,这也是第一个用电脑编写的算法。

WordCount编程

Github项目地址

221801420寒假作业2/2


PSP表格

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

解释思路描述

刚开始的思路就是整个程序由两个文件组成,一个WordCount类进行文件读写,一个Lib类定义四个函数分别实现读取字符个数,读取有效行数,读取单词数,对单词出现频率进行排序四个功能,所以会涉及到java的文件读写,字符串处理以及正则表达式的知识。


代码规范制定链接

CodeStyle


设计与实现过程

1.WordCount类主函数

public class WordCount {
    public static void main(String[] args) {
        Lib lib = new Lib();
        if (args.length == 2) {
            File inputFile = new File(args[0]);
            File outputFile = new File(args[1]);
            BufferedWriter buffWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "utf-8"));
				
            //获取字符个数并输出
            int characterNum = lib.statisticsCharacters(inputFile);
            buffWriter.write("characters: " + characterNum);
            buffWriter.newLine();
				
            //获取单词个数并输出
            int wordNum = lib.statisticsWords(inputFile);
            buffWriter.write("words: " + wordNum);
            buffWriter.newLine();
				
            //获取有效行数并输出
            int lineNum = lib.statisticsLines(inputFile);
            buffWriter.write("lines: " + lineNum);
            buffWriter.newLine();
                
            //对单词频率映射表进行排序并输出出现频率最高的前十个单词
            List<Map.Entry<String, Integer>> list=lib.wordsFrequency(inputFile);
            int num = 1;
            for(Map.Entry<String, Integer> map : list) {  
                if(num <= 10) {  
                    buffWriter.write(map.getKey() + ": " + map.getValue());
                    buffWriter.newLine();
                    num++;  
                }else break;  
            }
            buffWriter.close();
        }else {
            System.out.println("文件输入个数错误");
        }
    }
}

2.Lib类getReader函数:
函数功能为将File指针转换为BufferedReader读指针(由于使用次数太多,所以将其封装为函数)

    /*
     * 功能:返回utf-8编码的读指针
     * 输入:File文件指针
     * 输出:BufferedReader指针
     */
    static BufferedReader getReader(File file) {
        InputStreamReader read = null;
        read = new InputStreamReader(new FileInputStream(file),"UTF-8");
        return new BufferedReader(read);
    }

3.Lib类statisticsCharacters函数
通过利用BufferedReader自带的read()函数对文件中的字符逐个读取,统计需要统计的字符数

   /*
     * 功能:统计文件字符数
     * 输入:File文件指针
     * 输出:文件中含有的字符数
     */
    static int statisticsCharacters(File file) {
        int characterNum = 0;
        BufferedReader in = getReader(file);
        while (true) {
            int characterAscill=in.read();
            if (characterAscill >= 0 && characterAscill <= 127) {
                characterNum ++;
            }
            if (characterAscill == -1) break;
        }
        return characterNum;
    }

4.Lib类的statisticsWords函数
利用readLine()逐行读取,并利用正则表达式对每一行中符合单词定义规则的字符串进行匹配,匹配成功则单词总数加一

    /*
     * 功能:通过正则表达式对单词进行捕获
     * 输入:File文件指针
     * 输出:该文件中含有的单词数
     */
    static int statisticsWords(File file) {
	     int wordNum = 0;
	     BufferedReader in = getReader(file);
	     String str = null;
	     while ((str = in.readLine()) != null){
				
	         //通过正则表达式对每一行的字符串进行匹配查找,若存在符合条件的单词则加入wordsMap
	         String ragex = "[a-zA-Z]{4,}[a-zA-Z0-9]*";
	         Pattern p = Pattern.compile(ragex);
	         Matcher m = p.matcher(str);
	         while (m.find()) {
	             String s = m.group();
	             wordNum++;
	         }
	     }
	     return wordNum;
    }

5.Lib类的statisticsLines函数
同样使用readline()函数获取每行除换行符以外的字符串,若包含除空白符以外的字符则为有效行

    /*
     * 功能:通过对每一行的字符串的检查来统计文件的有效行数
     * 输入:File文件指针
     * 输出:该文件中含有的有效行数
     */
    static int statisticsLines(File file) {
        int lineNum = 0;
        BufferedReader in = getReader(file);
        String str = null;
        while ((str = in.readLine()) != null){
            for (int i = 0;i<str.length();i++) {
                if (str.charAt(i) != ' ' && str.charAt(i) !='\t') {
                    lineNum++;
                    break;
                }
            }
        }
        return lineNum;
    }

6.Lib类的wordsFrequency函数
首先利用statisticsWords的函数将单词与出现次数录入Map当中,最后利用重载Collection的Comparator比较器对Map进行排序生成List

    /*
     * 功能:通过将每个单词放入treemap,再将treemap按照value排序可获得前十个单词的频率
     * 输入:文件的指针
     * 输出:按字典序排序完成的List
     */
    static  List<Map.Entry<String, Integer>> wordsFrequency(File file){
        Map<String,Integer> wordsMap = new TreeMap<>();//单词和频率的映射表
        BufferedReader in = getReader(file);
        String str = null;
        while ((str = in.readLine()) != null){
               
            //通过正则表达式对每一行的字符串进行匹配查找,若存在符合条件的单词则加入wordsMap
            String ragex = "[a-zA-Z]{4,}[a-zA-Z0-9]*";
            Pattern p = Pattern.compile(ragex);
            Matcher m = p.matcher(str);
            while (m.find()) {
                String s = m.group();
                s = s.toLowerCase();
                if (wordsMap.containsKey(s)) {
                    int num = wordsMap.get(s);
                    wordsMap.put(s, num + 1);
                }else {
                    wordsMap.put(s, 1);
                }
            }
        }
        List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(wordsMap.entrySet()); 
        
        //通过比较器实现比较
        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {  
            public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {  
                return o2.getValue().compareTo(o1.getValue());  
            }  
        });    
        return list;
    }

性能改进

未改进前程序处理100万行文本时间:

性能改进主要有:
将正则表达式的Pattern变量的创建放到while循环外同时将其他while循环内声明的变量放于循环外节省创建变量时间

	         String ragex = "[a-zA-Z]{4,}[a-zA-Z0-9]*";
                 Pattern p = Pattern.compile(ragex);
                 String s = null;
	         BufferedReader in = getReader(file);
	         String str = null;
	         while ((str = in.readLine()) != null){
				
	             //通过正则表达式对每一行的字符串进行匹配查找,若存在符合条件的单词则加入wordsMap
	           /*  String ragex = "[a-zA-Z]{4,}[a-zA-Z0-9]*";
	             Pattern p = Pattern.compile(ragex);*/
	             Matcher m = p.matcher(str);
	             while (m.find()) {
	                 s = m.group();
	                 wordNum++;
	             }
	         }

将单词频次查询的操作与查询单词个数一同执行,避免查询两边单词,但同时这样修改会使排序单词频率的函数失去独立性,无法独立使用

        
       //此部分可在查询单词个数的同时完成
       /* Map<String,Integer> wordsMap = new TreeMap<>();//单词和频率的映射表
          BufferedReader in = getReader(file);
          String str = null;
          while ((str = in.readLine()) != null){
               
            //通过正则表达式对每一行的字符串进行匹配查找,若存在符合条件的单词则加入wordsMap
            String ragex = "[a-zA-Z]{4,}[a-zA-Z0-9]*";
            Pattern p = Pattern.compile(ragex);
            Matcher m = p.matcher(str);
            while (m.find()) {
                String s = m.group();
                s = s.toLowerCase();
                if (wordsMap.containsKey(s)) {
                    int num = wordsMap.get(s);
                    wordsMap.put(s, num + 1);
                }else {
                    wordsMap.put(s, 1);
                }
            }
        }
        */
        List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(wordsMap.entrySet()); 
        
        //通过比较器实现比较
        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {  
            public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {  
                return o2.getValue().compareTo(o1.getValue());  
            }  
        });    
        return list;

对相同文件进行处理可减少两秒时间


单元测试

statisticsCharacters函数单元测试


statisticsWords函数单元测试


statisticsLines函数单元测试


wordsFrequency函数单元测试


1万行文本处理时间:



100万行文本处理覆盖率

如何优化覆盖率?
我认为优化覆盖率应该尽可能测试更多的数据,确保每个逻辑分支都能够运行,非法的,报错的等等都有数据进行测试可以有效提高覆盖率,同时应该避免将使用次数少的功能独立成函数,本次程序中由于异常仅发生在文件的IO输入输出,所以没有覆盖到的基本是捕获错误的操作。


异常处理说明

本题的异常主要存在于IO输入,主要考虑以下三种情况:
1.多余两个文本地址输入(正常通过,提示“文件输入个数错误”)
2.少于两个文本地址输入(正常通过,提示“文件输入个数错误”)
3.文件不存在或者找不到(正常通过,提示“无法找到文件”)


心路历程与收获

此次作业的完成难度并不大,更多的是要投入时间认真注意每个细节,难的是做好做完美,适合的算法,全面的测试用例,代码的规范等都是我之前写代码所没有注意到的,所以这次的作业并不能够完善得很好,但我从这次作业中学习了很多,收获了很多。
1.学会了用github管理代码版本
2.学会了利用PSP表格来指导程序开发流程
3.确立了代码规范,并在这次的作业当中严格遵守
4.复习了JAVA的部分知识

posted @ 2021-02-28 11:38  BigClever  阅读(80)  评论(4编辑  收藏  举报