寒假作业(2/2)
寒假作业(2/2)
Question | Answer |
---|---|
这个作业属于哪个课程 | 2021春软件工程实践|W班 (福州大学) |
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 作业目标 |
任务一
阅读《构建之法》并提问
问题1:章节4介绍了结对编程,提出了结对编程的优点及一些要领,但为什么结对编程在所了解到的工作过程中(至少国内)很少实施?
阅读该章节后,第一反应是这种方法就类似于教科书上介绍的方法:看的时候再有道理也很少会有人真正运用。
查找一些资料后结合自己想法:对于绝大多数企业最讲究的是利益和效率,更准确的说是马上看的到的回报。
结对编程确实能带来程序开发上的效率提升,但这不是立竿见影的。
其中影响最大的是结对双方的差异,比如双方的水平,性格很大程度影响结对效果。即使可以磨合培养,要承受不可预测的时间成本以及不小的失败概率。
另一方面,程序员本身对要去适应对方的这种工作生活未必有很大意愿。
问题2:章节5,6介绍了瀑布模型和敏捷流程两种区别很大的开发流程,当今软件相关企业会采用哪种?利与弊?
通过阅读可知的是瀑布模型有明确的需求,甚至说需要明确的需求驱动才会一步步进行。而敏捷开发则是通过部分需求开发满足最小需求的版本投入市场测试,根据反馈不断迭代更新软件。
个人猜测现在市场需求瞬息万变,企业更多采用敏捷流程开发。
通过查阅资料得出现在的企业确实更多采用敏捷流程开发,原因一方面是满足所有需求的开发周期太长,产品未上线可能就要被淘汰;另一方面是产品经理也不可能考虑到所有需求。
但这样的开发也同样存在弊端:“需求管理”被淡化了,很多人以为敏捷就是不需要需求管理,甚至就是“可以随便改需求”,反正可以迭代。
从这个意义上说,敏捷流程实施得不好,一个本来是用来提高效率的方法,就会被滥用成进一步搅乱开发过程的方法。
因此,我认为在敏捷流程的每一次迭代中应该谨慎的分析每一个小的需求,确认是否需要,增加后不轻易修改删除,调查市场对这个改动的反馈的时间也应该长一些。
问题3:章节9.2.1中提到了Master Programmer(MP)和SlaveProgrammer(SP),MP和其他成员交流,了解需求,MP只写抽象的伪代码,或者对功能的描述;SP根据MP的文档,实现具体的功能。SP只用和MP交流。MP其中MP和现在的产品经理PM有什么区别?
通过阅读下文可知,MP在微软创办人之一查尔斯提出之际就遭到了反对,因为没有人想做SP,刚加入团队的开发人员会问——为什么我们不能当MP?这次改革最后不了了之。
另外PM不是我一开始所想的单纯指代产品经理,实际上可指代三种:Product Manager(产品经理) Project Manager(项目经理--正确地做流程)Program Manager(微软的职位名称,负责除产品开发和测试之外的所有事情)。
文章着力介绍了微软的PM,这个职位最重要的特点是:管事不管人,和团队的其他人平等工作,带领团队达成最重要的目标,并保持团队的平衡。
阅读完章节后,很容易得到答案,即二者是不同阶段提出的职位,都旨在提高项目效率,区别是沿用至今的PM不负责软件的实际编程,是作为一个沟通链接团队人员及领导客户的存在。
问题4:章节9中提到了PM,介绍了一系列优点,那这个职位的存在对于软件开发是有利无弊的吗?
此为阅读中产生的疑问,后文给出了解答:“PM太多了以后,由于大部分决定都是经过平等而反复的讨论协商折中得到的,一个可能的负面后果便是委员会设计(Design by Committee),
一些产品不能很快跟上市场变化,在需要个人特色的方面,如设计和用户体验,这么设计出来的东西大多中规中矩,了无新意”,“牛人主导的项目,往往会大起大落;PM主导的产品中,“不犯大错”成了一个特点”。
显然PM不会百利无一弊,但对于追求稳定性的企业来说,利远大于弊。
问题5:章节16.1.4中提到:“大家听了很多创新者的故事,有些人想,他们真了不起,第一个想出了这些美妙的想法,要是我早生几十年,也第一个实现那些想法就好了”,应该如何看待这个观点?
后文明确指出了“成功的创新者未必是先行者”,通过阅读后的思考,我认为首先应把握关键词“成功”,提出想法实施的先行者必然称为创新者,但关键是不一定成功。
结合前文举例可以知道这有多方面的原因:
1、创新有颠覆式和改进式两种,颠覆式的创新往往会对行业乃至社会造成巨大变革,波及既得利者。因此最早乃至早期提出的先行者其实承受巨大压力,并不容易推行他的想法。
文章举例了雅卡尔自动织布机和史蒂夫·乔布斯的NeXT公司错过了伯蒂姆·伯纳斯-李的HTML和互联网远景
2、原文提及“创新是几代人、许多团队前赴后继持续创新的结果。就像拼图一样,很多聪明人都模糊地看出了最终图像,都在一块一块地拼接,往往拼好最后一块的人得到了最大的荣誉。”
从这个观点我们也能明白,先行者更多的做了铺路的工作,后来者在他们的经验想法上改进,最终完成了“成功的创新”,这里的成功应该指的是某个技术或产品真正开始普及。
从结果及带来的收益看,最后摘取果实的才是“成功的创新者”,但这不意味着前人就是失败的。
冷知识
在程序中bug一词用于技术错误。这一术语最初由爱迪生在1878年提出的,但当时并没有流行起来。在这的几年之后,美国上将Grace Hopper在她的日志本中,写下了她在Mark II电脑上发现的一项bug。
不过实际上,她说的真的是“虫子”问题,因为一只蛾子被困在电脑的继电器中,导致电脑的操作无法正常运行。如图片所见,她写道“这是我在电脑上发现的第一个bug”。
任务二
Github项目地址
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 15 |
• Estimate | • 估计这个任务需要多少时间 | 10 | 15 |
Development | 开发 | 370 | 652 |
• Analysis | • 需求分析 (包括学习新技术) | 60 | 120 |
• Design Spec | • 生成设计文档 | 30 | 35 |
• Design Review | • 设计复审 | 20 | 5 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
• Design | • 具体设计 | 30 | 20 |
• Coding | • 具体编码 | 180 | 310 |
• Code Review | • 代码复审 | 20 | 122 |
• Test | • 测试(自我测试,修改代码,提交修改) | 20 | 30 |
Reporting | 报告 | 60 | 67 |
• Test Repor | • 测试报告 | 30 | 42 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 15 |
合计 | 440 | 734 |
本来认为编一个统计程序很快,但考虑各方各面包括代码规范,接口设计,测试等,实际用时远超预估
因为第一次基本功能成功运行后又修改了一些功能的设计思路,并发现方法过长,于是把方法修改或拆分,故代码复审时间远超预计
解题思路描述
- 实现打开input文件并读取数据
在每个方法开头通过FileReader和BufferedReader将文件内容读入字符串后进行其他处理
- 对读取数据进行字符统计
构建方法countChar,读取文件内容,按字符遍历,在ascii(0-127)范围的计入,函数返回统计值
- 对读取数据进行行的数量统计
构建方法countLine,读取文件内容,按行遍历,若行为空则不计入,函数返回统计值
- 对读取数据进行单词的数量统计并记录单词
构建方法countWord,读取文件内容至字符串,通过str.split([^a-zA-Z0-9])将字符串分割成若包含干个小字符串的字符串数组,遍历数组对每个字符串进行判断是否满足正则表达式[a-zA-Z]{4,}[a-zA-Z0-9]*
满足则加入统计单词频率的HashMap并将计数变量count+1,函数返回统计值
- 对记录的单词按频率进行整理排序
构建方法outputWords,传入HashMap并创建TreeMap数组wordArray。
遍历HashMap,利用TreeMap数组下标作为单词出现次数,如wordArray[5]表示出现5次的单词,将HashMap中各个单词转为小写,按出现次数放入。
再利用TreeMap字典序特性用迭代器遍历TreeMap数组中每一个TreeMap输出key,10个为止。
- 按格式输出各项结果至output文件
构建方法writeToText,接收文件和字符串两个参数,将字符串写入文件。
调用该方法将各项统计值写入output文件
代码规范制定链接
设计与实现过程
- Lib中设计了9个方法,注释为功能简介
- 字符统计
while((ch=(char)bfr.read())!=(char)-1) //按字符读取文本内容
{
if(ch>=0 && ch<=127)
{
i++; //累计字符数
}
}
- 行的数量统计
while((str=bfr.readLine())!=null)
{
char[] c=str.toCharArray();
for(int i=0;i<c.length;i++)
{
if (c[i]!='\n' && c[i]!='\r' && c[i]!='\t')
{
count++;
break;
}
}
}
- 单词的数量统计并记录单词
...
String[] strs=str.split("[^a-zA-Z0-9]"); //将文本内容按分隔符分开成若干字符串
for(int i=0;i<strs.length;i++)
{
if(strs[i].matches("[a-zA-Z]{4,}[a-zA-Z0-9]*")) //依次判断字符串是否是单词
{
addWordToHash(hashMap,strs[i]); //是单词则加入统计用的hashMap
countWords++; //计数加一
}
}
- 将单词存入HashMap
//全部置为小写
str=str.toLowerCase();
if (!hash.containsKey(str)) //若尚无此单词
{
hash.put(str,1);
}
else //如果有,就将次数加1
{
hash.put(str,hash.get(str)+1);
}
- 对HashMap处理按频率和字典序输出
//遍历map,获得最大出现次数
...
//构造初始化TreeMap数组
TreeMap[] wordArray=new TreeMap[maxOccur+1];
...
//增改TreeMap数组
iterator=hash.keySet().iterator(); //重置迭代器
while(iterator.hasNext())
{
String word=(String)iterator.next();
addWordToTree(wordArray[hash.get(word)],word);
}
//遍历TreeMap数组
int num=1;
for (int i=maxOccur;i>0;i--)
{
iterator=wordArray[i].keySet().iterator();
while(iterator.hasNext())
{
String word=(String)iterator.next();
if (num<10)
{
//System.out.println(word+": "+i);
result+=word+": "+i+"\n";
num++;
}
}
}
- 按格式输出各项结果至output文件
- 清空文件方法
FileWriter fw=new FileWriter(file,true); //第二个参数true则为append方式打开文件
BufferedWriter bfw=new BufferedWriter(fw);
bfw.write(str);
- 写入文件方法
FileWriter writer=new FileWriter(file); //没有第二个参数true则为写方式打开
writer.write("");
writer.close();
- 汇总调用各个方法
//输出前清空原有内容
clearText(output);
//统计文本
...
//合成结果
...
//输出结果
writeToText(output,result);
性能改进
- 文件的输入输出采用BufferedReader,相比文件操作流更高效。
- 判断单词起初使用自己想的对字符串每一个字符判断的方法,繁琐且分支判断多。后采用正则表达式,提高代码阅读性并提升了性能。
单元测试
- 测试字符数量统计功能
@Test
public void testCountChar()
{
Lib lib=new Lib();
File testInputFile=new File("testInput.txt");
String str="\nfile\nFILE\nFile\nwindows2000\nwindows98\n123file\nfile1234\r\nfil\n\n";
int loopTimes=10000;
String testStr="";
for(int i=0;i<loopTimes;i++)
{
testStr+=str;
}
lib.clearText(testInputFile); //清空测试输入文件
lib.writeToText(testInputFile,testStr); //把测试字符串放入测试输入文件
int result=lib.countChar(testInputFile);
Assert.assertEquals(result,610000);
}
...
String str="\nabcd\nabcd\rabcd\n\r\n\t";
...
Assert.assertEquals(result,19);
- 测试行数量统计功能
...
String str="\nfile\nFILE\nFile\nwindows2000\nwindows98\n123file\nfile1234\r\nfil\n\n";
int loopTimes=10000;
...
int result=lib.countLine(testInputFile);
Assert.assertEquals(result,80000);
...
String str="\nabcd\nabcd\rabcd\n\r\n\t";
int loopTimes=1;
...
int result=lib.countLine(testInputFile);
Assert.assertEquals(result,3);
- 测试单词数量统计功能
...
String str="\nfile\nFILE\nFile\nwindows2000\nwindows98\n123file\nfile1234\r\nfil\n\n";
int loopTimes=2000;
...
int result=lib.countWord(testInputFile);
Assert.assertEquals(result,12000);
- 测试单词频率统计功能
...
String str="\nfile\nFILE\nFile\nwindows2000\nwindows98\n123file\nfile1234\r\nfil\n\n";
int loopTimes=1000;
...
lib.countWord(testInputFile); //统计单词获得HashMap
String result=lib.countWordFrequency(lib.hashMap); //利用HashMap获得频率
String target="file: 3000\nfile1234: 1000\nwindows2000: 1000\nwindows98: 1000\n";
Assert.assertEquals(result,target);
- 测试用时截图
- 覆盖率截图
process方法是汇总输出结果的,故没有覆盖率
异常处理说明
无自定义异常类,使用了IOException,cmd运行时出现参数错误会提示,其他异常会输出系统默认异常信息。
心路历程与收获
- 本次作业了解学习了git和github的使用,之前只闻其名没有尝试,这次使用体会到了代码托管备份对程序员编写修改测试程序的好处
- 阅读了大型公司的代码手册,了解相关设定并设计了自己的代码规范,改正了之前有时候会使用无意义命名的变量或函数的习惯
- 学习使用了Junit进行单元测试,相比于之前在类中四处设置测试函数,Junit更清晰规范,并体会到了函数设计粒度要尽可能小以方便测试
- 阅读教材,对软件行业一些概念名词有理更正确的认识,对项目开发,创新等方面有了更深的体会