软工实践寒假作业(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项目地址
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类调用
具体:
- 文件读取:使用BufferedReader的read()方法
- 统计字符:由于不会出现合法ASCII以外的字符,所以打算直接获取字符串长度
- 统计单词数:感觉以前都是一个字符一个字符的判断,所以刚开始也是这么做,但是之前看到了正则表达式感觉有助于这个统计,而且更加方便,由于之前只闻其名,这次就动手学了
- 统计有效行:刚开始想的是用'\n'结合无效行计算,但是最后上网看到了相关的正则表达,就用了后者
- 统计文件中各单词的出现次数(对应输出接下来10行):想的是用map存,排序的话之前没怎么深入学习,就上网查了下相关用法
- 文件写入:使用BufferedWriter的write()方法
- 资料来源:csdn、简书、百度等
2.4 代码规范链接
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)
}
函数调用图:
功能实现
- 读取文件:使用BufferedReader的read()方法读取字符,通过StringBuilder拼接,最后返回文件字符串。
static String readFile(String infile) throws IOException {
...
while ((ch = reader.read()) != -1) {
builder.append((char)ch);
}
...
}
- 统计文件字符数:直接获取文件字符串的length。
static int countCharNum(String str) {
return str.length();
}
- 统计文件有效行数:通过正则表达式匹配
static int countValidLineNum(String str) {
...
Pattern linePattern = Pattern.compile("(^|\n)\\s*\\S+");
Matcher matcher = linePattern.matcher(str);
while (matcher.find())
lineNum++;
...
}
- 统计文件单词数:使用正则表达式匹配非空白符,进行分割,然后对每个分割完的串进行单词判断;同时在这一步进行单词词频统计。
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);
}
}
}
...
}
- 单词频率排序:使用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();
}
}
});
...
}
- 写入文件:使用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进行字符串拼接
- 使用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");
2. 使用StringBuilder对20w字符文件进行字符串拼接(这部分运行时间仅有15msreader = 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");
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);
- 测试覆盖率
如何优化覆盖率
- 代码尽量简洁
- 不要生成写太多无用的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前垂死挣扎,太痛苦了(还有建议电脑出现小故障及时去修,别等下崩了,啥也没有)