软工实践第二次作业
软件工程实践寒假作业(2/2)
这个作业属于哪个课程 | 2021春软件工程实践|W班(福州大学) |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 阅读构建之法,提出问题。学习使用git以及github ,实现WordCount程序 |
作业正文 | 软工实践第二次作业 |
其他参考文献 | 《构建之法》 |
阅读《构建之法》并提问
问题一
在第二章 个人技术和流程中,讲到如果用随机数以增加测试的真实性是不好的,他的回答是:
我们还是要用随机数等办法“增加测试的真实性”,但不是在单元测试中。
对此我有疑问,什么是单元测试?为什么不在单元测试中用随机数等方法增加测试的真实性?
通过深入了解我认为
单元测试(unit testing):是指对软件中的最小可测试单元进行检查和验证。
而随机数进行测试时导致程序出错后,不能确保下一次运行又能重复这次错误。单元测试是为了保证某一小部分功能的正确性,以确保不会影响到整个程序运行,所以它必须包括输入数据和预期结果。
问题二
在第四章 两人合作中,提到的结对编程的方法让我难以体会到他的高效性,虽然说这样的安排可以让代码更规范,错误也会减少。但是就我自己来说,从来没有试过两个人一起编程。我认为结对编程会导致我在投入编程时思路被频繁打断。那为什么要采取结对编程呢?
在查阅资料后我注意到一点,越是顶尖的公司,他们越愿意采取结对编程的方法,这从侧面证明了结对编程一定是有价值的,是利大于弊的。而且我还在网上看到一个软件工程师的发言“结对编程只有体验过的人才知道他的功效”,在他的观点中,结对编程是最好的“老带新”的方法,两个人的磨合可以让新人快速成长。在有些模块中,结对编程可能并不适用,但是在培训和做重要、基层模块时,它能起到十分优秀的效果。
问题三
在第二三四章中,我看到了审查的重要性,那审查环节是怎么进行的呢,是由专门的人负责吗?
在询问过一个认识的在佛山工作的程序员之后我才知道,不是所有的公司都会采取像教材中那样的方式。在小公司中他们采取的是每个人负责一块,自己进行自己模块的审查,只有测试环节是别人做的,甚至在有些小项目,测试环节也是自己进行的。这种形式更类似于我们学生在学校的合作模式。造成这种模式的原因我觉得是为了效率,以及团队代码的不规范。这种合作方式是有局限性的,所以我们要从现在开始就有意识地规范代码。
问题四
第十六章讲了好多关于创新的案例,但是我有个疑惑,关于创新的方向和时机这些问题是应该又程序员来考虑的吗,不应该是策划之类的?
一开始我一直认为程序员要做的就是完成自己的那块任务,但是实际上程序员的编程环节第一段就是需求分析,在实际工作中不像做题这样,划定了输入输出,有着标准答案。在需求分析中,我们就应该找到创新的机会。而策划的工作是负责公司项目企划工作的全面掌控。包括组织、参与、指导企划方案的制定。他可以进行工作的分配,而如何实现功能还是要看我们程序员。
问题五
在十六章中我了解到,那些行业的先驱者不一定就是最后的赢家,那些先驱者为什么没能保住优势,反而被后起之秀替代了?
在深入了解后不难发现,这些先行者失败的原因大多有两方面,一方面是资金不足。新的领域有着新的风险,新的创新的用户是有一个增长过程的,一开始的投入往往得不到相应的回报,便难以继续发展。另一方面是由于没能继续创新,成功的公司也需要不断的创新,比如google搜索引擎,虽然是后面才加入的企业,但是它采用了分布式爬行系统网页采集技术、页面等级技术和超文本匹配分析技术,提供图像搜索功能、学术搜索、地图搜索、在线翻译、新闻网站群、年度排行榜、网页快照、语言转换等功能。在先行者的基础上继续创新,才能有今天的成就。
冷知识
历史上第一款数字计算机游戏遭遇巨大失败。第一个电脑游戏出现于1962年,由麻省理工学院
的计算机程序员Steve Russell与其团队一同编写,这款名为《太空大战》的游戏耗费了他们
近200个小时。来源网址
虽然现在游戏行业已经十分壮大,但是在1962年,游戏并得不到大众的接受,这便说明了创新
的风险性。行业的创新需要考虑到市场,消费群体等很多方面。特别是像他们这样开创一个全
新的行业,便有着更大的不确定性和风险,并且往往付出与回报是不匹配的。但是创新又是必
要的,有了他们迈出的第一步,才有了现在如此发达的游戏产业。
词频统计个人作业
GitHub
PSP
Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|
计划 | 20 | 20 |
估计这个任务需要多少时间 | 20 | 20 |
开发 | 390 | 563 |
需求分析 | 20 | 35 |
生成设计文档 | 20 | 30 |
设计复审 | 10 | 8 |
代码规范 | 10 | 30 |
具体设计 | 120 | 180 |
具体编码 | 120 | 240 |
代码复审 | 30 | 20 |
测试 | 60 | 20 |
报告 | 40 | 40 |
测试报告 | 10 | 10 |
计算工作量 | 15 | 10 |
事后总结, 并提出过程改进计划 | 15 | 20 |
合计 | 450 | 623 |
解题思路
1.输入输出
按题意可以知道输入输出都是以文本形式进行,所以查找有关c++文件流的资料。由于读写于文件不方便调试,所以这一步到最后一步进行。
2.实现字符数统计
统计字符数,由于测试用例中不会出现中文的情况,所以只要知道每行的字符串长度再相加就可以了。
3.实现词频统计
统计单词数在每一次分隔符后判断是否为单词,比较容易实现。
统计词频,首先要知道单词是否重复,所以我设想用vector创建一个words结构体的链表,对于每一个单词遍历一遍链表,找是否是已经存在的单词。如果是则num++,如果不是则在链表末尾插入。
要求输出前10的单词,所以还要对words进行排序。
4.实现有效行统计
统计有效行就是排除掉空白的行,然后逐行读取。
代码规范
设计与实现过程
创建一个words结构体,包含string类型的word和int类型的num,并创建vector数组。
typedef struct
{
string word;
int num;
}words;
vector<words> v;
文件读取,文件名通过main函数得到
//统计字符数
int count = countLetter(argv[1]);
//统计词频,存入words链表
int word = countWord(argv[1]);
//统计行数
int rows = countRow(argv[1]);
//文件输出流
ofstream out(argv[2]);
if(!out){
cout << "无法打开文件";
exit(1);
}
out << "characters: " << count << '\n';
out << "words: " << word << '\n';
out << "lines: " << rows << '\n';
wordsort();
for(int i = 0;(i < v.size() && i < 10);i ++){
out << v[i].word << ": " << v[i].num << '\n';
}
统计字符数:由于没有汉字,可以通过逐字符读取来得到字符数,这样也解决了\n,\r的问题。
//字符数
int count = 0;
while(fin.get(c)){
count ++;
}
统计词频:分为两部分,一部分是单词的提取,一部分是对单词出现次数的统计
- 单词的提取
逐字符读取,遇到字母开始记录,用letters记录下开头有连续几个字母,直到
出现非字母数字则结束记录,看letters数是否大于等于四个,符合要求则记录
下单词。
//读取到非英文字母结束,遇到大写全部转化为小写,aword是临时记录的单词
while((s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z')){
if(isupper(s[i])){
s[i] = tolower(s[i]);
}
aword.append(1,s[i]);
letters ++;
i++;
}
//读取到非英文非数字结束
while((s[i] >= 'A' && s [i] <= 'Z')||(s[i] >= 'a' && s[i] <= 'z') || (s[i] <= '9' && s[i] >= '0')){
if(isupper(s[i])){
s[i] = tolower(s[i]);
}
aword.append(1,s[i]);
i ++;
}
//判断是否符合单词的条件
if(letters >= 4){
word ++;
int ishave = 0;
for(int j = 0;j < v.size();j ++){
if(v[j].word == aword){
ishave = 1;
v[j].num ++;
}
}
if(ishave == 0){
words newword;
newword.word = aword;
newword.num = 1;
v.push_back(newword);
}
isfirst = 1;
letters = 0;
aword = "";
- 词频的统计
遍历words的链表,将得到的单词一一对比,遇到相同则num++,没有重复的则
将词加入队尾。
int ishave = 0;
for(int j = 0;j < v.size();j ++){
if(v[j].word == aword){
ishave = 1;
v[j].num ++;
}
}
if(ishave == 0){
words newword;
newword.word = aword;
newword.num = 1;
v.push_back(newword);
}
统计非空白行数:逐行读取,遍历每行字符,出现不为空白符即可计数
while (getline(fin,s)){
for(int i = 0;i < s.size();i ++){
if((s[i] != ' ') && (s[i] != '\t') && (s[i] != '\r') && (s[i] != '\n') && (s[i] != '\v') && (s[i] != '\f')){
isnull = 0;
break;
}
}
if(isnull == 0){
rows ++;
isnull = 1;
}
}
输出最多输出前十个单词:所以需要把单词按照词频排序,用先比较词频,后比较字典顺序,按冒泡排序法排序words链表。
for(int i = 0;i < v.size() - 1;i ++){
for(int j = i + 1;j < v.size();j ++){
if(v[i].num < v[j].num){
words temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}else if(v[i].num == v[j].num){
if(v[i].word.compare(v[j].word) > 0){
words temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
}
}
}
性能改进
将原本的冒泡排序法通过重写结构体的比较函数,调用自带的sort排序。
struct words{
string word;
int num;
bool operator < (const words& b){
if(num == b.num){
if(word.compare(b.word) < 0){
return true;
}else{
return false;
}
}else{
return (num > b.num);
}
}
};
单元测试
- 字符统计
测试文件
123456
8
测试代码
TEST_METHOD(TestMethod1)
{
Lib lib;
int letters = lib.CountLetter("D:\\test.txt");
Assert::AreEqual(8, letters);
}
代码覆盖率
- 词频统计
测试文件
Stan1 stan2 STAN2 stan3 sTAn3
stAN3 Stan4 Stan4 Stan4 Stan4
words acat eminem stan SOMETHING
212312ings stan6 stan5
测试代码
TEST_METHOD(TestMethod1)
{
Lib lib;
int words = lib.CountWord("D:\\test.txt");
Assert::AreEqual(17, words);
lib.wordsort();
string s1 = "stan4";
string s2 = "stan3";
string s3 = "stan2";
string s4 = "acat";
string s5 = "eminem";
string s6 = "something";
string s7 = "stan";
string s8 = "stan1";
string s9 = "stan5";
string s10 = "stan6";
Assert::AreEqual(s1, lib.v[0].word);
Assert::AreEqual(s2, lib.v[1].word);
Assert::AreEqual(s3, lib.v[2].word);
Assert::AreEqual(s4, lib.v[3].word);
Assert::AreEqual(s5, lib.v[4].word);
Assert::AreEqual(s6, lib.v[5].word);
Assert::AreEqual(s7, lib.v[6].word);
Assert::AreEqual(s8, lib.v[7].word);
Assert::AreEqual(s9, lib.v[8].word);
Assert::AreEqual(s10, lib.v[9].word);
}
代码覆盖率
- 统计行数
测试文件
1
2
3
4
测试代码
TEST_METHOD(TestMethod1)
{
Lib lib;
int rows = lib.CountRow("D:\\test.txt");
Assert::AreEqual(4, rows);
}
代码覆盖率
异常处理
文件IO的异常处理
ifstream fin(inter);
try{
if (!fin) {
throw inter;
}
}
catch (char* s){
cout<<"open file:["<<s<<"] failed"<<endl;
exit(1);
}
心路历程与收获
-
在这次作业中,我认识到了软件工程的就业前景,也深入切实地思考了成为一个程序员会面临的问题。对于未来虽然有了更多的认识,但也认识到前路的坎坷。
-
在编写PSP表时,我深刻的感受到了拟定计划对于编程的重要性。同时我也意识到调试,测试程序是一件耗时耗力但又十分重要的事情。在这次作业之前,我从来没有进行过单元测试,也不知道代码覆盖率是什么。现在我才领悟了,以前的编程是不完整的,没有审查环节的编程是远远不够的。在测试中我发现我的程序还有很多问题,这都是在以前所发现不了的。