寒假作业(2/2)
一、作业基本信息
这个作业属于哪个课程 | 我的班级 |
---|---|
这个作业要求在哪里 | 作业链接 |
这个作业的目标 | 提出带思考的问题,记录WordCount程序编程过程,学习使用git |
GitHub项目地址 | 前往项目 |
其他参考文献 | 无 |
二、阅读《构建之法》并提问
问题1
- 一个对算法熟悉的程序员和一个对算法一知半解的程序员差别有多大?
阅读了第一章 软件工程概述:
我找到了工作,成了一名程序员, 但是我发现所有的算法别人已经实现了,我只要调用就可以了。
似乎我公司的软件和数据结构,算法的关系都不大。那我当初辛辛苦苦学习的数据结构和算法有用么?
如何区分一个好的程序员和不好的程序员呢?
查找了资料:算法对程序员意味着什么?
程序员对算法通常怀有复杂情感,算法很重要是共识,但是否每个程序员都必须学算法是主要的分歧点。
很多人觉得像人工智能、数据搜索与挖掘这样高薪的工作才用得上算法,觉得算法深不可测。但是这些其实
都不是具体的算法,而是一系列算法的集合。
- 思考:之前有学过算法与数据结构这门课,第一印象是这门课难度比较大,要学许多算法,印象最深的是
Dijkstra算法。在学这门课之前,我对于算法只听其名,学了之后发现算法对于程序的开发有着非常大的
帮助,算法的逻辑巧妙,提高程序的性能,减少了运行时间 ,提高了效率,这无疑会使你的程序比别人的
优秀许多。对算法熟悉的程序员对于数据的处理应该比较敏感,能选择好的数据结构,使用好的算法来处理
数据;而对算法一知半解的程序员也许还会思考蛮久,无从下手。
问题2
- 如何避免自己成为鹦鹉?
阅读了第五章 团队成员不同的投入和心态:
有些人是 鹦鹉 - 他们有漂亮的羽毛, 能说会道, 联系广泛, 能提出很多建议, 很多点子. 但是他们
不执行, 除了一些人云亦云的观点和一些关于架构的空谈之外, 他们没有其他投入. 一旦项目失败, 他们就会
飞到另一个项目中去。 他们的投入级别是 – 围观 (bystander).
- 思考:看鹦鹉这类人的定义能发现这类人缺少实践,空学了一堆知识却没有将理论投入实践;而且缺少行动力。
为了避免成为这样的人,在团队项目的开发过程中,我应该更加积极地参与,与成员进行讨论,遇到困难不退缩,
微笑着面对困难。
问题3
- 遇到技术上的困难时,应该自己解决,还是应该去问别人?
阅读了第五章 角色 - Dev:
首先我们要明确,这是一个实际的团队项目。不是学校里的闭卷考试(一些人在离开学校很久还偶尔做恶梦,
考试中一些题做不出来…)。
- 思考:我感觉这两端要保持平衡。不能一味自己解决做闭卷考试,也不能遇到困难就求助于他人。
最重要的还是个人能力的提高,这样自己也有了独立解决困难的能力;当然术业有专攻,遇到自己
完全不了解的难题,与其想破脑袋,不如求助于他人,教学相长
问题4
- 需求分析的重要性
阅读了第六章 项目需求分析和建议:
你的创意解决了用户的什么需求? 这个需求可以是明确的, 公开的 (例如: 希望能上网玩三国杀). 也可能是说
不清道不明的, 例如 - 以前没人说: 嗯, 如果我能找到这样一个网站, 我可以去偷菜, 就好了…
我们要充分了解用户的痛苦, 他们对已有软件, 服务不满意的地方。但是用户往往也不知道颠覆型的创新。
例如亨利 · 福特 当年发明汽车之前, 如果他深入用户之中, 了解他们的需求, 用户会告诉他 - 我希望我的马车更快一些!
查找了资料:需求分析的重要性
需求分析就是分析软件用户的需求是什么.如果投入大量的人力,物力,财力,时间,开发出的软件却没人要,那所有的投入
都是徒劳.如果费了很大的精力,开发一个软件,最后却不满足用户的要求,从而要重新开发过,这种返工是让人痛心疾首的.
(相信大家都有体会)比如,用户需要一个for linux的软件,而你在软件开发前期忽略了软件的运行环境,忘了向用户询问这个问题,
而想当然的认为是开发for windows的软件,当你千辛万苦地开发完成向用户提交时才发现出了问题,那时候你是欲哭无泪了,痕不得找块豆腐一头撞死.
- 思考:没有试着做过需求分析。但我想当项目比较大时,需求分析有利于功能的划分。没有需求分析的话,做出来的
软件无法满足需求,那软件也许就相当于白开发了。
问题5
- 单元测试的重要性
阅读了第二章 单元测试:
你的RP是由你的程序质量决定的。
——阿超
这一章讲的是两人合作,既然程序是两个人写的,那就会出现一个人写的模块被另一个人写的模块调用的情况。
很多误解、疏忽都发生在两个模块之间。如何能让自己写的模块尽量无懈可击?单元测试就是一个很有效的解决方案。
- 思考:“单元测试用于检验软件最小组成单元的正确性。”单元测试是将程序的功能划分成小功能进行正确性的测试,
相当于大的功能,基本功能或者小的功能肯定是比较好测试的,如果小功能测试通过,那么大功能的正确性也就得到了
保证。我感觉单元测试真不错,虽然我还没用过。
软件工程发展中的冷知识
Bug的起源,你知道Bug也有一段有趣的历史吗? Bug的历史起源
1945年,一只小飞蛾钻进了计算机电路里,导致系统无法工作,一位名叫格蕾丝·赫柏的人把飞蛾拍死在工作日志上(见图),
写道:就是这个 bug(虫子),害我们今天的工作无法完成——于是,bug一词成了电脑系统程序的专业术语,形容那些系统中的
缺陷或问题
三、WordCount编程
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
• Estimate | • 估计这个任务需要多少时间 | 800 | 900 |
Development | 开发 | ||
• Analysis | • 需求分析 (包括学习新技术) | 100 | 120 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 30 | 60 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
• Design | • 具体设计 | 60 | 60 |
• Coding | • 具体编码 | 300 | 350 |
• Code Review | • 代码复审 | 60 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | ||
• Test Report | • 测试报告 | 60 | 60 |
• Size Measurement | • 计算工作量 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 40 | 40 |
合计 | 800 | 900 |
解题思路
题目给出了四个功能:统计字符数量、统计单词数量、统计有效行数、统计各单词出现频次
- 首先是统计字符数量,我首先想到的是字符流。查找资料发现某些编码方式1个字符所用的字节数可能是多个,考虑到这
点,用字符流才是正确的。可以用带缓存的读写器BufferedReader和BufferedWriter,从头读到文件末尾,同时统计
字符数量。 - 统计单词数量总数:统计单词总数的同时可以同时统计词频。我的思路还是一个字符一个字符读取,当有4个连续的
字母时,判断它是一个单词,然后再遇到分割符之前,将单词补齐。得到一个完整的单词时,顺便统计词频,还需要将 - 统计有效行数:可以通过readline方法一行一行读取文件,并赋值给String类型的变量,根据其内容判断是否是一个
有效的行。 - 统计词频:通过统计单词数量获得了单词统计的数据,这里只需要把这些数据按照单词的字典序,单词的出现频率进行排序
就可以得到一个根据词频排序的单词列表。
代码规范链接
设计与实现过程
程序运行截图
结构
统计功能方法在Lib类中,main方法在WordCount中
WordCount
- 设计了一个类MyWords用来保存每个单词的内容和出现频次
class MyWords {
private final String content;
private int frequency;
}
Lib
- 统计字符数量方法countCharacters(),使用read()方法一个一个字符读取,直到文件末尾
BufferedReader inputFile = new BufferedReader(new FileReader(inputFileName));
BufferedWriter outputFile = new BufferedWriter(new FileWriter(outputFileName,false));
int b = inputFile.read();
while (b != -1) {
i++;
b = inputFile.read();
}
- 统计单词数量的方法countWords(),还是使用read()方法一个字符一个字符读取,通过一些条件来判断是否是一个单词
for (int i=0; i<4; i++) {
b = reader.read();
if (b ==-1) {
break;
}
if (Character.isLetter((char)b)) {
str += Character.toLowerCase((char)b);
if (i == 3) {
isItAWord = true;
numOfWords++;
break;
}
} else {
break;
}
}
后面就是将单词补齐,或者直接跳到下一个单词。需要注意的是读取的过程中要考虑文件末尾的情况。
- 统计行数的方法countLines(),使用readLine()方法进行逐行读取,使用String变量来保存
读到的字符串,并进行判断是否是空行,判断行数变量是否需要增加
String lineStr;
lineStr = reader.readLine();
while (lineStr != null) {
if (isEmptyLine(lineStr)) {
lineStr = reader.readLine();
continue;
} else {
numOfLines++;
lineStr = reader.readLine();
}
}
- 统计单词频率的方法countFrequency(),前面统计单词数量时获得了一个ArrayList
类型的列表,
在这里只需对列表里的单词先根据字典序排序,然后再根据单词频率排序,这样就得到了排好序的列表
for (int i=0; i<myWordsArrayList.size() - 1; i++) {
for (int j=i+1; j<myWordsArrayList.size(); j++) {
boolean flag = myWordsArrayList.get(j).getContent().
compareTo(myWordsArrayList.get(i).getContent()) < 0;
if (flag) {
MyWords tempWord = myWordsArrayList.get(i);
myWordsArrayList.set(i, myWordsArrayList.get(j));
myWordsArrayList.set(j,tempWord);
}
}
}
for( int i=0; i<myWordsArrayList.size() - 1; i++) {
for (int j=i+1; j<myWordsArrayList.size(); j++) {
boolean flag = myWordsArrayList.get(j).getFrequency() >
myWordsArrayList.get(i).getFrequency();
if (flag) {
MyWords tempWord = myWordsArrayList.get(i);
myWordsArrayList.set(i, myWordsArrayList.get(j));
myWordsArrayList.set(j,tempWord);
}
}
}
性能改进
测试:读取一个200w字符的文件,统计其字符数、单词数、行数、词频
多次测试得到结果:
2884ms
2839ms
2858ms
2920ms
时间花费蛮长的,有很大的改进空间。
改进:使用HashMap来存储单词,与单词的频率形成映射关系。
不使用双重for循环,降低时间复杂度,改进排序算法
单元测试
测试结果:
- countCharactes:
- countWords:
- countLines:
- countFrequency:
- 代码覆盖率:
*如何提高代码覆盖率:
1.代码逻辑性要严谨
2.增加用例
3.简化逻辑
异常处理说明
遇到的异常:FileNotFoundException
这是很容易遇到的异常,有时候如果不注意,可能会浪费程序员很多时间。
四、心路历程与收获
完成这个作业的过程中充满坎坷,并且还没有完善。
由于没有第一时间开始作业,导致后面甚至变成赶作业,后悔。
愈发感觉自己的能力还有待提高,学习的自觉性也有待提高。
- 首先是大致浏览看了《构建之法》,对于软件工程这门学科有了粗浅的了解。
学习了Git 和 GitHub的使用,有助于管理软件版本,不至于太杂乱。而且有助于为后续的
团队合作打下基础。- 制订了代码规范,认识到之前没有严格代码规范胡乱排版胡乱缩进的坏处。按照代码规范
编写程序,让代码更优美,更有可读性,治好了强迫症。- WordCount程序不算一个很难的程序,难点是性能的改进,选用正确的数据结构,使用好的算法,这些
细节能让WordCount的性能得到改进。- 第一次接触了单元测试这个概念,这在后续的团队合作中应该会是一个很重要的工作,保证模块功能的正确性,
让你的小伙伴可以正常使用你写的功能,减少成员冲突,可以快乐玩耍。- 总结:通过第一次的个人编程作业,学到了很多新的东西,也发现了亿点不足,还需努力。