欢迎来到fujiangfer的博客

黄师塔前江水东,春光懒困倚微风。 桃花一簇开无主,可爱深红爱浅红。
扩大
缩小

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

作业基本信息

这个作业属于哪个课程 <2021春软件工程实践S班>
这个作业要求在哪里 <软工实践寒假作业(2/2)>
这个作业的目标 <提问、词频统计>
其他参考文献 ...

目录:

  1. 阅读《构建之法》并提问
  2. 完成词频统计个人作业

Github:https://github.com/fujiangfer/PersonalProject-C

1. 阅读《构建之法》并提问

一、在3.1中提到了关于团队对个人的期望

大多数工程师都在团队的环境中工作,怎么样才是一个合格,甚至优秀的队员呢?前面提到了PSP ( Personal Software Process ),和它对应的有团队的软件流程TSP ( Team SoftwareProcess ),TSP对团队成员也有要求:

1.交流:能有效地和其他队员交流,从大的技术方向,到看似微小的问题。

2.说到做到:就像上面说的“按时交付”。

3.接受团队赋予的角色并按角色要求工作:团队要完成任务,有很多事情要做,是否能接受不同的任务并高质量完成?

4.全力投入团队的活动:就像一些评审会议,代码复审,都要全力以赴地参加,而不是游离于团队之外。

5.按照团队流程的要求工作:团队有自己的流程(见“团队和流程”一章),个人的能力即使很强,也要按照团队制定的流程工作,而不要认为自己不受流程约束。

6.准备:在开会讨论之前,开始一个新功能之前,一个新项目之前,都要做好准备工作。

7.理性地工作:软件开发有很多个人的、感情驱动的因素,但是一个成熟的团队
成员必须从事实和数据出发,按照流程,理性地工作。很多人认为自己需要灵感和激情,才能为宏大的目标奋斗,才能成为专业人士。著名的艺术家Chuck Close 说:我总觉得灵感是属于业余爱好者的。我们职业人士只是每天持续工作。今天你继续昨天的工作,明天你继续今天的工作,最终你会有所成就。

那个人应该对团队有期望吗?还是说应该如何选择适合自己的团队?

总的来说有以下几个:

选择一个好的上级,好的要求,团队才能带领你把项目做好,所以选择领头人很重要.

多看几家团队,纵向横向都比较下,自己选择一个好的,团队真的很重要.

1.团队的价值观是否认同

2.领导的能力

3.领导的处事作风是否认同

4.整个团队的风气是不是积极向上,要是死气沉沉的就不用考虑了

5.队员之间的相处

6.加入己有没有发展、提升起码要有所进步

7.公司对团队的态度

看了网上的些回答,我就感觉,好像领导者,领头人或者说团队的管理者非常重要,甚至是起决定性作用,我认为除了对领导者个人的要求外的几点,像是团队价值观、
团队风气、这些似乎也和领导者有很大关系。就好像我们不是在选团队,而是在选领导,或者说选团队本质就是选领导?

二、关于书中4.3.2提到goto

函数最好有单一的出口,为了达到这一目的,可以使用goto。只要有助于程序逻辑的清晰体现,什么方法都可以使用,包括goto

实际应用中,goto究竟适不适用?团队会不会对这方面有所要求?

goto似乎一直以来争议很大,像我们以前学C的时候老师就要我们尽量别用,有人认为只要清晰可读都能用,觉得非常的方便。

严格来说goto不是不能用,不过一般用于出错处理部分,如果你代码用goto能非常简洁且不会有任何问题的确可以用

我发现goto在很多场合都很有用,比如跳出多重循环等.

极端反对的人,像这个:

忘记goto吧。。。那是面向过程的东西。。现代编程,基本全是面向对象的。goto会使一个严谨的程序,变成一坨翔。除非你想做的是个BUG或病毒。
否则你的工作中,可能一辈子也不会用到goto

似乎更多的认为应该慎用

goto这个无条件的转移目标,以前是很多争议的,就算是现今也是争议很多.很多大神说不能用是因为(不是完全不能用是尽量不要用),
你没事跳来跳去你会搞的整个程序只有你自己看的懂,(你跳二十次之后你自己也看不懂).基本上不推广goto这无条件转移(但是是可以用的).
if & goto这两个可以一起用但尽量是少用(等你调试就知道有多无言)

