寒假作业(2/2)
| 这个作业属于哪个课程 | 2021春软工实践W班(福州大学) |
| ---- | ---- | ---- |
| 这个作业要求在哪里 | 寒假作业2/2 |
| 这个作业目标 | 1.阅读《构建之法》并提问
2.WordCount编程 |
| 其他参考文献 | CSDN,《构建之法》 |
阅读《构建之法》
思考
1.关于需求分析
如上图,一个项目的最终结果受多方面客观因素的影响,公司调研收集需求,小组进行分析,PM写出spec,开发人员根据spec开发,最终推出的软件和市场需求相差巨大。这让我感到项目开发的困难,大学期间的项目多偏向简单,但是工作时,项目会十分复杂,参与人数众多,一定会存在信息差,比如,PM要求开发人员实现某项功能,但在开发阶段开发人员反馈无法实现该功能或因技术原因与设计有偏差。每一个开发步骤的细微的不同,都会导致结果偏差巨大。因为缺乏大型项目开发经历,所以对于如何将推出的软件更贴合用户需求,如何提高团队的效率我有些迷茫。
2.关于PSP
PSP作为软件工程师开发的模型,明确标明在各阶段应该做的事情和所花费的时间,按照逻辑思维排序的方式也非常有利于工程的推进,但是我认为,PSP表格的各项较为笼统,难以预估准确时间。比如本次实践,使用PSP预估时间,但因为对需求的不熟悉,导致实际所用时间与预估时间相差较大。同时,有时候任务的执行顺序并不是按PSP的步骤执行,而采用并行或跳过某步骤进行开发。我认为PSP是能有效规划安排开发工作,但是如何使用它,使它更贴近实际情况,我有些疑惑。
3.关于创新
在创新迷思中提到,很多故事书里的聪明人忽然灵光乍现的故事。但在软件方面,皮特·德鲁克讽刺:“我们上课的同学也想了不少宏大的创新思想,但是课程最后什么也没做成,剩下的就是一个空的构想。”很多想法是不错的,但是过于冗杂的实现问题劝退创新者。对此,书中也立即给出了回答,“就像拼图一样, 很多聪明人都看出了最终图像, 都在一块一块地拼接, 往往拼好最后一块的同学得到最大的荣誉。 但是没有前人的积累,没有自己扎实的能力, 没有 ”最后一块” 等着同学们去拼”。你看学者灵光乍现,但这是基于他们在顿悟之前已经打下深厚的基础,学识支撑他们的理论,所以积累是不分行业的。
4.关于测试
当项目开发结束,接着就进入测试环节。一百个人心中有一百个哈姆雷特,用户在使用功能时的输入可谓是千差万别,那是否测试人员需要进行成千上万次测试?在这方面,书中下文解释按不同方面进行分类。实际我在网上搜集资料后了解到,测试按测试目的分为多类,比如按软件质量特性,分为可靠性测试,压力测试,安装测试等等。测试前将可能的用户输入归类,再列出类型的典型用例。
5.关于绩效管理
如何给团队成员论功劳算的上是永久性话题。即使在大学期间也会有这样的困扰,“这位同学在团队工作中划水,为什么他最后贡献率和我一样!”这类问题,只要是团队,多少都想过。步入社会后,这类型问题会急剧加深,书中也给了各种解决方案。我比较认同按“完成任务维度+团队贡献维度”的二维评价体系。按劳论功看起来是比较合理的方法,但团队的氛围直接影响团队的进展,沟通等方方面面,我倾向从两方面来论功。
冷知识
第一个bug的发现及命名
计算机业有史以来最杰出女性,Cobol语言设计者,被誉为Cobol之母,美国海军将军。
设计第一个编译程序,发现世界上第一个BUG。
霍珀的主要任务是编写程序,她为MarkⅠ,以及后续机器MarkⅡ、Mark Ⅲ编写出大量软件。有一次,格蕾丝使用的MarkⅡ机出了故障。出错的继电器找到了,故障的原因也找到了:里面有一只翼展达4英寸蛾子。蛾子被用镊子夹了出来,她们用胶条贴在记录This is the first actual bug found。”(这是发现的第一只虫子。)
bug这个词后来成为计算机领域里的一个习惯说法。
WordCount
GitHub项目地址
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 960 | 107 |
• Analysis | • 需求分析 (包括学习新技术) | 180 | 100 |
• Design Spec | • 生成设计文档 | 30 | 40 |
• Design Review | • 设计复审 | 10 | 10 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
• Design | • 具体设计 | 60 | 120 |
• Coding | • 具体编码 | 480 | 540 |
• Code Review | • 代码复审 | 60 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 180 |
Reporting | 报告 | 180 | 120 |
• Test Repor | • 测试报告 | 60 | 40 |
• Size Measurement | • 计算工作量 | 20 | 20 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 100 | 40 |
合计 | 1160 | 1210 |
解题思路描述
需求分析
功能:读写文件,解析并统计文件内容
应用场景:
- 命令行测试程序使用
- 在单元测试框架下使用
- 与数据可视化部分结合使用
解题思路
- 考虑本次统计字符等功能,使用C++编写程序;
- 在读入文件同时,将文件内容以string类型的变量存储;
- 统计字符数,调用string变量的length()函数;
- 统计行数,按读入‘\n’划分,字符一个一个读入,若非空白字符,则存入临时的字符串中;若是则不做处理,最终遇到'\n'再判断字符串是否为空。
- 统计单词数,遍历,直到读到第一个字母或数字,接着向后读取三个字符,判断该四字符是否均为字母,若是,接着向下读取,直到遇到分割符;若不是,则break;将合理的单词和频率存入map容器中;
- 单词频率排序,遍历map容器内容至vector容器中,再使用vector的sort()函数得出。
代码规范制定
设计与实现过程
项目架构
- 首先设置一个类CountFile用来包含将会使用的功能函数。
class countFile{.....}
- 实现功能的方法
class countFile{
public:
string getString(ifstream& in);
void loadMap(string str);
//字符和行统计数
void countChara(string str,ofstream& out);
void countLine(string str,ofstream& out);
void countWord(ofstream& out);
//利用向量排序
vector<pair<int,string> > getSort();
private:
map<string,int> mp;
};
getstring()
用来将读入文档的字符流以string类型存储,并在遍历过程中实现大小写,转换便于后续功能的实现处理;loadMap()
该函数将遍历字符串,并判断是否为正确字符,若是再写入map容器中;countChara()
从参数获取文档字符串,用length获得字符串长度;countLine()
按读入‘\n’划分,再按照有效行数规定判断;countWord()
将map容器中int项相加,得到单词树目;getSort()
将map容器数据转存储在vector容器中,通过sort函数直接获得排序结果。
核心代码
- 将有效单词插入map中,判断单词是否合法顺序如流程图所示:
void countFile::loadMap(string str){
for(int i=0;i<str.length()-3;i++){
//跳过非字母和非数字字符
while(i<str.length() && !isdigit(str[i]) && !islower(str[i])){
i++;
}
string temp;//存储合理单词
bool flag=true; //判断是否符合四个字母开头
for(int j=0;j<4;j++){
if(!islower(str[i+j])){
flag=false;
break;
}
}
if(flag){
for(int j=0;j<4;j++){
temp.append(1,str[i+j]);
}
i+=4;
while(i<str.length() && isdigit(str[i]) || islower(str[i])){
temp.append(1,str[i]);
i++;
}
mp[temp]++;
}
}
}
- 流程图
*获得字符串,转换大小写
string countFile::getString(ifstream& in){
char ch;
//文本内容作为字符串存储
string str;
while((ch=in.get())!=EOF){
//asii码字符范围
if(ch>0&&ch<=127){
//字母大写->小写
if(ch>=65 && ch<=90){
ch+=32;
}
str.append(1,ch);
}
}
return str;
}
- 排序
vector<pair<int,string> > countFile::getSort()
{
map<string, int>::iterator it;
vector<pair<int, string> > ve;
for (it=mp.begin(); it!=mp.end(); it++)
{
ve.push_back(make_pair(it->second,it->first));
}
sort(ve.begin(), ve.end(),cmp);
return ve;
}
性能改进
接口改进及函数提取
- 因为功能最初为一步一步实现,部分功能存在重复步骤,比如打开文件,遍历字符串等等。
后期定义函数,调用使用。eg:loadMap()
,getString()
- 接口改写
为了方便模块测试,统计字符,单词,行数函数接口转为获得字符串,返回int。但后续输出崩溃,且接口过多繁杂,又使用最初版本。
单元测试
- 测试统计字符数
TEST_METHOD(Chara)
{
countFile CF;
//字符统计
Assert::AreEqual(1, CF.countChar(" "));
Assert::AreEqual(3, CF.countChar(" \n\t"));
}
- 测试单词数
TEST_METHOD(Words)
{
countFile CF;
//字符统计
Assert::AreEqual(1, CF.countWord("asdf 123asdfg"));
Assert::AreEqual(2, CF.countWord("asdf456\nqwer"));
Assert::AreEqual(0, CF.countWord("\n \t"));
Assert::AreEqual(1, CF.countWord("agebdr..."));
}
- 测试有效行数
TEST_METHOD(Line)
{
countFile CF;
//字符统计
Assert::AreEqual(1, CF.countLine(" qwe123\n\n"));
Assert::AreEqual(0, CF.countLine(" \n \n \n"));
Assert::AreEqual(0, CF.countLine(""));
Assert::AreEqual(1, CF.countLine("yuui\n"));
}
测试这块没有完全搞懂,只能测试简单例子,函数接口改到心态爆炸,只能做到这种地步,覆盖率没有做成功,°(°ˊДˋ°) °。
异常处理说明
- 命令行参数缺失
if (!argv[1] ) {
cout << "Please enter read file!";
return 0;
}
if(!argv[2]){
cout << "Please enter write files";
return 0;
}
心路历程与收获
- 在本次实践中,初次使用单元测试的方法进行测试,虽然使用中问题很多,耽误很长时间,但是它测试比较方便;
- 本次作业代码难度不大,但细节偏多,最初编写的算法冗杂,在后续优化上较为乏力;
- 代码方面使用C++,提前用了一天复习和查资料,编程还是应该时刻做,才能更好的掌握语言。
- 最深刻的教训是设计时一定要计划函数之间的接口,设计阶段工作没做好,在测试阶段为了能更直观简单的测试,又再次修改自己的各函数的接口,结果提取出过多函数反而导致代码崩溃。所以前期的工作一定要做好,对之后的任务要有自己的把控,避免后期任务崩盘。