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

这个作业属于哪个课程 2021春软件工程实践|W班
这个作业要求在哪里 寒假作业2/2
这个作业的目标 阅读《构建之法》提出问题、根据需求编写程序、使用PSP进行时间管理与总结
其他参考文献 csdn、《构建之法》

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

说明:因为本人不只阅读第三版,所以引文文字的出处只提是哪个章节的。

问题1 软件工程有没有银弹?原因是什么?

第十一章:谷歌研究院的院长Peter Norvig被问及同样问题的时候说:“我从来不喜欢UML类型的工具,如果你不能通过计算机语言表达( UML要表达的东西),那就是这种语言的弱点。”像任何新技术一样,以UML为代表的图形化分析方法的确解决了不少实际问题,但是也引发了一些误解、误用、狂热和“银弹"的信仰。
第三章:软件的模块之间存在着各种复杂的依赖关系,软件的不可见性和易变性。

对于银弹这个词我可能见过那么一两次,但是没有真正去了解这个词到底什么意思。我查了资料说的是,由于银弹这个词是从英文silver bullet单纯的翻译而来,所以对于中国人很不好理解,看英文翻译过来的文章,其实要表达的意思就是“万金油”,感觉有效果,实际上可能只是安慰剂。还有出自《没有银弹》的定义,在软件工程中,银弹指能让生产力在十年中提高十倍的方法。经验而谈,”万金油“这东西应该不存在,因此很显然我也认为软件工程不存在银弹。但这是因为书中所提到的软件工程本身存在各种复杂的依赖关系、不可见性和易变性,以至于我们无法从根本解决这些问题吗?

问题2 应该根据什么来选择在哪个方面追求“专和精"?

第三章:没有人能在学校里掌握所有“将来会用得到的知识"才离开学校.随后马上把技术运用在实践中。工程师应该在实际工作中不断学习和不断成长,根据自己的情况选择在哪个方面追求“专和精".在哪几个方面达到"知道就好”的水平”。

这里所说的”自己的情况“是指自己的喜欢程度,自己的能力?还是什么?还有就是困惑如果自己专和精的技术不是现在的主流,我们还有必要坚持吗?感觉根据自己有点不实际,好多都是根据形势去选择应该“专和精”的方面。现实中大多数可能都会选择追随主流,毕竟比较吃香吧。关于专和精,网上有个人给了个说法“做技术,一定要专和精,才会是生产力”,所以我还有一个困惑--“广”的地位在哪?多种技能难道不好吗?这是考虑到实际人的能力有限的情况,还是什么呢?

问题3 用户体验与产品质量为什么会冲突?

  • 第一章:专业人士都知道软件有"Bug" .软件团队的很多人都整天和Bug打交道,Bug 的多少可以直接衡量一个软件的开发效率、用户满意度、可靠性和可维护性。
  • 第一章:一个好的软件,即使功能和同类软件区别不大,但却会让人感觉到非常好用.这就是软件的用户体验(用户体验)。用户体验和数据结构、算法没有直接的关系,但是很多非常成功的软件就赢在这个方面.
  • 第十二章:好的用户体验是所有人都想要的,如果它和产品质量有冲突,怎么办

前两点好像确实是这么回事,bug的多少可以直接衡量用户满意度(也就是第二点提到的用户体验感);用户比较关心功能,而对怎么实现并不关心。但是我对第三点有点质疑,高质量的产品无疑具有较高的性能和很少的bug,而bug的多少可以直接衡量一个软件的用户满意度,所以高质量产品应该是对用户体验的一个提升。因此用户体验与产品质量应该不会冲突吧。假如我所说的是错误的,那一个好的软件是应该追求好的用户体验,还是应该所追求好的产品质量,或者是折中呢?其中的“好“到底是怎么定义的?在书上也有看到对应的答案”优秀的作品往往并不符合所有”好“的标准。没有最好的,只有最合适的“,但是还是不懂怎样才是”合适“与”足够好“。

问题4 如何把控软件的依赖关系?

第三章:软件的模块之间存在着各种复杂的依赖关系,软件的不可见性和易变性,使得软件的依赖关系很难定义清楚,导致软件不易得到及时的维护和修复。对依赖关系的两种极端态度都会引出可笑的行为,并且无一例外地会造成延迟交付。

概括出来的思想误区有这几种

  • 分析麻痹:想弄清楚所有细节、所有依赖关系后再动手,心理上过于悲观,不想修复问题。

  • 不分主次地想解决所有依赖问题:过于积极,想马上动手“完美地”达到目标,而不是根据现有条件找到一个“足够好”的方案。

  • 过早优化:在某一个局部问题上陷进去,花大量时间对其优化,无视全局。

  • 过早扩大化/泛化:程序虽然可扩展,但是要了解必要性和难度。把小问题真正解决好,也不容易。

