软工实践寒假作业(2/2)
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/2021SpringSoftwareEngineeringPractice/ |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/2021SpringSoftwareEngineeringPractice/homework/11740 |
这个作业的目标 | 阅读《构建之法》并提问、完成词频统计个人作业、撰写博客 |
作业正文 | https://www.cnblogs.com/rrtwo/p/14427132.html |
其他参考文献 | |
任务一
阅读《构建之法》并提问
选哪一种医生?
作为一个软件工程师,你觉得自己表现如何?有没有这样的体会:
看书的时候觉得“技止此耳”,开发项目的时候才觉得实际情况和书上讲的都有一些出入,一些重要的细节书上没有提。我们很多人是边看ASP.NET的书,边开发ASP.NET的项目,这相当于一边看医学书一边动手术……
如果你是病人,你希望你的医生是下面的哪一种呢?
a) 刚刚在书上看到你的病例,开刀的过程中非常认真严谨,时不时还要停下来翻书看看……
b) 富有创新意识,开刀时突然想到一个新技术、新的刀法,然后马上在你身上实验……
c) 已经处理过很多类似的病例,可以一边给你开刀,一边和护士聊天说昨天晚上的《非诚勿扰》花絮……
d) 此医生无正式文凭或正式医院的认证,但是号称有秘方,可治百病。
事实上,很多软件项目就是用a)或者b)这样的方法搞出来的。当然也有一些人走d)这条路。
讨论:
①你要选哪种类型的医生?
②医生、药剂师、律师和很多行业都有职业考试和职业证书,软件工程师需要有正式的职业证书才能上岗么?
显然是选c)。至少不能是d)这种的,因为TA的“号称”仅仅是TA的号称。没有证件背书,自然让人难以相信TA的能力。
虽然不能断定没有证书背书的人的能力一定是不行的,但有个最基本的证明,可以让人不用增加成本地去想这个人的能力实际上行不行。因此,不管是什么行业的工作者,有相应的职业证书可以说是十分必要的。
工程还是艺术
软件开发是一门工程,是一门艺术,还是一门手艺?你如何衡量艺术家?如何衡量创造能力?
如果是一门工程,那工程师要守规矩;如果是一门艺术,那艺术家要创新。
* 写诗歌最多的人是谁?
* 最有创造力的诗人是谁?
一些最有影响力的作家,他们的作品都非常少,甚至只有一本,例如:
* 《飘》,作者 Margaret Mitchell
* 《红楼梦》,作者曹雪芹
另外,优秀的作品往往并不符合所有“好”的标准。
软件设计工程师们在做代码复审的时候,是看“重复字”的多少,还是程序的艺术性?
这个问题的另一个侧面是,在中国,一个成名的歌唱家往往出现在各种场合,演唱她当年的作品,观众们往往显得百听不厌。一个软件工程师就不能这样,在舞台上展现他当年写的“Hello World”程序,或者是1.0的产品。为啥有这样的区别呢?
个人认为软件设计更偏向是一门工程。但这并不意味这软件设计没有创新的部分(现有的设计可不是从天上掉下来的)。有些设计是循序渐进发展出来的,有些则是一个人或者一个团体的突发奇想。
至于出道作品的问题,我是这样想的:他们的区别体现在——歌唱家演唱当年的作品的时候,必然不可能唱得和当年完全一致(不然直接看当年的录像岂不更好)。这其中必然包含了歌唱家新的见解。软件工程师亦是如此,如果只是机械地展示以前的代码,则这并不能体现出作者以往至今水平的变化。区别便在于此。
绞刑架和职业发展
移山公司的人力资源总监给同学们做了职业发展的演讲,大意是随着软件工具和软件工程理论的发展,开发软件将会越来越容易,软件企业的水平都是CMMI4级以上。软件白领的生活指日可待,金额也不是梦,大家前途无可限量,学软件工程的同学越来越多,就是明证。大家纷纷鼓掌。最后他分享了一个故事:
两个劫匪在亡命的路上看到一幅绞刑架,劫匪小弟说,大哥,如果这世界上没有绞刑架,咱们的职业就好干多了。大哥说:你真笨!如果没有了它,这世上做劫匪的人怕是太多,我俩恐怕竞争不过同行,早就饿死了!
请思考这个故事对个人及软件业发展的启示。
工具越多越好,就能发展得更好吗?如果不懂得这些工具的使用方法,有再多的工具也无益于解决问题。如果仅仅是懂得这些工具的使用,却不曾想过这些工具为什么这么设计(对比软件行业:只会用框架,不知道框架为什么这么设计),用得再好也只是一个工匠,而不是“设计师”。如果只会用这些工具解决及其具体的问题,那就更加糟糕了(不会处理更加宽泛的问题)。另外,认为工具越多就越轻松的观点我不能苟同——纺织车的出现反而加重了工人的劳力。不过这一点具体地讨论下去就和这次作业的内容无关了。
案例
程序员小飞原计划三天完成某个任务,他说服了同事,坚持采用自己独特的实现方式。现在是第三天的下午,他马上就可以做完。但是在实现功能的过程中,他越来越意识到自己原来设计中的弱点,他应该采取另一个办法,才能避免后面集成阶段的额外工作。但是他如果现在就改弦更张,那就意味着公开承认自己的设计不好,并且会花费额外的时间,这样他的老板,同事也许会因此看不起他。如果他按部就班地按既定设计完成,最后整个团队还要花更多时间在后续集成上,但那就不是他个人的问题了。怎么办?
缺陷迟早是要暴露的。不能,也不应该视图隐瞒和推锅。再者,不管是哪种处理方法,最后自己还是要处理(无非是别人要不要处理的区别)。不如一开始就把错误解决掉,而不是到了后面问题变得更加麻烦才处理。
自知之明
我们经常看到一些IT专业的同学、同事,或者专家对一些事情进行评论,并且表现得头头是道,他们真的懂多少,他们有自知之明么?在刚学习某个语言或技术的时候,是否也有这种现象?
有的人不知道自己不知道,有的人则是不知道自己知道。显然,不知道自己不知道的人们并没有自知之明。但发生这种情况无可厚非,只有通过不断地学习才能意识到——其实自己并不是什么都知道。
任务二
GitHub 项目地址
[https://github.com/Tozzger/PersonalProject-Java]
估计将在程序的各个模块的开发上耗费的时间
Personal Software Process Stages | 预估耗时(分钟) |
---|---|
计划 | |
• 估计这个任务需要多少时间 | 720 |
开发 | |
• 需求分析 (包括学习新技术) | 30 |
• 生成设计文档 | 60 |
• 设计复审 | 30 |
• 代码规范 (为目前的开发制定合适的规范) | 30 |
• 具体设计 | 120 |
• 具体编码 | 120 |
• 代码复审 | 30 |
• 测试(自我测试,修改代码,提交修改) | 120 |
报告 | |
• 测试报告 | 60 |
• 计算工作量 | 60 |
• 事后总结, 并提出过程改进计划 | 60 |
合计 | 720 |
解题思路描述
题目有以下要求:
- 从文件中读取数据
- 分析数据
- 以文件的形式输出数据
我们可以分别处理这3项要求。其中第2个要求又可以分为更加细致的内容:
- 统计字符数
- 统计单词数
- 统计有效行数
- 统计词频
进一步地,我们要把这4项提取为接口,以方便调试与调用。
输入:我们可以通过 Files 类中的 lines() 获取文件中每行的字符串(不含换行符)。
输出:我们可以通过 PrinterStream 输出符合格式的文本。
代码规范的链接
[https://github.com/Tozzger/PersonalProject-Java/blob/main/041801114/codestyle.md]
计算模块接口的设计与实现过程
为了完成计算,我们可以定义一个接口,这个接口中有我们需要实现的方法。再实现这个接口。接口部分内容如下:
long getCharCount();// 字符数
long getWordCount();// 单词数
long getLineCount();// 有效行数
Map<String, Long> getWordFrequency();// 词频(最多10个单词)
因为题目要求从文件中读取信息。因此,实现类的构造函数如下:
public FileCounter(Path path) throws IOException
以下几乎所有的代码都直接地或者间接的运用到了以下内容:
- NIO包的工具类
- Stream流式操作
- 正则表达式
统计字符数
直接读取文件的字符数即可。
path.toFile().length()
统计单词数
这个可以分为以下步骤
- 通过分隔符分割字符串
可以通过 String 的 split 方法实现 - 判断字符串是否为单词
可以通过正则匹配或者简单的字符串比较实现。 - 单词计数
把所有单词存放到数组中,数组大小即为单词数量。
Files.lines(path)// 分行,以**系统**的换行符为分隔符
.map(String::toLowerCase)// 转小写,因为不管是输入还是输出,都不区分大小写。另外这个 map 往下移动的话,正则的“a-z”后面要加个“A-Z”
.flatMap(CounterHelper::split)// 分词: str -> Arrays.stream(str.split("[^a-z0-9]+")),非单词字符作为分隔符
.filter(CounterHelper::isWord)// 保留单词: str -> Pattern.compile("[a-z]{4,}[a-z0-9]*").matcher(str).matches(),只有符合要求的单词才会被保留下来
.toArray(String[]::new)
统计有效行数
读取文本中所有的行,去除空白的行后,剩余的便是有效的行。
Files.lines(path).filter(CounterHelper::isNotBlank).count()// isNotBlank: str -> !str.trim().isEmpty()
trim会把空白字符(源代码中,不大于' '的字符)剔除。
统计词频
先统计每个单词出现的次数,再根据要求返回所需的结果。
operateWordFrequency(wordFrequency, allWord) {
allWord.entrySet().stream().sorted(COMPARTOR)// COMPARTOR: 词频倒序,字符串大小正序
.limit(10)
.forEach(e -> wordFrequency.put(e.getKey(), e.getValue()));
}
计算模块接口部分的性能改进
因为文件IO耗时巨大。因此,我们计算完结果后应当将结果保存下来,再次调取结果时直接返回结果。
lineCount = FileCounterHelper.solveLineCount(path)// 例子:保存有效行的结果
另外,正则表达式可以换成字符比较。
str -> {
if (str.length() < 4)
return false;
for (int i = 0; i < 4; i++) {
if (!Character.isLowerCase(str.charAt(i))) {
return false;
}
}
for (int i = 4; i < str.length(); i++) {
char ch = str.charAt(i);
if (!Character.isLowerCase(ch) && !Character.isDigit(ch)) {
return false;
}
}
return true;
}
不过实际测试后发现,这个替换的优化不甚明显。
计算模块部分单元测试展示
文本的内容为10000000行“file123”,单元测试的工具为JUnit 4。
上图分别为为输入、输出。
覆盖率没有100%的类是因为这些类没有被实例化(静态工具类,不需要实例化)。
计算模块部分异常处理说明
这部分能遇到的异常均为IO异常(和计算本身无关),比如找不到输入文件。因此直接抛出异常,让调用者自行处理。
在程序的各个模块上实际花费的时间
Personal Software Process Stages | 实际耗时(分钟) |
---|---|
计划 | |
• 估计这个任务需要多少时间 | |
开发 | |
• 需求分析 (包括学习新技术) | 10 |
• 生成设计文档 | 10 |
• 设计复审 | 10 |
• 代码规范 (为目前的开发制定合适的规范) | 10 |
• 具体设计 | 30 |
• 具体编码 | 220 |
• 代码复审 | 30 |
• 测试(自我测试,修改代码,提交修改) | 60 |
报告 | |
• 测试报告 | 60 |
• 计算工作量 | 30 |
• 事后总结, 并提出过程改进计划 | 30 |
合计 | 500 |
心路历程与收获
通过这次作业,我注意到了许多平常没有注意到的或者忽视的细节。
比如自以为已经配置好的环境。当我对写好的代码进行单元测试时,一直报错找不到某个方法,但是代码本身是可以通过编译的,说明这个方法是存在的。经过查阅资料,我才意识到可能是版本的问题。因为我平常写Java代码都是在Java 11写,作业是用Java 8的。旧版本自然不可能找得到新版本才有的方法。换句话说,我的作业代码和测试代码Java版本不一致(忘了把作业代码的Java版本换掉,却没有忘了换测试代码的)。当我切回版本并把高版本才有的方法删除后。测试代码就可以正常运行了。
这件事说明了一个问题。平常写代码前应该对这些事先配置的内容多加注意,而不是一路点击“确认”过去。如果在这些简单的问题上不多加注意的话,后面要付出更多的代价解决这些甚至不能称之为问题的“问题”。比如上面环境的问题——创建工程,直接点确认,换来半个小时的排查工作。