软工实践寒假作业2/2
项目github地址
这个作业属于哪个课程 | 2021春软件工程实践|W班(福州大学) |
---|---|
这个作业要求在哪里 | 软工实践寒假作业2/2 |
这个作业的目标 |
1.阅读《构建之法》并提问 2.设计一个程序,能够满足一些词频统计的需求。 |
github地址链接 | https://github.com/1069042059/PersonalProject-C |
其他参考文献 | 《构建之法》、邹欣老师博客、博客2、c++单元测试、opencppcoverage、cmomit教学 |
目录
阅读《构建之法》的疑问?
问题一、仅在必要时,才使用“类”?
- 问题出处:4.3.4如何处理C++中的类中提到,只有在必要的情况下才使用类。
- 自我认知:在c++的学习当中,类是作为一个很重要的知识点进行学习,上课写程序老师也是尽量让我们用类进行书写。根据《构建之法》,c++课程对类的教学投入那么多时间是否必要,而且类与结构体相似,书中也提交能用结构体就不要使用类,是书过于片面还是我的认知有误。
- 寻求解答:类封装很多时候都是为了安全。C++语言中定义一个函数,那么在其他文件中(假定你有很多.c文件)定义的函数是可以访问的。除非把函数定义成static。有了类,只要把函数改成private,那么该函数就只有自己可以访问了,其他文件都访问不了。相当于把自己写的函数保护起来了。类还有作用就是继承。比如汽车和火车可以同时继承于车。这样可以使程序的结构很清晰 至于其他的,类中属性也是很强大的存在。get和set方法等。一个private变量通过get和set方法可以保证其他类可以用该变量有可以保护该变量不被非法访问。
问题二、老板驱动的流程(Boss-Driven Process)引发的思考
- 问题出处:5.3开发流程
- 自我认知:私以为老板驱动的流程不算是一种开发流程,开发流程应该是从需求开始,从程序发布结束。而老板只能算是旁观者,特殊的用户,通过老板的要求进行程序的推进,但推进流程依然按照正常的步骤进行。
- 寻求解答:当软件订单的获得不是主要靠技术实力,而是靠个人关系,或者暗箱操作的时候,老板的能力决定了一个团队是否能获得订单,既然软件的具体功能并不重 要(或者哪个团队做水平都差不多),那么老板说做什么就做什么。但这与开发流程是一个事情吗?抱有疑问
问题三、用户调查问卷如何获得正确的需求?
- 问题出处:8.3 获取用户需求——用户调查
- 自我认知:这种方法是指向用户提供事先规定好的问题,让用户回答。但是,这种情况下用户一般不会专心或者说符合自己内心想法地填写问卷,一般都是随意填写,随便选择。如此收集到的用户需求如何提取出重要的信息,筛选掉杂乱问卷。纵然依照《构建之法》里所说的设计问卷,但还是无法引导用户来正确表达自己内心的需求。
- 寻求解答:明确调查目的和内容,问卷设计应该以此为基础;明确针对人群,问卷设计的语言措辞选择得当;在问卷设计的时候,就应该考虑数据统计和分析是否易于操作;问题数量合理化、逻辑化,规范化
问题四、PM的盛行会导致产品跟不上潮流或者因为过多的调研而导致在一项领域的占领落于下风吗?
- 问题出处:9.3 PM做开发和测试之外的所有事情
- 自我认知:目前软件更新迭代过快,一个新的领域出现到一家独大不过数月之短。若一个PM调研时间过长,讨论过久,是否会错过该软件发布的最佳时间,导致在其他拥有巨大用户的公司的竞争下销声匿迹。例如飞信与微信的竞争,飞信“灰飞烟灭”,微信长盛不衰。
- 寻求解答:随着产业的发展,软件应用的深度和广度、软件的复杂度、软件团队的复杂度都极大地提高了,PM起到沟通、交换、影响、润滑、讨价还价的作用————就像商业社会的金钱一样。
问题五、对于顺序执行的代码段也有多种组合吗?
- 问题出处:在13.2.2中的代码覆盖率测试中提到不同的代码是否执行,有很多种组合。一行代码被执行过,没有出现问题,并不表明这一行代码在所有可能条件的组合下都能正确无误地运行。
- 自我认知:我所知道的代码除了多线程之外,其他的一般是顺序执行,即使有循环结构,也是一遍一遍顺序执行下去,所以这里所说的一行代码有很多组合是不是有所夸大。
- 寻求解答:不知是我理解有误还是问题太过浅显,通过多种关键词进行检索找不到答案
WordCount编程
Github项目地址
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
• Estimate | • 估计这个任务需要多少时间 | 720 | 1750 |
Development | 开发 | ||
• Analysis | • 需求分析 (包括学习新技术) | 60 | 150 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 30 | 30 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 15 | 30 |
• Design | • 具体设计 | 30 | 60 |
• Coding | • 具体编码 | 540 | 1080 |
• Code Review | • 代码复审 | 30 | 80 |
• Test | • 测试(自我测试,修改代码,提交修改) | 30 | 180 |
Reporting | 报告 | ||
• Test Report | • 测试报告 | 15 | 30 |
• Size Measurement | • 计算工作量 | 20 | 20 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 820 | 1750 |
解题思路描述
刚拿到题目,就分部解析,一步一步思考怎么做
- 输入文件和输出文件以命令行参数传入,那命令行怎么输入,第一个问题。
- 统计文件字符数,那么只需c++的get方法读取字符循环累加就行了
- 统计单词总数,这个会比较麻烦
- 至少4个英文字母开头,那么我用了六个状态来判断是否为单词,只要前四个字符有数字就不是单词
- 单词以分隔符分割,前四个字符都是英文字母的情况下,那么他就是单词,但是只有读到空格或者换行符才会确认这是一个单词的结束。
- 统计文件的有效行数,读到某一行没有任何字符就+1
- 统计文件中各单词的出现次数
- 频率相同的单词,优先输出字典序靠前的单词.我想到了set集,但是最后发现还是哈希表适合我,最后查了c++的哈希表操作,有点久没看,忘了一些操作。将单词和次数存入一个哈希表中,再挑前10存入队列中
- 输出单词统一小写,输入时就转成小写
代码规范制定链接
设计与实现过程
分成五个文件和一个main.cpp,每个文件做一个功能。
- ParseArgs文件,用于处理命令行输入的错误处理
- CountChars文件,用于计算多少个字符
while (file.get(c)){ //字符读入,循环累加
chars++;
}
- CountLines文件,用于计算多少行
while (file.get(c)){
if (state == EMPTY && !isspace(c)){//该行有字符且字符不是空格之类的
state = NOTEMPTY;
}
if (c == '\n' && state == NOTEMPTY){//该行不为空且碰到换行符后累加
lines++;
state = EMPTY;
}
}
- CountWords文件,计算多少单词,代码与词频统计相似
- Word文件,用于词频统计
int TransWord(int state, char in, string &word){//用于计算每个单词出现的次数,然后存入哈希表中,再进行排序操作(排序操作未贴出)
switch (state){
case OUT: //一开始为0
if (Right(in)) return OUT; //碰到空格、数字和不是字母等依然为0
if (isalpha(in)) { word += in; return A; }//是字母,先组成word
if (IsNum(in)) return NoWord;//碰到数字即不是单词
case NoWord:
if (Right(in)) return OUT;
else return NoWord;
case A://判断第二个字符是否为英文字母
if (IsNum(in)) { word.clear(); return NoWord; }
if (isalpha(in)) { word += in; return B; }
else { word.clear(); return OUT; }
case B://判断第三个字符是否为英文字母
if (IsNum(in)) { word.clear(); return NoWord; }
if (isalpha(in)) { word += in; return C; }
else { word.clear(); return OUT; }
case C://判断第四个字符是否为英文字母
if (IsNum(in)) { word.clear(); return NoWord; }
if (isalpha(in)) { word += in; return WORD; }
else { word.clear(); return OUT; }
case WORD://判断单词是否结束,结束就加入哈希表
if (isalnum(in)) { word += in; return WORD; }
else{ IntoHashTable(word); word.clear(); return OUT; }
}
return ERROR;
}
- 演示结果,在小黑框输出前二十个用来调试判断,在output.txt中输出前十个
性能改进
- 项目性能测试截图
- 将main函数循环5000次,运行的时间的50.848秒,,其中main函数占了97.13%。一层一层剖析下去,main函数里面CountChars函数占了6%,CountLines函数占了6%,CountWords函数占了7%。最大的一部分还是WordNums和TopWord函数,这两个分别是判断生成单词,存储到哈希表的函数。所以要进行改进的话只能在哈希表的存储这边下功夫。哈希表不仅有push,pop还有sort,所以重要的地方是sort的改进。
- 改进方法:将sort去掉,把哈希表里面的值一个一个循环提取到一个优先队列里面,设置排序,然后直接输出就好了。
- 优化改进查的资料:优先队列与队列
单元测试
namespace CountChar{
TEST_CLASS(UnitTest1){
public:
TEST_METHOD(TestMethod1){
char filename[70] = "F:\\大三下\\软工第二次作业\\221801307\\src\\WordCount\\CountChar.txt";//字符计算测试
int count = CountChars(filename);
Assert::IsTrue(count == 20);//20个字符则成功
}
};
}
namespace EmptyFile{
TEST_CLASS(UnitTest1){
public:
TEST_METHOD(TestMethod1){
char filename[70] = "F:\\大三下\\软工第二次作业\\221801307\\src\\WordCount\\EmptyFile.txt";//空文件测试
int chars = CountChars(filename);
int lines = CountLines(filename);
int words = CountWords(filename);
Assert::IsTrue(chars == 0 && lines == 0 && words == 0);
}
};
}
namespace EmptyLine{
TEST_CLASS(UnitTest1){
public:
TEST_METHOD(TestMethod1){
char filename[70] = "F:\\大三下\\软工第二次作业\\221801307\\src\\WordCount\\EmptyLine.txt";//空行测试
int lines = CountLines(filename);
int chars = CountChars(filename);
Assert::IsTrue(chars == 4);
Assert::IsTrue(lines == 0);
}
};
}
- 共十个单元测试函数,当时构造测试数据就想着不用多,把所有小细节处理完就成功了,所以针对每一个项目函数进行两个构造函数测试,一个测试处理特殊情况,一个处理正常情况。感谢同学在群内的提问给我提供足够多的特殊情况思路。不过,有一个地方处理不太好,是用绝对路径进行单元测试,我尝试了多次相对路径的情况,也百度了,没处理成功。原本估计一小时完成,因为这个路径问题,推迟了三个小时。
- 代码覆盖率是我处理最久的一个,我跟着网上教程安装配置了opencppcoverage但一直运行失败,查了两天,所有方法都试过了还是一无所获。最后我将代码发给同学,让同学帮我进行代码覆盖率测试。这个搞的心态崩了
异常处理说明
- 文件打开异常->文件错误测试
if (!file) { //文件打开失败处理
printf("Failed to open output file.\n");
return -1;
}
- 文件不存在->空命令行测试
if (argv[1] == NULL){
printf("No input file name!\n");
return -1;
}
if (argv[2] == NULL){
printf("No output file name!\n");
return -1;
}
心路历程与收获
- 刚看到作业博客的时候助教还没通知,从头看了一遍,发现怎么那么多。但是转念一想,这不是日常吗?然后撑着看完,梳理了一遍思路,先看书再写代码,代码用c++写,java的一些功能还没摸透(后面发现c++功能我也没了解多少)
- 看书看了几天之后发现不行了,太多了,看不完,就又开始着手代码工作。但发现还有一个要求,git操作。还好我之前报名实验室就有安装配置过,有一点点基础。可是,不知道怎么回事,最近github有毒,一会登得进去,一会登不进去,导致一些commit错误,将错误的版本传了上去,后面删除重构了。不知道有没有什么关系,还是挺害怕的。
- 经过github的毒害之后我买了个加速器,心疼钱。之后便是制定代码规范了,由于加入实验室,老师给我们发了华为的c++代码规范(这也是为什么我用c++的原因,后面写代码可能会用java,毕竟思路差不多,函数API也差不多)。前置任务完成便是打代码了,打,往死里打。一开始是将所有功能都写在一个文件里面,里面有多个函数,main函数里面也实现了两个功能(行数和字符数)。
- 然后是接口封装,一开始我并没有懂“计算核心"Core模块"”是什么,不懂就百度,作为一个合格的百度人,百度使我变强。通过百度,我大致了解了,便将一个文件拆分,拆成一个函数一个.cpp文件和.h文件,头文件有对这个函数的介绍,.cpp文件内有算法逻辑和注释。
- 一切都有条不紊地进行着,单元测试、性能分析都问题不大,直到代码覆盖率。查了好久找到vs可以用的代码覆盖率工具opencppcoverage,开始查找资料、重装、反复运行了十来次之后,我还是放弃了,可能是vs要重装吧,这个还没试过。
- 收获:充实了寒假生活,重拾了对c++的stl库的使用。熟练掌握了百度这项技能,同时,对vs和vs code这两个IDE的使用更加熟练了。还有就是命令行操作,命令行操作是最像黑客的一种,而黑客是吸引我报名软件工程的一个因素。多酷啊。最最重要的是学会了软件开发的流程,单元测试,性能分析,git使用,构建之法等等,这些知识对我今后软件开发有重要作用。