软工实践寒假作业(2/2)
软工实践寒假作业(2/2)
这个作业属于哪个课程 | 2021春软件工程实践|S班 |
---|---|
这个作业要求在哪里 | 软工实践寒假作业(2/2) |
这个作业的目标 | 1、重读阅读之法提出问题 2、熟悉github的使用 3、编写字符统计程序 4、学会单元测试 |
其他参考文献 | 《码出高效_阿里巴巴java开发手册》 邹欣老师博客关于单元测试和回归测试 《浅析 Java Stream 实现原理》 《IDEA中使用JUnit4(单元测试框架)》 |
任务一 重新阅读《构建之法》并提问
1、为什么有工作经验的软件工程师比学生在“需求分析”和“测试”上所花的时间多,但编码时间短?我们应当怎么去安排“需求分析”、“测试”和“开发”的时间呢?
问题来源:
P36页PSP相关内容:软件工程师比大四学生多读了3年书,多工作了3年,两类人任务的质量要求也不一样。我们可以看到,工程师在“需求分析”和“测试”这两方面明显地要花更多的时间(多60%以上);但是在具体编码上,工程师比学生要少花1/3强的时间。显然,从学生到职业程序员,并不是更加没完没了地写程序——花在写代码上的时间反而少了许多。
我的思考和查证:
对于问题1,客观原因:软件工程师所参加的项目往往比学生所写项目更加复杂且更少前例参考,所以软件工程师花更多的时间在需求分析上。主观原因:软件工程师经过各类bug的磨练,有更强的编码能力,所以在开发上时间更短,但在测试方面则更加严谨,所以花费更长的时间。(我的瞎想,哈哈哈哈哈)
对于问题2:三者的时间的分配应当依据实际项目的不同来权衡。对于结构复杂的项目在需求分析上可能需要花费更多的时间;对性能要求高的项目则可能在开发上需要 多下功夫,不断进行优化;对容错率较低的项目则适合在测试方面多下功夫。
2、怎么才能避免“为了自己的角色而做绩效优化”?
问题来源:P321问题3
很多年前,我曾在一个软件团队里负责测试工作,职责之一是编写各种测试用例,以保证系统的代码覆盖率达到80%以上。做过实际项目的工程师都知道,程序里的很多语句是用来处理种种异常情况的,这些情况大多都不会发生。但是若这些语句未被覆盖的话,这个模块的覆盖率就会下降,我就达不到80%的目标。所以我花了很多时间构造各种奇怪的测试数据,把程序中的那些椅角昔兄都尽可能覆盖掉。至于这些椅角春晃在实际中是否会发生,对用户的影响如何,程序是否应该这样设计,我都不太关心。只要覆盖率达到80%,老子的活就干完了!
我的思考和查证:
看了作者的这个故事分享,我深有感触。在日常学习作业中,我也总喜欢在有特定的某个参数上作巨大的努力,而忽视了某次任务的出发点。例如:每学期的《形势与政策》论文,总是想在字数上胜人一筹,而忽视了文章的深度和简洁度。又比如上学期的wen实践编写博客系统,只追求功能上的多,而不力求把求把某个功能深化做到极致。
为了避免在某个数字上迷失自我,我觉得“不忘初心”这个词语就是个很好的方法。时刻记住自己做这件事情的出发点是什么,要求是什么,目的是什么。
此外,对于工作中将某个指标当做工作绩效的行为,若有不合理之处,最好应当及时反馈,避免盲目卖力。
感谢两位大牛老师对此问题的评论,受益匪浅,现将问题求解补充完善:
养成价值判断习惯:做到“要事第一”,有明确的价值判断,才能把精力放对地方,才能不忘做事初心。缺乏价值判断习惯,往往是把做什么的决定权长期交给他人。凡事多掂量掂量,从全局优化出发来做价值判断,避免局部过度优化反而损害了全局优化。
学会计划:列计划时应当避免计划过大,可在一段时间内将任务细化(重而知道什么事情才是最重要的,避免盲目努力),这样可以有效的避免专注于细节而忽略整体。
3、产品经理要不要懂技术?
问题来源:P191
Product Manager :产品经理——正确地做产品。目前国内公司大部分PM都是指这个职位。产品经理对一个或多个产品或产品线负责,而互联网产品涉及到这些方方面面:产品定位、市场发展、需求分析、运营、营销、市场推广、商务合作。产品经理横跨这些部门,寻找资源,持续推进产品。随着产品的发展,不同公司,对PM要求会不一样。核心要求是,根据市场和用户需求,协调各部门资源,正确地把握产品定位和方向,解决用户的痛点,持续优化产品。
我的思考和查证:
前段时间“五彩斑斓的黑”的梗在各大论坛流行,这也揭示了程序员和“不懂技术”的产品经理之间的代沟。
但众所周知,产品经理的主要任务是:有自己的产品思维,具备出色的判断力,形成自己的商业分析逻辑,能够帮公司赚钱(或具备这个意识),有获取新用户的意识。产品经理花时间在技术上到底值不值呢?
经过查阅资料以及和有工作经验的程序员交流,我大致形成以下观点:
产品经理的竞争力不在于代码能力,并且个人精力有限,所以产品经理试图提高代码水平性价比并不高。
但是,懂技术的产品经理自然能够更好地和程序员沟通,不至于提出过高的需求,或者给开发组很少的时间,导致工作量巨大(可能是996原因之一)。
所以说,技术方面只需懂个大概,知道什么框架能干啥,有啥优势就好(和程序员大牛多交流)。
4、设计产品时是否需要考虑不直接给企业带来利润的用户的体验?
问题来源:P216页
Stone网用户画像:
1.商户:在网站上出售货物的用户
2.买家:在网站上购买货物的用户,还有越来越多的人通过手机访问
3.浏览者:在网站上浏览、比较货物,并不购买
我的思考和查证:
对于浏览者在网上浏览、比较货物、但并不购买,这种不直接带来利益,但有极大可能成为企业用户的用户在设计时是否需要考虑其需求?(放在Stone网中可能就是考虑未登录能否浏览商品等等)
个人认为企业的最主要也是最直接的任务是赚钱,应当把重心放在最重要的位置去设计产品。当然,为了黏住潜在用户,在不影响主要用户体验和公司利益的基础上也可以考虑非主要客户需求。
5、什么样的软件工程作业才叫好作业?
问题来源:
P37页:很多老师反映软件工程的作业题不好出,学生做的“大作业”也是了无新意,自学软件开发的读者往往也想不出什么有意义的题目来练习。怎么办?师生们身处轰轰烈烈的软件产业大环境,但是在软件工程课上做的题目却是非常简陋,没有起到应有的作用,这的确是一件很有讽刺意义的事情。
我的思考和查证:
书本中提到好的软件工程作业应当顾及软件工程的两大要素(复杂性和易变性)。
对于复杂性方面考虑到不同同学有不同的水平,可在一定难度基础上增加附加题目。
并且好的软件工程作业最好具有实际意义,因为每次自己写出能简化生活的程序就会特别有成就感,真正的好作业应当师能让学生有成就感,而不是“被迫”完成。
软件工程发展的过程中的冷知识和故事
第一位程序员的故事
一般认为第一位程序员名叫阿达·洛芙莱斯(Ada Lovelace),是个不折不扣的女程序员,她的颜值很高,美貌过人,是英国著名诗人拜伦的女儿(白富美,豪门>贵族)。在1834年,阿达的朋友——英国数学家、发明家兼机械工程师查尔斯·巴贝其(Charles Babbage)——发明了一台分析机。阿达则致力于为该分析机编写算法,并于1843 年>公布了世界上第一套算法。巴贝其分析机后来被认为是最早期的计算机雏形,而阿达的算法则被认为是最早的计算机程序和软件。
为了纪念这位伟大的程序员:
美国国防制作了一个新的高级计算机编程语言——Ada,以纪念阿达·洛芙莱斯。
在微软的Wins产品里也可以找到阿达的全息图标签。
英国计算机公会每年都颁发以阿达命名的软件工程创新大奖。
任务二
作业描述
在大数据环境下,搜索引擎,电商系统,服务平台,社交软件等,都会根据用户的输入来判断最近搜索最多的词语,从而分析当前热点,优化自己的服务。 首先当然是统计出哪些词语被搜索的频率最高啦,请设计一个程序,能够满足一些词频统计的需求。
1、项目github链接
2、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时 | 实际耗时 |
Planning | 计划 | 0.5h | 15min |
• Estimate | • 估计这个任务需要多少时间 | 0.5h | 15min |
Development | 开发 | 12h | 12h |
• Analysis | • 需求分析 (包括学习新技术) | 2h | 3h |
• Design Spec | • 生成设计文档 | 1h | 0.5h |
• Design Review | • 设计复审 | 0.5h | 0.5h |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 15min | 10min |
• Design | • 具体设计 | 2h | 1.5h |
• Coding | • 具体编码 | 4h | 6h |
• Code Review | • 代码复审 | 0.5h | 20min |
• Test | • 测试(自我测试,修改代码,提交修改) | 3h | 4h |
Reporting | 报告 | 1.5h | 2.5h |
• Test Repor | • 测试报告 | 0.5h | 1.5h |
• Size Measurement | • 计算工作量 | 0.5h | 10min |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 0.5h | 1h |
合计 | 14h | 15h |
3、解题思路
-
需求分析
- 从文件获取输入
- 统计字符数
- 统计单词数
- 统计最多的10个单词及其词频
- 输出结果
-
初看需求的迷惑
First:命令行运行程序
在以往java学习没有尝试过cmd运行程序。不过想到以前C++课上老师特意提到过args数组的用法是用来传递外部参数,又想到java main函数也有args参数,由此试验,果不其然。针对命令:java WordCount input.txt output.txt args[0] == input.txt args[1] == output.txt。
Second:怎么去分割单词和计算有效行数
首先想到的技术是正则表达式,正则在字符匹配方面较为简单。之后花了点时间重新复习正则表达式,确实有其可行之处(具体实现见下文)。
Third:排除非法单词
同样可用正则解决
Forth:单词频数统计
想的是用map,具体优化方法暂时未想到。
Fifth:单元测试的概念和怎么去实现
对于我来说,单元测试是全新的概念,单从字面理解的话:一个单元一个单元地测试代码 (单元可以是一个函数)。通过查阅邹欣老师的博客才有了进一步了解。并查阅资料得知可以借助IDEA采用JUnit框架完成测试。
4、我的代码规范.md
5、计算模块接口的设计与实现过程
设计两个类,Lib和WordCount类
- Lib类中包含各种独立的方法
- getWords
- getChars
- getLines
- getMaxCntWords
- WordCount类则是包含Main函数的类,包括程序的流程和文件读写处理。
-
getWords() 和 getMaxCntWord()方法的实现
- 在Lib类中设计了 handleWord() 方法用来计算字符数和统计出现频率最高的10个单词。
上诉两个功能有重叠部分,故设计此函数一同处理。
-
拆分Word采用正则表达式(拆分单词和判定是否为合法单词一并进行,提高效率)
Pattern wordPattern = Pattern.compile("(^|[^A-Za-z0-9])([a-zA-Z]{4}[a-zA-Z0-9]*)"); Matcher matcher = wordPattern.matcher(text); //匹配
-
采用Map<String,Interge>存放单词和出现的频率,单词作key,频率作Value。
-
getLines()方法的设计
-
Pattern linePattern = Pattern.compile("(^|\n)\\s*\\S+"); //行数匹配正则表达式 lines = 0; //行数置类 Matcher matcher = linePattern.matcher(text); //匹配 while (matcher.find()) { lines++; }
-
同样采用正则表达式进行匹配
-
-
getChars()方法设计
chars = text.length();
- 直接计算待处理字符串长度即可。
6、性能改进
- 读写文件
- 读写文件是选择采用BufferReader和BufferWriter类
- 带有缓存的读写类能够减少操作系统访问文件的次数,加快程序的运行速度(访问文件是个慢操作,要尽量避免)。
- 对Map排序
maxCntWords = maxCntWords .entrySet() .stream() .sorted(Map.Entry.<String, Integer> comparingByValue() //按值降序 .reversed() .thenComparing(Map.Entry.comparingByKey()))//按key字典序升序 .limit(10) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));
- 采用stream流
- Stream 处理数据的过程可以类别成工厂的流水线。数据可以看做流水线上的原料,对数据的操作可以看做流水线上的工人对原料的操作。
- 第一次使用流处理Collection,受益很大。参考资料
7、单元测试和性能分析
单元测试使用的是Java的JUnit框架,IDEA对其有很好的支持。 [参考博客](https://blog.csdn.net/weixin_44425934/article/details/99858528)
7.1测试获取字符数测试
- 用循环生成测试字符串
@org.junit.jupiter.api.Test
void getChars()
{
String oriString = "h*8i2|.\ndss";
int loopCnt = 666;
for(int i = 0; i < loopCnt; i++)
{
text += oriString;
}
-----
}
7.2测试行数测试
- 测试了三种情况:空白,正常,含空白行
@org.junit.jupiter.api.Test
void getLines()
{
text = "124\n12312\n\n\n\n3123\n\n"; //包含空白行
lib = new Lib(text);
assertEquals(lib.getLines(),3);
}
7.3获取单词总数测试
- 构建的字符串:hello1,hello2,hello3,123jki2,iii\n,并用循环加长。
@org.junit.jupiter.api.Test
void getWords()
{
String oriString = "hello1,hello2,hello3,123jki2,iii\n"; //包含多种非法单词
int wordsCnt = 3;
int loopCnt = 666;
for(int i = 0; i < loopCnt; i++)
{
text += oriString;
}
-----
}
7.4获取频率最高十个词测试
//普通情况
@org.junit.jupiter.api.Test
void getMaxCntWords()
{
text = "";
String strs[] = {"hello1","hello2","hello3","hello4","hello5","hello6","hello7","hello8","hello9","hello10","hello11"};
for(int i = 0; i < strs.length; i++)
{
for(int j = 0; j < 100; j++)
text += strs[i] + " ";
}
----------
}
//同频率,按key字典序排序情况
@org.junit.jupiter.api.Test
void getMaxCntWords()
{
text = "";
String strs[] = {"hello1","hello10","hello11","hello2","hello3","hello4","hello5","hello6","hello7","hello8","hello9"};
for(int i = 0; i < 100; i++)
{
for(int j = 0; j < strs.length; j++)
text += strs[j] + " ";
}
---------
}
7.5性能测试
1、十万字1000种可能字符(字符长度5-9)
/*构建文档*/
BufferedWriter fileWriter = new BufferedWriter(new FileWriter("input.txt"));
Random r = new Random(1);
for(int i = 0; i < 100000; i++)
{
fileWriter.write("hello" + r.nextInt(1000) + " ");
}
fileWriter.close();
-----
用时:582ms
2、一百万字10000种可能字符(字符长度5-10)
/*构建文档*/
BufferedWriter fileWriter = new BufferedWriter(new FileWriter("input.txt"));
Random r = new Random(1);
for(int i = 0; i < 1000000; i++)
{
fileWriter.write("hello" + r.nextInt(10000) + " ");
if(i % 30 == 0)
fileWriter.write("\n");
}
fileWriter.close();
用时:1997ms
3、现实文章测试
characters: 463539
words: 27440
lines: 15484
output: 1764
print: 1568
string: 1568
system: 1372
null: 1176
file: 980
inputbfd: 980
inputfilereader: 980
stringbuilder: 980
args: 784
用时:639ms
7.6覆盖率截图
所有函数和代码均有测试
8、异常处理说明
主要包含异常处理的部分在输入输出部分,计算部分未作过多处理。
-
传入参数不等于两个
if(args.length != 2) { System.out.print("程序只接受两个参数"); return; //退出程序 }
-
读写文件错误:try catch finally
try { ---- } catch (Exception e) { System.out.print(e.getMessage()); System.out.print("\n文件写出错!"); } finally { if(output != null) output.close(); }
9、心路历程与收获
- 熟悉了github的使用
- clone,fork,pull request,merge
- 我的关于GitHub的博客
- 版本控制好用,有时候程序写着写着就崩了,找bug又极其困难时候,返回上一个版本就好了。这也是老师所要求的代码有进展就及时签入的好处。
- 复习巩固java知识,特别是Map的使用,以及了解了Stream在Collection中的使用和好处。
- 制订Personal Software Process (PSP, 个人开发流程,或称个体软件过程),让自己在开发时提前规划,开发起来更加顺手,并且有种计划感和紧迫感,自我感知开发效率有一定的提高。
- 养成良好的代码规范习惯十分重要,在未来编码中会对自己提高要求。
- 单元测试:
- 以往开发中没有重视到的点,系统地测试能够使自己的代码更加健壮。
- IDE自带的测试模块也能够极大简化测试过程(以前只会手工测试)。