goto可以用,为什么不可以用呢。只是能不用的时候就不要用,能用其他办法解决跳转的就不用goto,goto只是在迫不得已的是否才用,并且不能多用。

goto是慎用而不是不能用

我的想法也是尽量不用,但是以前图方便也会用,像书上给的例子,也可以用一个符号位解决,而避免用goto。就是不知道工作中对这个有没有强制要求之类东西。

三、书中6.3关于敏捷的团队

敏捷对团队的要求很简单:自主管理( Self-managing )、自我组织(Self-organizing )、多功能型(Cross-functional ),但是这很难做到。
软件项目的团队各式各样(请看“团队和流程”一章),假设一个团队做得还不错,现在要变成敏捷流程,那团队要做下面的改变:

1.自主管理:以前领导布置了任务,我们实现就可以了,现在要自己挑选任务;每次
Sprint结束之后,还要总结不足,提出改进,并且自己要实施这些改进。“自主管理”不等于“没有管理”。
2.自我组织:以前做好自己的事情就好了,安心下班。现在每个人要联合起来对项目负责,有人工作落后了还要帮助他改进,项目缺少某类资源还要自己顶上去。
3.多功能型:以前规格说明书由PM来写,测试由测试人员来做,现在每个人都全面负责,自己搞定规格说明书,和别人沟通,同时自己搞定测试。

这里提到了每个人要联合起来对项目负责,落后还要帮忙改进。但是书中也提到过我们在团队中要各司其职,如7.2.4。这冲突吗?

我觉得这应该是不冲突的,因为就是是各司其职也同样对项目共同负责,在敏捷的团队中也有任务的分配。但如果项目出问题了,普通
情况下肯定是由负责这部分任务的人负责,但在敏捷的团队中,也一样吗?不是说每个人都全面负责,有人工作落后了还要帮助他改进吗。
我感觉我有点混乱。

四、在12.1.3中提到的了

长期使用之后,软件会更好用么?

在设计软件界面时,我们的设计师经常会画新功能的UI设计图,来征求大家的意见。我注意到大部分设计都假设用户是头一次使用产品,
所以没有任何积累的文件、照片、处理过的图像、曾经做过的选择等数据。我同意第一印象很重要,但是当用户已经是第N次使用你的产品时,你的UI能否为这些用户提供方便呢?你的产品是下面的哪一种:

a.软件用得越多,一样难用

b.软件用得越多,越发难用

c.软件用得越多,越来越好用

软件是否好用和硬件也是相关的,但硬件发展很快(摩尔定律),所以软件开发的时候也要考虑硬件。

这abc三种情况是可能同时出现的,那么开发者如何把握软件功能的提升和对硬件的要求提升的平衡?

        同一款软件不断更新,可能有人会觉得越来越好用,因为他的硬件性能强大,有人觉得越发难用,因为他可能用的是好几年前的机器。
        拿手机来说,app的大小增长简直太可怕,几年前还是十几M或者几十M,现在动辄几G,有些应用因太大被用户暂时抛弃。
        我觉得软件提升前要考虑当前设备的持有分布,把握好用户群体分布,利益最大化;
极端假设:社会上所有人都很有钱,设备一直保持最新款,那软件的提升就只需要考虑软件本身的功能性能了,完全不用考虑用户的硬件是否支持,因为用户的硬件一直是最好的。

五、书中16.3.0提到的改良式创新(Incremental Innovation)和颠覆式创新(Disruptive Innovation)

提到了个故事:

雅卡尔( Joseph Marie Jacquard ) 1752年出生于里昂,一成年便在丝绸工坊打工,并且很快成为一个有创意的、技艺娴熟的工匠。
他的改革计划在法国大革命期间多次中断,但1805年一大批改革后改进后的半自动织机最终在法国运转了起来。
新织机不但缩短了产品的成型时间,更重要的是减轻了劳动量,减少了工作人数。这必然引起大批工人的恐慌和随之而来的抵制及破坏,
因为使用雅卡尔织布机后,原来需要六名工人完成的工作现在只需一名,这就意味着大批工人的失业。雅卡尔多次受到人身攻击,甚至有人对他以死相逼,
更严重的是,工坊里的新型织机不断被损坏和焚烧。尽管如此,革新的成果还是迅速遍及全国。1812年,整个法国已装置了一万一干多台雅卡尔自动织布机。

颠覆式创新的影响这么大,会不会对这种创新起到致命影响?比如因人身攻击之类被迫停止。有没有办法减小影响的同时改变社会?

