#软工实践-个人项目-词频统计

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文件~


后记:

分享五月天

posted @ 2018-09-12 19:34  Huzr  阅读(272)  评论(0编辑  收藏  举报