这些误区其实我也都有过。第一种实际上是还没有开始就已经结束。第二种就好像做作业一样,没有先更好的熟悉API就着手,导致后面直接重构,十分浪费时间。关于优化这个问题,Donald Knuth论文中说到”找到瓶颈,做全局的性能分析,而不是把时间浪费在对程序中非关键部分的速度的无限思索“,但是很多同学包括我自己目前的全局观念不够多,导致经常放第三种思想错误。所以我们应该是只做全局瓶颈的性能优化嘛?但是不是每个部分都优化全局性能更好吗?同时也很困惑怎样去把控软件的依赖关系,怎样去思考软件的开发才能解决所有依赖问题存在这样的问题。

问题5 团队中角色和职责有必要自然转换吗 ?

第十七章:团队成员相互支持,相互依赖,角色和职责能够根据项目的要求自然转换

这里所说的角色、职责能够自然转换是否要求我们要掌握多种技能,胜任前端与后端吗?但如果对于专攻前端和专攻后端的两人,那这个要求显然不实际。而且感觉较多人都会选择一个方向专攻。所以我认为这个要求太过严格吧(虽然如果能做到,那更有助于完成各种项目,这个是无疑的)。我还有一个疑惑是全栈工程师的定义到底是什么,为什么我有这个困惑呢,因为上述所说的要求,符合百度百科中全栈工程师的定义,但是我在网上有看到一篇文章讲到,百度百科上的定义是错误的,原话是“真正的全栈工程师,是让你职业向上成长的概念;不是让你掌握更多开发语言,往旁边成长,这样只会成为一个大胖子,互联网行业发展这么快,大胖子是跟不上节奏的,会带来职业生涯的灾难”。所以真正的全栈工程师是广而不精吗?

附加题

英国著名诗人拜伦的女儿Ada Lovelace曾设计了巴贝奇分析机上解伯努利方程的一个程序。她甚至还建立了循环和子程序的概念。由于她在程序设计上的开创性工作,Ada Lovelace被称为世界上第一位程序员。 美国国防部开发的ADA语言就是为纪念这位世界上的第一位程序员而命名的。

源自:源链接

part2:WordCount编程

2.1 Github项目地址

PersonalProject-Java

2.2 PSP表格

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

2.3 解题思路

设计一个Lib类,在其中设计所有统计的方法,供WordCount类调用

具体

  1. 文件读取:使用BufferedReader的read()方法
  2. 统计字符:由于不会出现合法ASCII以外的字符,所以打算直接获取字符串长度
  3. 统计单词数:感觉以前都是一个字符一个字符的判断,所以刚开始也是这么做,但是之前看到了正则表达式感觉有助于这个统计,而且更加方便,由于之前只闻其名,这次就动手学了
  4. 统计有效行:刚开始想的是用'\n'结合无效行计算,但是最后上网看到了相关的正则表达,就用了后者
  5. 统计文件中各单词的出现次数(对应输出接下来10行):想的是用map存,排序的话之前没怎么深入学习,就上网查了下相关用法
  6. 文件写入:使用BufferedWriter的write()方法
  7. 资料来源:csdn、简书、百度等

2.4 代码规范链接

codestyle.md

2.5 设计与实现过程

类设计:Lib类(4个函数)、WordCount类(3个函数,内部有个IOUtils类(2个函数))

**
 * 统计类
 */
public class Lib {
    /**
     * @description 统计字符数
     */
    static int countCharNum(String str)
    
    /**
     * @description 统计空白行数
     */
    static int countValidLineNum(String str)
    
    /**
     * @description 统计单词总数, 并统计单词对应个数
     */
    static int countWordNum(String str)
    
    /**
     * @description 对wordMap中的单词频率排序
     */
    static List<HashMap.Entry<String, Integer>> sortWordMap()
}
public class WordCount {
    /**
     * @description 文件处理工具类
     */
    static class IOUtils {
        /**
         * @description 读取指定文件,返回对应字符串形式
         */
        static String readFile(String infile)

        /**
         * @description 将统计数据字符串写入文件
         */
        static void writeFile(String result, String outfile)
    }

    /**
     * @description 统计数据硬编码
     */
    private String getResult(String content)

    /**
     * @description 执行统计
     */
    private void process(String infile, String outfile)

    public static void main(String[] args)
}

函数调用图
图片

功能实现

  1. 读取文件:使用BufferedReader的read()方法读取字符,通过StringBuilder拼接,最后返回文件字符串。
static String readFile(String infile) throws IOException {
    ...
    while ((ch = reader.read()) != -1) {
      builder.append((char)ch);
    }
    ...
}
  1. 统计文件字符数:直接获取文件字符串的length。
static int countCharNum(String str) {
    return str.length();
}
  1. 统计文件有效行数:通过正则表达式匹配
