#软工实践-个人项目-词频统计
Github项目地址
https://github.com/pandaeathzr/personal-project
PSP表格:
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟 |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 10 | 0 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 120 | 240 |
· Design Spec | · 生成设计文档 | 20 | 30 |
· Design Review | · 设计复审 | 20 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 10 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 360 | 540 |
· Code Review | · 代码复审 | 60 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | ||
· Test Repor | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 120 |
合计 | 910 | 1330 |
解题思路:
实现基本功能:
-
统计文件的字符数:难度系数较低,在不考虑汉字的情况下,只需要记录读入字符的数量即可。
-** 统计文件的有效行数:**初步设想有效行只需判断该行有非空字符,且以换行符结尾或者文件结尾。 -
统计文件的单词总数:
- 要求单词至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。只需要连续确定4个英文字符后,再遇上分割符确定一个单词,然后记录即可。
- 分割符的定义是非字母数字符号,例如
!@#¥%……&*
还有空格、转义字符、换行符。
-
统计文件中各单词的出现次数:把每个单词记录下来,作为一个key放进map容器中,出现相同单词的时候对应的值增加1。并且由于单词不区分大小写,在记录单词前必须将其转换成小写字母(输出的单词统一为小写格式)。由于采用map容器,输出即可按照字典序。
其他要求:
- 使用Github来管理源代码和测试用例
- 提交的代码要求经过Code Quality Analysis工具的分析并消除所有的警告。
- 使用性能分析工具Studio Profiling Tools来找出代码中的性能瓶颈并进行改进。
- 使用单元测试对项目进行测试,并使用插件查看测试分支覆盖率等指标
惭愧的说以上的要求都是之前所没有涉及到的,所以对于这些工具,可能需要花费更多的时间去学习或者调试。
具体实现:
-
程序的结构大致如下:
-
统计字符数以及有效行数相对比较简单。故在此不对其展开具体实现。
-
对于统计单词数目,根据我们之前的想法,构造了一台字母的有穷自动机。在连续遇到4个字母前,若遇到非字母字符,则返回初始的状态(由于作图工具的限制,初始状态少了个>图形)。遇到连续的4个字母后,遇到字母和数字都会在当前状态下循环,除非遇到了我们定义的分隔符号,则终止并记录单词,存入map中。
- 部分代码:
···
map<string, int> strMap;
while (!infile.eof())
{
infile >> c; //读字符
if ((c >= 'a'&&c <= 'z') || (c >= 'A'&&c <= 'Z'))
{
c = lower(c);
char word[20] = {};
int k = 0, c_count = 0;
int num = 0;
int flag = 0;
while ((c >= 'a'&&c <= 'z') || (c >= 'A'&&c <= 'Z')) //判断是否为四个连续字母
{
word[k++] = c;
infile >> c;
c = lower(c);
num++;
if (num > 3)
{
flag = 1;
num = 0;
break;
}
}
while (whether_char(c)&&!infile.eof())
//whether_char()判断是否为分割符
{
//若不为分割符号且不是文件结尾读取不断后面字符
if (flag == 1)
{
c = lower(c);
word[k++] = c;
}
infile >> c;
}
if (flag == 1)
{
if (strMap.find(word) != strMap.end()) strMap[word]++;
// 统计单词,若存在相同单词,则词频+1
else strMap[word] = 1;
}
}
}
···
性能测试:
- 性能测试的结果如下所示
-
显然自己代码的瓶颈是在于无脑采用了map容器。这样虽然可以比较方便,或者说比较偷懒,但是的的确确给自己的程序性能带来了很大的影响。
-
map是平衡二叉树,适用于快速检索。由于二叉平衡树插入机制以及内存分配的原因,它不适合频繁的内存操作,包括声明map对象、插入数据、删除数据。正确的用法是首先把所有的数据加工处理准备好,然后交给map存储,以便检索。[1]
-
解决的方法:可以采用
hashmap
增大命中率。这样可能会让程序的性能有所改善。但是由于时间的原因,还没有采取相应的措施去改善代码。
单元测试:
单元测试的结果如下所示:
- 所采用的10个测试均能较好的完成。
-
|单元测试名称|内容介绍|
|:--|:--|
|EmptywordText|空文件测试|
|Up_low_wordEuqalText|大小写相等测试|
|Empty_zero_lineText|0行测试|
|IsNotWordText|错误单词测试|
|The_FistWordText|单词排序输出词频数最大的测试|
|ViodWordText|不存在单词测试测试|
|ViodlinesText|不存在有效行测试|
|ErrorFilenameText|错误文件名测试|
|VerylargewordText|超大单词测试|
|SeparatorText|分隔符测试| -
截取了部分的单元测试代码如下:
- 空文件测试
- 大小写相等测试
- 非单词测试
namespace UnitTest1
{
TEST_CLASS(EmptywordText)
{
public:
TEST_METHOD(TestMethod1)
{
// TODO: 在此输入测试代码
int count = CountChar("Emptyword.txt"); // 传入一个空的txt的文件
Assert::IsTrue(count == 0); // 它的值应该为0,测试通过
}
};
TEST_CLASS(Up_low_wordEuqalText)
{
public:
TEST_METHOD(TestMethod1)
{
// TODO: 在此输入测试代码
int count_up = CountChar("upperword.txt");
int count_low = CountChar("lowerword.txt"); //传入两个文件,一个为大写LIFE,一个为小写life
Assert::IsTrue(count_up == count_low); // 两个返回值应该相等,测试通过
}
};
TEST_CLASS(IsNotWordText)
{
public:
TEST_METHOD(TestMethod1)
{
// TODO: 在此输入测试代码
int count = Countwords_num(Countwords("NoWord.txt")); //传入一个非单词的文件,例如123file
Assert::IsTrue(count == 0); // 因为不是单词,所以返回值应该为0,测试通过
}
};
}
代码覆盖率:
代码覆盖率的结果如下图所示:
异常处理:
如果输入了一个不存在的文件名,则提示文件名错误
int CountChar(char *filename)
{
···
else infile.open(filename);
if (!infile)
{
cout << "you filename is error" << endl;
return 0;
}
···
总结与感悟
每天起床第一句,先给自己打个气!
我对软工实践的态度还是挺积极的。但是由于开学季、新生周,自己还担任班导以及一些职务,处理这些非学习上的事情可以说占据了我这一周大部分的时间。每天都要并发执行好多好多的事情,所以在作业的前半段时间,只能在深夜里留出一两个小时做软工作业。当时的念头居然是为了做作业舍不得睡觉。
┗|`O′|┛ 嗷~~
态度摆是摆在这里了,然而这次作业还是十分受限于自己的硬核实力。作业上所提出的其他要求,在之前的日子里基本上是没有碰到过的。
-
要求使用VS2017的时候我还觉得换了IDE挺麻烦的(现在使用起来似乎蛮不错)。
-
Github:之前只是皮毛的建了个基于hexo的个人主页。并没有很好的理解Github的作用。直到在写完基本功能后,尝试去学习了下Github的入门教程,仿佛发现了一片新天地。每次修改完一次的功能,就commit一下。然后可以看到自己的成长的过程,这种感觉很棒。但是自己目前仍然对github的一些命令不大熟悉,导致自己曾经有一次把库搞炸了,重头foke了一下。同时,在使用的过程中,阅读了畅畅酱博客以及构建之法的相关知识。知道了注释以及这些信息的提交尽量不要使用中文,所以在这个过程中逐渐养成了这个习惯。自己的英语功底不好,有时候还要借助谷歌翻译,但是这也是一个进步。而缺点在于,还是没有能够清晰的给出commit信息。这个需要慢慢培养一下自己。
-
性能测试:性能测试前,知道自己写的代码有点渣,也清楚在那些部分肯定是泛红的一片。导致这中原因,主要在于自己没有能够好好的分析需求,追求时间,仓促编码,而省下来的这部分时间却在编码的过程中翻倍付出。软工实践不是那种走一步看一步的事情。如果没有能够以一个相对大局的观念,统筹规划一下,组织好代码结构,写好对应的流程图,会对之后的编码以及代码的性能,可扩展性有很大的帮助的。
-
单元测试:这是个很神奇的东西。这是我稍微弄懂单元测试后的想法。而单元测试时,我在这卡壳了很久,一些链接文件刚开始也没搞好。然后陷入了一个绝望的,疯狂修改设置的循环中,导致了原本还能生成运行的exe文件都消失了。但是看着别人的博客的介绍,感觉这个应该不是一个卡住不动的地方。OK,既然陷入了一个混乱中,不如直接一刀割开。把自己的本地仓库删了,重新clone了一次(这里就体现了Github的好处)。谨慎细致的配置单元测试,发现其实使用它还是蛮容易的。看着自己马马虎虎的10个单元测试还是有点开心的。
-
代码覆盖率:这个不是很懂就是了。时间关系就不多细细讲了,没有遇到什么卡住的地方。看到自己的测试代码覆盖率也仅仅平均80%,说明了前面的测试还有很多不足的地方需要细细去调整。
这里感谢一下畅畅酱,在它的博客下,我能够比较快速的找到了自己工具需求、以及对我写博客和单元测试有很大的指导。然后在昨天也很幸运的成立了团队项目的初创四人帮,有了一个大概的方向。软工实践的第二次作业就让我知道的前面的路途不易,所以自己的实力还需要努力提高,才能更好的匹配上自己的队友,与大家并肩作战~!
同时感谢助教提供的excel表格转md文件~
后记:
分享五月天