软工实践寒假作业(2/2)
这个作业属于哪个课程 | 2021春软件工程实践S班 |
---|---|
这个作业要求在哪里 | 软工实践寒假作业(2/2) |
这个作业的目标 | 1.阅读《构建之法》并提问 2.学会使用GitHub 3.学会单元测试和性能分析 |
作业正文 | 我的寒假作业(2/2) |
其他参考文献 | 《构建之法》、《廖雪峰的git教程》 |
目录
part1:阅读《构建之法》并提问
Q1:
在2.3个人开发流程中,“工程师在‘需求分析’和‘测试’这两方面明显地要花更多的时间(多60%以上);但是在具体编码上,工程师比学生要少花1/3的时间。显然,从学生到职业程序员,并不是更加没完没了地写程序——花在写代码上的时间反而少了许多。”
我认为将学生和职业程序员做这样的对比有点不合适,因为学生编码的时间和经验相对较低,各项能力水平不足,花在编码上的时间也会更多。而随着编程能力的提升,职业程序员能够更加轻松写代码能够将更多时间花在需求分析与测试上。因此我认为这样比较不太合适,并不能很有力证明学生到职业程序员写程序花费时间的差别。
Q2:
在3.1团队对个人的期望中,提到了TSP对团队成员的要求,其中在理性地工作这一点中,“很多人认为自己需要灵感和激情,才能为宏大的目标奋斗,才能成为专业人士。著名艺术家Chuck Close说:我总觉得灵感是属于业余爱好者的。我们职业人士只是每天持续工作。今天你继续昨天的工作,明天你继续今天的工作,最终你会有所成就。”
对于这个观点,我有不同的看法。我认为在一个团队中,即使作为一个成熟的团队成员必须从事实和数据出发,但也不应该失去工作上的灵感和激情。据我所知如今很多公司的程序员每天都做着相同的工作,即使有好的灵感也会因为团队而做出妥协,日复一日,对工作的激情可能会逐日降低。因此我认为在统筹团队的要求之后,也需要保持着对工作的灵感和激情。
Q3:
在3.4技能的反面中,作者给出了考察一个人是否精通模仿的办法。”
a.给面试者一个打乱颜色的魔方;
b.要求他把六面还原;
c.如果还原了,要求他把魔方恢复成我最初给他的那个混乱的局面,必须一模一样。“
我认为这种方法不能适用于考察一个人是否精通软工这门学科,就好比一个程序员解决了一些bug,并且以后发现此类bug自己能够解决,然而却需要恢复这些bug才能算精通吗?我认为一个程序员只要掌握解决bug的能力,就能算是精通,而不需要复原bug。
Q4:
在11.4的从Spec到实现中,“写好代码后,小飞对照设计文档和代码指南进行自我复审,重构代码。“ 在这段话中,重构是什么意思?
根据网上查阅的资料,所谓重构是这样一个过程:在不改善代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。本质上说,重构就是在代码写好之后改进它的设计。但我对具体如何使用它还不是很了解,重构应该选择随时进行还是等最后再重构,还有如果发现需要大面积修改,那应该选择重构还是重写呢?
Q5:
在16.1.1迷思之一:灵光一闪,伟大的创新就紧随其后中,“就像拼图一样,很多聪明人都模糊地看出了最终图像,都在一块一块地拼接,往往拼好最后一块的人得到了最大的荣誉。但是没有前人的积累,没有自身扎实的功底,就没有’最后一块‘等着大家去拼。”
对于以上作者认为“一个成功的创新,必须要有自身扎实的功底,而对于那些技术水平不足的人,哪怕有了灵光也只能是空的构想”这点,我有不同的看法。我认为对于现阶段的大学生,可能自身的技术和功底没有那么成熟扎实,但遇到一个好的创意时,我们仍然可以先从一些比较简单的东西做起,积攒经验和能力,尽力向自己的创意靠拢,最终等到能力水平达到后再实现自己的创新。我认为创新也是需要一步步积累,并不是一蹴而就的。
附加题:冷知识和故事的例子:
史上第一款电脑病毒,竟然是由防御技术专家Fred Cohen亲手设计出来的。他创造电脑病毒的目的仅仅是为了证明程序对电脑感染的可行性,从未希望借此对电脑造成任何危害。但这款程序却能够对电脑进行感染,并且能通过软盘等移动介质在不同计算机之间进行传播,因而命名为病毒。后来,他又创造出一种主动式电脑病毒,主要目的是帮助电脑用户找到未受感染可执行文件。
part2:WordCount编程
Github项目地址
PSP表格
Personal Software Process Stages | 预估耗时 | 实际耗时 |
---|---|---|
计划 | ||
• 估计这个任务需要多少时间 | 10min | 10min |
开发 | ||
• 需求分析 (包括学习新技术) | 6h | 8h |
• 生成设计文档 | 30min | 40min |
• 设计复审 | 10min | 8min |
• 代码规范 (为目前的开发制定合适的规范) | 20min | 20min |
• 具体设计 | 1h | 45min |
• 具体编码 | 16h | 28h |
• 代码复审 | 1h | 4h |
• 测试(自我测试,修改代码,提交修改) | 8h | 12h |
报告 | ||
• 测试报告 | 2h | 3h |
• 计算工作量 | 10min | 5min |
• 事后总结, 并提出过程改进计划 | 20min | 17min |
合计 | 35h50min | 57h25min |
解题思路描述
本次作业代码部分要求我们完成四项功能。
- 统计文件的字符数:这项功能的实现相对简单,我选择采用get()按字符读入的方法来完成。
- 统计文件的单词总数:这项功能要求统计单词,并且单词不分大小写用分隔符隔开。首先拿到题目考虑怎么读文件,按字符读入感觉有点太麻烦,我采用按字符串读入,然后再去判断里面的有效单词。大致思路是读入一串字符串,用向量容器去存储分隔符的位置,再通过条件满足至少以4个英文字母开头,进行有效单词的判断。
- 统计文件的有效行数:主要思路是当出现不是' ','/t','/n'的有效字符时,行数加一,当读到'/n'时,需要考虑和上一个'/n'之间是否有读到有效字符,所以读到有效字符时要将信息保存下来。
- 统计文件中各单词的出现次数:刚拿到题目的时候有点无从下手,后来通过网上的资料了解到可以通过map进行存储单词和出现次数,一下子就豁然开朗。我的主要思路是按照第二小点的方式判断出有效单词,然后通过map关联容器将单词的名字和出现次数保存起来,最后通过vector容器进行循环排序,每次将最大次数的单词存入新的vector容器中以便输出。
代码规范制定链接
计算模块接口的设计与实现过程
代码分为.h头文件、.cpp函数实现文件、.cpp主函数文件和.cpp单元测试文件 ,主要代码包含了上图五个函数,其功能独立,其中CountMaxWord()和WriteToFile()有执行先后关系。
设计与实现过程
统计文件字符数
while (f.get(c))
{
charcnt++;
}
统计文件的单词总数
代码...//当没有分隔符时且从该符号起四个字符都是字母
代码...//否则先判断只有一个分隔符时,前四个字符是否都为字母
//当有一个以上分隔符且四个字符都是字母
for (int i = 0; i < separators.size(); i++)
{
if ((s[separators[i] + 1] >= 'a' && s[separators[i] + 1] <= 'z') && (s[separators[i] + 2] >= 'a' && s[separators[i] + 2] <= 'z') &&
(s[separators[i] + 3] >= 'a' && s[separators[i] + 3] <= 'z') && (s[separators[i] + 4] >= 'a' && s[separators[i] + 4] <= 'z'))
{
wordcnt++;
}
统计文件的有效行数
while (f.get(c)) //按字符读入
{
if (c != ' ' && c != '\t' && c != '\n') //如果不为空时,表明该行有效,lineflag置1
{
lineflag = 1;
}
else if (c == '\n' && lineflag == 1) //否则当输入字符为'\n',且该行有效时,有效行数加一
{
linecnt++;
lineflag = 0;
}
}
if (lineflag == 1)linecnt++;//检查最后一行是否为有效行
统计文件中各单词的出现次数
//使用vector容器对vec->second(出现次数)进行排序
for (int i = 0; i < time.size(); i++)
{
if (i == 10) break;//最多取10个
int max = 0;//最大出现次数
string maxword;//对应单词名
for (vector<pair<string, int>>::iterator vec = v.begin(); vec != v.end(); vec++)
{
if (vec->second > max)
{
max = vec->second;//存下当前最大数单词
maxword = vec->first;
}
else if (vec->second == max)//字典序
{
if (vec->first < maxword)
{
max = vec->second;//存下当前最大数单词
maxword = vec->first;
}
}
}
if (max) x.push_back(make_pair(maxword, max));
//将本次循环中出现次数最大的单词名和次数存入vector容器
}
性能改进
上面两图是我测试一个30000行大文件的性能分析图,总耗时6.369s.其中最消耗cpu的是IO,各个函数中读字符的循环操作最消耗cpu。因此要想大大改进性能,可以从优化读字符的cpu消耗入手。因为时间关系还未进行优化,后期会继续优化,继续更新。
单元测试
部分单元测试代码
UnitTes1:
//此段单元测试用于测试最多输出有效字母个数
vector<pair<string, int>> v;
CountMaxWord(in, v);
//此函数用于将有效单词按出现次数降序排序
int num = WriteToFile(out, v);
//此函数用于按顺序输出单词和次数并返回输出单词的数量
Assert::IsTrue(num == 10);
UnitTest2:
//此段单元测试用于测试频率相同的单词,是否优先输出字典序靠前的单词
vector<pair<string, int>>v;
CountMaxWord(in, v);//此函数用于将有效单词按出现次数降序排序
vector<pair<string, int>>::iterator vec = v.begin();
Assert::AreEqual(vec->first, word1);
Assert::AreEqual((vec + 1)->first, word2);
Assert::AreEqual((vec + 2)->first, word3);
构造思路:第一个单元测试 通过比较最输出单词数是否为10 来进行构造。
第二个单元测试通过 将排序好的向量容器中的单词名字输出与预想的名字进行比较 来进行构造。
如何优化覆盖率:当时做单元测试时是结合代码覆盖率起来设计的,做之前我只有一个CountMaxWord函数用于排序和输出,为了设计出上面第二个单元测试我将这个函数的排序和输出功能分为两个函数,以便检验以上单元测试。具体如何优化覆盖率,我的想法很简单就是通过单元测试把每一行代码都一一运行过去。
异常处理说明
异常处理代码
freopen("unusual.txt","w",stdout);//用文件将错误输出信息保存便于单元测试检测
//输入文件路径错误异常
if (argv[1] == NULL || argv[2]==NULL)
{
cout << "请输入文件路径" << endl;
return -1;
}
//输入文件过多异常
if (argc > 3)
{
cout << "输入文件过多" << endl;
return -1;
}
//无法打开文件异常
if (!f)
{
cout << "无法打开文件" << endl;
return -1;
}
对应单元测试
//当输入文件路径错误时引发该异常 如没有设定命令行参数
ifstream f("C:\\Users\\apple\\source\\repos\\WordCount\\WordCount\\unusual.txt", ios::in);
string s;
f >> s;
string unusual = "请输入文件路径";
Assert::AreEqual(s, unusual);//通过判断错误文件内容是否为“请输入文件路径”来验证
//当输入命令行参数过多时引发该异常
代码...//命令行参数路径
ifstream f("C:\\Users\\apple\\source\\repos\\WordCount\\WordCount\\unusual.txt", ios::in);
string s;
f >> s;
string unusual = "输入文件过多";
Assert::AreEqual(s, unusual);//通过判断错误文件内容是否为“输入文件过多”来验证
//最后一个单元测试与上面相似就不列出代码了
心路历程与收获
此次作业对我来说收获匪浅!一开始看到作业有那么多的要求,讲道理确实有点晕。学习使用GitHub的时候,学得不太清楚,自己上b站上学习了相关的操作之后,才豁然开朗。后面写代码作业,碰到不少的算法上的困难,通过求助同学和csdn上的例子,才一步步完成作业。让我耗时最多也让我收获最多的要数单元测试了。从一开始毫无头绪,一步步自己摸索尝试,到能够写出规范的(我觉得-)单元测试代码并完成相应的代码测试,也是满满的成就感。通过这次实验自己也是学到了很多很多的知识,并且在以后的代码也会用GitHub进行管理,并且自己做单元测试完善自己的代码。
当然这次也有许多不足的地方,比如说一开始没有注意要先阅读代码规范,定制自己的代码,后来在check作业的时候才来阅读代码规范,发现自己的代码乱糟糟的,好多代码写得一点都不规范,然后才进行修改。以后的编程要先定好自己合理的代码规范再去编程,让自己的代码更加清晰明了。
更新:发现自己这次没注意看作业要求,很多要求都没达到,比如说commit的要求,下次作业一定先认真看要求再做。