查阅资料:

一旦颠覆性创新出现(它是市场上现有产品更为便宜、更为方便的替代品,它直接锁定低端消费者或者产生全然一新的消费群体),现有企业便立马瘫痪。
为此,他们采取的应对措施往往是转向高端市场,而不是积极防御这些新技术、固守低端市场,然而,颠覆性创新不断发展进步,
一步步蚕食传统企业的市场份额,最终取代传统产品的统治地位。

我也认为蚕食是个好办法,可以有效的减少冲突,但是需要付出时间代价;但是要是能更快的带来变革,也一定有积极的影响吧。至于会不会对创新起到致命影响,
如果那么容易被击败,也就不能称得上颠覆式创新了吧,我认为影响最多就是蛰伏一些时间,当然时间也可能很长。

小知识

打开搜索引擎,搜索“软件开发教父”,你会搜到一个叫Martin Fowler的人。

2001年2月,Martin Fowler,Jim Highsmith等17位著名的软件开发专家齐聚在美国犹他州雪鸟滑雪圣地,
举行了一次敏捷方法发起者和实践者的聚会。在这次会议上面,他们正式提出了Agile(敏捷开发)这个概念,并共同签署了《敏捷宣言》。

没错就是他。他被公认为全球知名的面向对象分析设计、UML、模式等方面的专家,现在还担任ThoughtWorks公司的首席科学家。

但是仔细一看,你会发现,他的职业写的居然是演说家。百度百科介绍:马丁·福勒是一个软件开发方面的著作者和国际知名演说家,专注于面向对象分析与设计,
统一建模语言,领域建模,以及敏捷软件开发方法,包括极限编程。

这有一篇他采访“我是一个幸运的家伙”。真的不愧是演说家。
里面提到对程序员的建议

对全世界的程序员我都是那么几条建议。

第一,每年学习并熟悉一个新的编程语言。坚持几年,你对于程序设计会有非常深刻的见解。

第二,学习测试驱动开发,这种新的方法会改变你对于软件开发的看法。

第三,劳逸结合,不要总是绷得紧紧的,爬爬山,跳跳舞,经常放松神经,你会发现你更有活力和创造力。我的一些最好的想法就是在山顶上萌发的。

2. 完成词频统计个人作业

题目要求:

假设有一个软件每隔一小段时间会记录一次用户的搜索记录,记录为英文。

输入文件和输出文件以命令行参数传入。例如我们在命令行窗口(cmd)中输入:

//C语言类  
WordCount.exe input.txt output.txt

//Java语言  
java WordCount input.txt output.txt

则会统计input.txt中的以下几个指标

统计文件的字符数(对应输出第一行):

只需要统计Ascii码,汉字不需考虑
空格,水平制表符,换行符,均算字符
统计文件的单词总数(对应输出第二行),单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。

英文字母: A-Z,a-z
字母数字符号:A-Z, a-z,0-9
分割符:空格,非字母数字符号
例:file123是一个单词, 123file不是一个单词。file,File和FILE是同一个单词
统计文件的有效行数(对应输出第三行):任何包含非空白字符的行,都需要统计。

统计文件中各单词的出现次数(对应输出接下来10行),最终只输出频率最高的10个。

频率相同的单词,优先输出字典序靠前的单词。
例如,windows95,windows98和windows2000同时出现时,则先输出windows2000

输出的单词统一为小写格式
然后将统计结果输出到output.txt,输出的格式如下;其中word1和word2 对应具体的单词,number为统计出的个数;换行使用'\n',编码统一使用UTF-8。

characters: number
words: number
lines: number
word1: number
word2: number
...

目录:

  1. PSP表格
  2. 解题思路描述
  3. 代码风格
  4. 计算模块接口的设计与实现过程
  5. 计算模块接口部分的性能改进
  6. 计算模块部分单元测试展示
  7. 计算模块部分异常处理说明
  8. 心路历程与收获

1. PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
• Estimate • 估计这个任务需要多少时间 10 20
Development 开发
• Analysis • 需求分析 (包括学习新技术) 180 360
• Design Spec • 生成设计文档 30 20
• Design Review • 设计复审 20 10
• Coding Standard • 代码规范 (为目前的开发制定合适的规范) 20 10
• Design • 具体设计 60 100
• Coding • 具体编码 360 800
• Code Review • 代码复审 120 180
• Test • 测试(自我测试,修改代码,提交修改) 120 360
Reporting 报告
• Test Repor • 测试报告 180 200
• Size Measurement • 计算工作量 30 20
• Postmortem & Process Improvement Plan • 事后总结, 并提出过程改进计划 30 30
合计 1160 2110

