软工实践第二次作业

软件工程实践寒假作业(2/2)

这个作业属于哪个课程 2021春软件工程实践|W班(福州大学)
这个作业要求在哪里 作业要求
这个作业的目标 阅读构建之法,提出问题。学习使用git以及github
,实现WordCount程序
作业正文 软工实践第二次作业
其他参考文献 《构建之法》

阅读《构建之法》并提问

问题一

在第二章 个人技术和流程中,讲到如果用随机数以增加测试的真实性是不好的,他的回答是:

我们还是要用随机数等办法“增加测试的真实性”,但不是在单元测试中。

对此我有疑问,什么是单元测试?为什么不在单元测试中用随机数等方法增加测试的真实性?

通过深入了解我认为

单元测试(unit testing):是指对软件中的最小可测试单元进行检查和验证。
而随机数进行测试时导致程序出错后,不能确保下一次运行又能重复这次错误。单元测试是为了保证某一小部分功能的正确性,以确保不会影响到整个程序运行,所以它必须包括输入数据和预期结果。

问题二

在第四章 两人合作中,提到的结对编程的方法让我难以体会到他的高效性,虽然说这样的安排可以让代码更规范,错误也会减少。但是就我自己来说,从来没有试过两个人一起编程。我认为结对编程会导致我在投入编程时思路被频繁打断。那为什么要采取结对编程呢?

在查阅资料后我注意到一点,越是顶尖的公司,他们越愿意采取结对编程的方法,这从侧面证明了结对编程一定是有价值的,是利大于弊的。而且我还在网上看到一个软件工程师的发言“结对编程只有体验过的人才知道他的功效”,在他的观点中,结对编程是最好的“老带新”的方法,两个人的磨合可以让新人快速成长。在有些模块中,结对编程可能并不适用,但是在培训和做重要、基层模块时,它能起到十分优秀的效果。

问题三

在第二三四章中,我看到了审查的重要性,那审查环节是怎么进行的呢,是由专门的人负责吗?

在询问过一个认识的在佛山工作的程序员之后我才知道,不是所有的公司都会采取像教材中那样的方式。在小公司中他们采取的是每个人负责一块,自己进行自己模块的审查,只有测试环节是别人做的,甚至在有些小项目,测试环节也是自己进行的。这种形式更类似于我们学生在学校的合作模式。造成这种模式的原因我觉得是为了效率,以及团队代码的不规范。这种合作方式是有局限性的,所以我们要从现在开始就有意识地规范代码。

问题四

第十六章讲了好多关于创新的案例,但是我有个疑惑,关于创新的方向和时机这些问题是应该又程序员来考虑的吗,不应该是策划之类的?

一开始我一直认为程序员要做的就是完成自己的那块任务,但是实际上程序员的编程环节第一段就是需求分析,在实际工作中不像做题这样,划定了输入输出,有着标准答案。在需求分析中,我们就应该找到创新的机会。而策划的工作是负责公司项目企划工作的全面掌控。包括组织、参与、指导企划方案的制定。他可以进行工作的分配,而如何实现功能还是要看我们程序员。

问题五

在十六章中我了解到,那些行业的先驱者不一定就是最后的赢家,那些先驱者为什么没能保住优势,反而被后起之秀替代了?

在深入了解后不难发现,这些先行者失败的原因大多有两方面,一方面是资金不足。新的领域有着新的风险,新的创新的用户是有一个增长过程的,一开始的投入往往得不到相应的回报,便难以继续发展。另一方面是由于没能继续创新,成功的公司也需要不断的创新,比如google搜索引擎,虽然是后面才加入的企业,但是它采用了分布式爬行系统网页采集技术、页面等级技术和超文本匹配分析技术,提供图像搜索功能、学术搜索、地图搜索、在线翻译、新闻网站群、年度排行榜、网页快照、语言转换等功能。在先行者的基础上继续创新,才能有今天的成就。

冷知识

历史上第一款数字计算机游戏遭遇巨大失败。第一个电脑游戏出现于1962年,由麻省理工学院
的计算机程序员Steve Russell与其团队一同编写,这款名为《太空大战》的游戏耗费了他们
近200个小时。来源网址

虽然现在游戏行业已经十分壮大,但是在1962年,游戏并得不到大众的接受,这便说明了创新
的风险性。行业的创新需要考虑到市场,消费群体等很多方面。特别是像他们这样开创一个全
新的行业,便有着更大的不确定性和风险,并且往往付出与回报是不匹配的。但是创新又是必
要的,有了他们迈出的第一步,才有了现在如此发达的游戏产业。

词频统计个人作业

GitHub

PersonalProject-C

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.实现有效行统计
统计有效行就是排除掉空白的行,然后逐行读取。

代码规范

codestyle.md

设计与实现过程

创建一个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表时,我深刻的感受到了拟定计划对于编程的重要性。同时我也意识到调试,测试程序是一件耗时耗力但又十分重要的事情。在这次作业之前,我从来没有进行过单元测试,也不知道代码覆盖率是什么。现在我才领悟了,以前的编程是不完整的,没有审查环节的编程是远远不够的。在测试中我发现我的程序还有很多问题,这都是在以前所发现不了的。

posted @ 2021-02-28 16:49  murasameEM  阅读(158)  评论(5编辑  收藏  举报