static int countValidLineNum(String str) {
    ...
    Pattern linePattern = Pattern.compile("(^|\n)\\s*\\S+");
    Matcher matcher = linePattern.matcher(str);
    while (matcher.find())
        lineNum++;
    ...
}
  1. 统计文件单词数:使用正则表达式匹配非空白符,进行分割,然后对每个分割完的串进行单词判断;同时在这一步进行单词词频统计。
static int countWordNum(String str) {
    ...
    String[] words = str.split("[^a-z0-9]+");
    for (String word : words) {
        if (word.matches("[a-z]{4,}[a-z0-9]*")) {
            wordNum++;
            if (wordMap.containsKey(word)) {
                int n = wordMap.get(word);
                wordMap.put(word, n + 1);
            } else {
                wordMap.put(word, 1);
            }
        }
    }
    ...
}
  1. 单词频率排序:使用Collection.sort对Map进行排序。
static List<HashMap.Entry<String, Integer>> sortWordMap() {
    ...
    Collections.sort(wordMapList, new Comparator<HashMap.Entry<String, Integer>>() {
        @Override
        public int compare(HashMap.Entry<String, Integer> word1, HashMap.Entry<String, Integer> word2) {
            if (word1.getValue().equals(word2.getValue())) {
                return word1.getKey().compareTo(word2.getKey());
            } else {
                return word2.getValue() - word1.getValue();
            }
        }
    });
    ...
}
  1. 写入文件:使用BufferedWriter的write()方法将结果写入文件。
static void writeFile(String result, String outfile) throws IOException {
    ...
    BufferedWriter writer = null;
    writer = Files.newBufferedWriter(Paths.get(outfile), StandardCharsets.UTF_8);
    writer.write(result);
    ...
}

2.6、性能改进

  • 文件读取的方式使用了带缓冲的BufferedReader和BufferedWriter,提高了读写效率。

  • 用StringBuffer代替String进行字符串拼接

    1. 使用String对20w字符文件进行字符串拼接(这部分运行时间将近19s
    reader = new BufferedReader(new FileReader(infile));
    long startTime = System.currentTimeMillis();
    while ((ch = reader.read()) != -1) {
        str += (char)ch;
    }
    long endTime = System.currentTimeMillis();
    System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
    

    img
    2. 使用StringBuilder对20w字符文件进行字符串拼接(这部分运行时间仅有15ms

    reader = new BufferedReader(new FileReader(infile));
    long startTime = System.currentTimeMillis();
    while ((ch = reader.read()) != -1) {
        builder.append((char)ch);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
    

    img

2.7、单元测试

说明:均是通过BufferedWriter将数据写入文件,在单元测试函数中调用Lib类中对应方法进行测试,并通过Assert.assertEquals()将测试函数的结果与预期结果进行比较。

以下展示代码均为关键代码

  • 测试统计字符数
    需要考虑Ascii码,空格、水平制表符、换行符等都需要考虑在内
String str = "aaa[ \t.bbgdb\nnwindows2000\\n123file\\rsdsd\r\n1";
...
Assert.assertEquals(Lib.countCharNum(testStr), testStr.length());
  • 测试统计单词数
    需要考虑单词至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写
String str = "tes@word\naaaa\tqifei12h,1wordne[ss1\n  fqsq1a \n\n3|]hioaoy";
...
Assert.assertEquals(Lib.countWordNum(testStr), 5 * 10000);
  • 测试统计有效行数
    任何包含非空白字符的行,都需要统计
String str = "  \nh53 jk,dne[ss1\n  fqqdfgg grga \n \t \n";
...
Assert.assertEquals(Lib.countValidLineNum(testStr), 2 * 1000);
  • 测试覆盖率
    img
    img

如何优化覆盖率

  • 代码尽量简洁
  • 不要生成写太多无用的getter、setter

2.8、异常处理说明

  • 基本是I/O异常
  • WordCount类中,对文件传入参数个数进行了异常处理
if (args.length < 2) {
    System.out.println("2 paths needed");
    return;
} else if (args.length > 2)
    System.out.println("choose tow font paths");

2.9、心路历程与收获

  • 复习了git和GitHub的使用,再一次感受到了用git工具管理代码的优越性,以前都是一次性提交,感觉确实不一样。
  • 通过这次作业初步学习单元测试,之前的测试方法都是直接运行程序,十分不便,但是使用单元测试,可以更加方便地对特定代码进行测试。
  • 之前看见过正则表达式,以为贼难,初步学习了正则表达式,虽然规则挺多的,但是还是比较好理解。
  • 首次使用PSP,刚开始觉得感觉这都是随意估计,没什么依据,看了《构建之法》中的相关内容却深有感受。
  • 切记不要再ddl前垂死挣扎,太痛苦了(还有建议电脑出现小故障及时去修,别等下崩了,啥也没有)
posted @ 2021-03-05 22:18  WiLLyy  阅读(126)  评论(9编辑  收藏  举报