2. 解题思路描述

具体的函数

  1. 将文件路径作为字符串参数传递
  2. 字符计算函数将读入每一个字符并统计数量,返回值为字符数。
  3. 行数计算函数将读入每一个字符以'\r''\n'为界分隔行,并且检测空行忽略,返回值为行数。
  4. 单词计算也是读入每一个字符,检测是否符合单词规则,并且存下,返回值为单词总数。
  5. 保存单词函数会将单词存下,无返回值。
  6. 单词排序函数会将存下的单词按规则排序并返回结构数组。

3. 代码风格

https://github.com/fujiangfer/PersonalProject-C/blob/main/221801131/example/codestyle.md

4. 计算模块接口的设计与实现过程

(1)字符计算
    char code;
    int num = 0;
    infile >> noskipws;//强制读入空格和换行符
    while (!infile.eof())
    {
        infile >> code;
        if (infile.eof())
            break;    //防止最后一个字符输出两次
        num++;
    }

这里其实也进行了多次更改,一开始觉得没什么问题,结果老出这样那样的问题。'\r'读不出来也折腾了好久,原来用二进制读就能读到了。算法其实没什么好说的,
就是一个个读罢了。

(2)行数计算
    char code;
    bool flag = false;
    int num = 0; 
    infile >> noskipws;
    while (!infile.eof()) {
        while(infile >> code) {
            if (code >= 0 && code <= 127) {
                if (!isspace(code))
                    flag = true;  //标志是否空行
            }
            if (code == '\n')    //读完一行,跳出循环
                break;
        }
        if (flag){
            num++;
            flag = false;
        }
    }
  1. 一个个字符读入,强制读入空白
  2. 同时记下是否有非空的合法字符
  3. 但遇到'\n'时进行判断,不为空行则行数加一,标志位重置。
  4. 返回行数

采用一个个字符读取的方法,读到'\n'时算一行,同时设置一个标志位来算空行,只有那一行有非空ASCII字符才会计数一行。

--------原来'\r'不做换行(其实原来就没考虑)。没有注意要求文档,谢谢助教提醒。

(3)单词计算&保存&排序
    char word;
    char* str = new char[100];  //记录单词
    bool flag = false;
    bool isWords = false;
    int charCount = 0;//记录字母数
    int num = 0;
    word = infile.get();
    while (true) {
        if (word >= 'A' && word <= 'Z')word += 32;
        if (flag)
        {
            if (!((word >= 'a' && word <= 'z') || (word >= '0' && word <= '9'))) { //确认一个单词后寻找下一个间隔符
                if (isWords) {
                    str[charCount] = NULL;
                    safeWord(str);
                }
                memset(str, NULL, sizeof(str));
                flag = false;
                isWords = false;
                charCount = 0;
            }
            else if (isWords) {
                str[charCount++] = word;
            }
        }
        else {
            if (word >= 'a' && word <= 'z') {    //判断是否是字母
                str[charCount++] = word;
                if (charCount == 4) {     //4个英文字母开头时计一个单词数并寻找下一个间隔符
                    num++;
                    flag = true;
                    isWords = true;
                }
            }
            else {                    //没有4个英文字母开头则寻找下一个间隔符
                flag = true;
                continue;
            }
        }
        if ((word = infile.get()) == EOF)break;
    }
    if (isWords) {                   //若最后一个为单词则保存
        str[charCount] = NULL;
        safeWord(str);
    }
  1. 逐个读取字符,若为大写则换为小写。
  2. 连续4个字母,记下为单词,单词数加1。
  3. 若为单词则继续保存字符,直到下一个分隔符。
  4. 遇到分隔符,检测当前存储是否为单词,若是则存下。
  5. 因为最后一个单词可能碰不到分隔符,就额外判断一次。

简单粗暴,还是一个个读取字符,碰到连续四个字母开头,分隔符间隔的单词存入map中,保存函数如下:

void WordCount::safeWord(char* str) {
    string word;
    word = str;
    map<string, int>::iterator iter = words.find(word);//找单词 
    if (iter == words.end()) {
        words.insert(pair<string, int>(word, 1));//没找到单词,插入单词并设定次数为1 
        wordnum2++;                              //单词个数+1
    }
    else {
        iter->second++;//找到单词,单词出现次数增加 
    }
}

然后就是排序了,借用vector把map中的单词排序,然后存入结构数组中。

typedef pair<string, int> PAIR;

struct CmpByValue {
    bool operator()(const PAIR& lhs, const PAIR& rhs) {
        if (lhs.second == rhs.second) {//词频相同时
            if (lhs.first.compare(rhs.first) > 0)return false;
            else return true;
        }
        else return lhs.second > rhs.second;
    }
};

void WordCount::wordsort() {
	Words temp;
    map<string, int>::iterator iter;
    iter = words.begin();
    vector<PAIR> vwords(words.begin(), words.end());
    sort(vwords.begin(), vwords.end(), CmpByValue());
    for (int i = 0; i != vwords.size(); ++i) {
        wwords[i].word = vwords[i].first;
        wwords[i].count = vwords[i].second;
    }
}

类和结构体的声明

struct Words {         //用于存放单词及次数
    string word;
    int count;
};

class WordCount {
private:
    int characternum;
    int wordnum1;        //单词总数
    int wordnum2;        //单词个数
    int linenum;
    map<string, int> words;//用于存放单词及次数
    Words* wwords;
public:
    WordCount();
    WordCount(char* Path);
    int getcharacternum();
    int getlinenum();
    int getwordnum1();
    int getwordnum2();
    Words* getwords();
    int charactersCount(char* Path);
    int wordCount(char* Path);
    int lineCount(char* Path);
    void safeWord(char* str);
    void wordsort();
};

5. 计算模块接口部分的性能改进

一开始单词频数的排序我用了简单的冒泡

for (int i = 0; i < count; i++) {                              //冒泡排序
    for (int j = 0; j < count - i - 1; j++) {
        if (wwords[j].count < wwords[j + 1].count) {
            temp = wwords[j];
            wwords[j] = wwords[j + 1];
            wwords[j + 1] = temp;
        }
        else if (wwords[j].count == wwords[j + 1].count) {
            if (!wwords[j].word.compare(wwords[j + 1].word)) { //频率相同的单词,优先输出字典序靠前的单词。
                temp = wwords[j];
                wwords[j] = wwords[j + 1];
                wwords[j + 1] = temp;
            }
        }
    }
}

然后跑了个大的文件,跑了1分多钟,完全不行啊,然后就去查找资料,学习map怎么排序,结果在前面,就不再放一遍了。

总之就是速度提高了很多。

性能分析

同一个文本的检测,文本不大,因为性能分析跑的太慢,还占空间,本来跑了大文本,报告几十g,c盘都给它搞完

所以跑了这个不打的文本测试

改进前

改进后

明显看出来时间的减少,和排序函数时间占比的下降

此外就是找单词的函数,可以用正则表达式完成,可以节省许多代码,但我也有点乱,记不住,这就不写了。

6. 计算模块部分单元测试展示

选了一些数据进行单元测试包括大文本、含有'\r'文本、空文本等等。因为题目要求说不考虑中文等,就没做中文相关单元测试,但是写代码的时候考虑了中文之类的,大都正常通过了。

覆盖率如下:

7. 计算模块部分异常处理说明

做了文件打开失败的提醒

    if (!infile) {
        cout << "文件打开失败!" << endl;
    }

或者

    if (!input) {
        cout << "未输入文件名或文件不存在" << endl;
        return 0;
    }	

8. 心路历程与收获

        其实就是比预想的难熬了些。本以为不是太难,然后同学发来一个大文本,炸了。修修补补,与同学一比,擦,结果不一样,还不知道谁错。
只得把词频全印出来,没毛病,他出问题了(>-<)。深夜12点,另一位同学发来贺电,给我带来了\r的福报,好家伙,行数统计和字符统计
都炸了...淦,好吧找资料,修修补补,搞完睡觉。
        然后就是单元测试性能分析这些,真的是没啥头绪,就只能一遍查资料一边慢慢搞定了。还有代码风格这事,
感觉编译器会对我有挺大的影响...像vs会自动往运算符两边加空格,但是修改的时候不会补上空格,有时候我也会忘记。还有就是{我其实不习惯重开一行,但是编译器生成,
或者规范是会单独一行。

posted @ 2021-03-02 20:31  fujiangfer  阅读(208)  评论(7编辑  收藏  举报