首页 > 技术文章 > 软工实践寒假作业(2/2)

xyh-Tse 2021-03-03 22:32 原文

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

作业摘要

这个作业属于哪个课程 2021春软件工程实践|S班 (福州大学)
这个作业要求在哪里 软工实践寒假作业(2/2)
这个作业的目标 1.开发WordCount程序
2.学习使用github,github desktop,Jprofiler软件等
作业正文 作业正文
其他参考文献 CSDN、简书、知乎

目录:

作业摘要

Part1:《构建之法》的深入提问

《构建之法》7问

附加题

Part2:WordCount编程

Github项目地址

PSP表格

解题思路描述

代码规范制定链接

设计与实现过程

性能改进

单元测试

异常处理说明

心路历程与收获


《构建之法》的深入提问

《构建之法》7问

Queation1:

文字引用 对产品功能性需求:要求产品必须实现某些功能。
综合需求:有些需求并不是单单一个软件模块就能满足,例如,“购物网站必须在24小时内把货物发送到用户手中”,这个需求牵涉到软件系统、货物派送系统、送货部门、监控系统等不同部门的功能和执行能力。
问题描述 有定义来看,一个需求可以即归为产品的功能性需求又归为综合需求吧,这种的情况下应该归为哪一个分类?(书本P159)
说法引用 网络上的定义和书本不同,百度百科中将软件需求做如下定义:需求包括三个不同的层次—业务需求、用户需求和功能需求—也包括非功能需求。
我的经验/困惑/观点 我个人想法是把重叠的部分归为综合需求,这样这个需求可以被实现的更全面。

Queation2:

文字引用 主治医师模式运用到极点,可以蜕化为明星模式,在这里,明星的光芒盖过了团队其他
问题描述 主治医师模式可以理解为只有一个“明星”,这是不是说明一般情况下明星模式优于主治医师模式?P98
说法引用 主治医师模式缺点:在一些学校的软工课上,这种模式逐渐退化成“一个学生干活,其他学生打酱油”
明星模式缺点:团队模式强调的是团队的作用,而不是个人的独角戏,这种模式显然违背了团队模式的初衷,效率也很低
我的经验/困惑/观点 两者其实很像,但由于明星模式表现突出的人更多,我觉得只要“明星们”团结一致,大概率下会优于主治医师模式。

Queation3:

文字引用 明星也是人,也会受伤,犯错误,如何让团队的利益最大化,而不是明星利益最大化?如何让团队的价值在明星陨落之后仍然能够保持?是这个模式要解决的问题。
问题描述 明星利益最大化是不是很能帮助团队利益的最大化,还是这个模式下明星利益化反而会降低团队的利益?
说法引用 明星模式优点:对“明星”个人的成长进步可能会有所帮助。
明星模式缺点:团队模式强调的是团队的作用,而不是个人的独角戏,这种模式显然违背了团队模式的初衷,效率也很低
我的经验/困惑/观点 以我的经验,我们开展团队合作时的模式其实可以近似为“明星模式”,一些大佬来完成重要的工作,其他人来协助完成,也就是所谓的打杂。所以我认为影响这个模式的反而是”大佬“和其他人之间的关系,如果明星利益太大,会使其他人发言权大大降低,所以应该采取一种“中庸”的状态。

Queation4:

文字引用 PM通常也能写代码,能玩转Excel、PPT、Visio、甘特图,会PS,有文字功底,写的博客有人爱读,反正,总得有几招绝活吧!不用说还要有大量的阅读,对IT行业、用户心理、社会都要有广泛的了解。
问题描述 PM的能力要求太广泛了,做好一个初级PM的切入点是什么?P198
说法引用 初学PM的建议:
1.基础的东西已经具备
2.要实实在在的参与一个产品的迭代
3.要多参考竞品
4.每增加一个功能,必须要问自己为什么
5.把每一个功能拆分到很细,比如登陆,账号用手机还是邮箱,为什么要用手机?为什么要用邮箱,你自己要搞清楚,虽然现在很多产品都首选手机做账号,但不是所有的都适合;密码字段,是用字符窜还是用纯数字,还是字符串+纯数字,是几个字节,需不需要特殊字符?大小写区不区分?
6.业务逻辑要清楚的列出来,你自己清楚了,才能给开发和测试提供炮弹
7.需求,大家都在讲需求,我就不强调,每个人有自己获取需求的渠道以及判断
我的经验/困惑/观点 之前我有参与西二的PM小组,PM开发项目是有一些固定的需求文档,每个公司也会有自己的一些模板,如果想当然的填补文档的内容就会偏离正确内容,所以我觉得PM这个职业的新手需要多注意细节,不能想当然。至于切入点可能要学习一些前辈的实际经验。

Queation5:

文字引用 场景怎么写?首先针对每一个场景,设计一个场景入口(描述场景如何开始)。接着描述典型用户在这个场景中所处的内部和外部环境(内部环境指心理因素等)。然后给场景划分优先级,按优先级排序写场景。
问题描述 对场景的重要性如何排序?P219
说法引用 基本型需求>期望性需求>兴奋型需求
使用需求的金字塔法则来表达,金字塔的最底层是基本型的需求,往上是期望型需求,最上面一层是兴奋型需求。
我的经验/困惑/观点 之前写过一些需求文档,虽然也是按照规则进行书写,但是我觉得还是参杂了许多的主观看法。

Queation6:

文字引用 团队在Bug数量上上下下的过程中,要注意消灭族老的问题,让老的Bug数量为0,以防止一些问题拖而未解决,有可能掩盖深层次的设计问题,要尽早把这些问题暴露出来,一个招数是,划定一个时间期限,一定要解决在此之前发现的Bug。
问题描述 一些bug可以被认为不予解决,会不会因此出现Bounce问题?P335
说法引用 解决bug流程:
1、找产品经理确认
2、不予解决,关闭
3、要解决,写明原因给开发
我的经验/困惑/观点 我的理解是应该要有前瞻性,可能出现Bounce问题的bug就不能不被解决,但是也不排除判断出错的可能。

Queation7:

文字引用 我们常说“软件的生命周期”——这个软件开发的周期结束了,生命也结束了。我们能不能像医学的尸体解剖一样,把这个软件的开发的流程解剖一下?
问题描述 事后诸葛亮会议是用来探究发布后产生的问题,还是总结开发过程中的问题?P339
说法引用
我的经验/困惑/观点 我认为既然是畅所欲言,就集思广益,想到什么讨论什么,但是各个要点需要遵循5WHY。

附加题

软件工程发展的过程中的故事

故事:

    1986年4月11日,美国东部德克萨斯州。一位患有面部皮肤癌的男性患者正在使用Therac-25 做放射性治疗。刚刚开始治疗,一团巨大的光亮就出现在他的眼前,耳边响起了煎鸡蛋的声音,其实这是机器灼烧了他的大脑和脑干的右侧额叶。因为严重的辐射,三周后这名病人就死亡了。

    因为之前的案例没有得到AECL和医院的重视,悲剧一再地发生。类似悲剧一共发生了6起,直到1987年雅基马谷医院的最后一次事故,整个悲剧事件才结束。

    后期的调查表明,全部的医疗事故都是因为软件包含严重的bug,放射性治疗的机器在正常情况下只会发射低能量的电子束,旧型号的机器为了保证不出意外,使用硬件互锁的机制确保能量不会升高。而新的型号Therac-25为了降低成本,改用了软件锁机制。可该程序本身包含的一个一字节的计数器常常会溢出,如果操作员恰好在溢出位输入命令,软件锁的机制就会失效。导致了悲剧,患者受受到100倍的辐射剂量,痛苦的死亡。

个人见解:

    1.该事例的发生,我们可以确定Therac-25没有没有通过充分的测试就被安装到设备中,理论上没有跳过开发的必要步骤,但是工程师的过度自信缺少标准的质量体系,使代码的质量无法保证。

    2.所有的工程不能只解决问题,还要对可能发生的错误提出解决方案。如上述故事中,Therac-25本应该有个“熔断机制”保证无论如何不能发生辐射量过大的情况,但是这在开发中没有实现。

   3.软件工程师应当从Therac-25事件中吸取教训,在软件的开发中边界条件的注意防御性编程的思考软件文档的完善与准确对测试工作的重视都是对自己的要求。


WordCount编程

Github项目地址

作业的主仓库: 点击进入——作业主仓库

我的Github仓库地址: 点击进入——Github项目地址


PSP表格

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

解题思路描述

step1:学习git、github、复习java

git教程

Java教程

step2:总结要求,确定实现的流程(详细见设计与实现过程)

step3:查找资料,深入了解Map函数的使用,并实现上所述流程

根据资料一共了解了三种map集合的遍历方式

  根据资料一共了解了三种map集合的遍历方式

1.使用keySet方法便利

缺点:keySet只是返回了所有的key没有value

//KeySet()把map集合中的所有键都保存到一个Set类型的集合对象中返回
Set<String> keys = map.keySet();
Iterator<String> it = keys.iterator();
while(it.hasNext()){
    String key = it.next();
    System.out.println("key: "+key+"value: "+map.get());
}

2.使用values方法遍历

缺点:values方法只能返回所有的value,没有key

//values()把所有的值存储到一个Collection集合返回值
Collection<String> c = map.values();
Iterator<String> it = c.iterator();
while(it.hasNext()){
    System.out.println("value: "+it.next());
}

3.使用entry方法遍历

Set<Map.Entry<String,String>> entrys = map.entrySet();
Iterator<Map.Entry<String,String>> it = entrys.iterator();
while(it.hasNext()){
    Map.Entry<String,String> entry = it.next();
    System.out.println("key: "+entry.getKey()+"value: "+entry.getValue());
}

显然,第三种是最符合要求的,所以最终选择第三种来实现词频统计。

step4:单元测试

要求编写十个以上的测试用例,首先将题目的要求罗列,按照要求编造例子,一一对应测试,再找几篇真正的英语文字,从字数简到繁进行测试。


代码规范制定链接

点击进入——代码规范


设计与实现过程

文字+代码的设计构思

1.输入需要读取的文件路径和结果的输出路径

2.读取文件,统计文件的字符数(对应输出第一行)、单词数(对应输出第二行)、有效行数(对应输出第三行)

3.将文件的所有字母转化为小写

4.通过Map集合,以键值对的方式去存储单词和出现的次数

5.创建一个BufferReader的缓冲流,将字符流对象传进去,提高读取的效率

6.创建一个spilt数组,用来分割字符串,通过调用map的key值获取value,进行单词统计

7.利用TreeMap实现Comparator接口,对Map集合进行排序


要求总结

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

  • 只需要统计Ascii码,汉字不需考虑
  • 空格,水平制表符,换行符,均字符

统计文件的单词总数(对应输出第二行)

单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写

  • 英文字母: A-Z,a-z
  • 字母数字符号:A-Z, a-z,0-9
  • 分割符:空格,非字母数字符号
  • :file123是一个单词, 123file不是一个单词。file,File和FILE是同一个单词

统计文件的有效行数(对应输出第三行)

任何包含非空白字符的行,都需要统计

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

  • 频率相同的单词,优先输出字典序靠前的单词
  • 输出的单词统一为小写格式

代码和注释

统计文件的字符数、英文单词数、行数。

            int charnum= 0 ;
	    int wordsnum= 0;
	    int linenum = 0;
	    InputStreamReader isr = new InputStreamReader(new FileInputStream(docin));
	    BufferedReader br = new BufferedReader(isr);
	    while( br.read()!= -1){
	    String s = br.readLine();
	    charnum += s.length();
        //在统计单词时,用split根据" "对每一行的单词进行统计
	    wordsnum += s.split(" ").length;
	    linenum ++;
	    }
	    isr.close();
        //写入文件
	    out.write("characters: " + charnum);
 	    out.newLine();
 	    out.write("words: " + wordsnum);
 	    out.newLine();
	    out.write("lines: " + linenum);
	    out.newLine();

按照单词的出现次数从高到低排序

                List<Map.Entry<String, Integer>> list = new LinkedList<Map.Entry<String, Integer>>();
		list.addAll(map.entrySet());
		Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
                        // 从高往低排序
			public int compare(Map.Entry obj1, Map.Entry obj2) {
				if (Integer.parseInt(obj1.getValue().toString()) < Integer
					.parseInt(obj2.getValue().toString()))
					return 1;
				if (Integer.parseInt(obj1.getValue().toString()) == Integer
					.parseInt(obj2.getValue().toString()))
					return 0;
				else
					return -1;
			}
		});

将出现频率最高的前十个单词写入文件

                int i = 0;
                //如果单词数大于等于10,就输出前10个单词
		if(wordsnum>=10) {
			for (Iterator<Map.Entry<String, Integer>> ite = list.iterator(); i < 10; i++)             {
				Map.Entry<String, Integer> maps = ite.next();
				out.write(maps.getKey() + ": " + maps.getValue());
				out.newLine();
				//System.out.println(maps.getKey() + "\t" + maps.getValue());
			}
		}
                //如果单词数小于10,有几个单词就输出几个单词
		else {
			    Iterator<Map.Entry<String, Integer>> ite = list.iterator();
			    if(ite.hasNext()) {
				Map.Entry<String, Integer> maps = ite.next();
				out.write(maps.getKey() + ": " + maps.getValue());
				out.newLine();
				//System.out.println(maps.getKey() + "\t" + maps.getValue());
			}		
		}

把文件中所有单词转化为小写

        public static void toLower(String file) throws Exception{
		Reader myReader = new FileReader(file);
		Reader myBufferedReader = new BufferedReader(myReader);
		CharArrayWriter  tempStream = new CharArrayWriter();
		int i = -1;
		do {
			tempStream.write(i);
			i = myBufferedReader.read();
			if(i >= 65 && i <= 90){
				i += 32;
			}
		}while(i != -1);
		myBufferedReader.close();
		Writer myWriter = new FileWriter(file);
		tempStream.writeTo(myWriter);
		tempStream.flush();
		tempStream.close();
		myWriter.close();
		}

统计单词词频

        public Map<String, Integer> wordCount(String fileName) throws IOException {
		// 打开文件
		File file = new File(fileName);
		FileInputStream fis = null;		
		fis = new FileInputStream(file);
		// 英文单词以空格为分隔符,将单词分隔,并将所有大写字母转换为小写
		BufferedReader bufr = new BufferedReader(new InputStreamReader(fis));
		String s = null;
		
			while ((s = bufr.readLine()) != null) {
				// 移除字符串的前导空白和后尾部空白
				s = s.trim();
				// 正则表达式:以非字母或者是数字为分隔符,进行分割
				String[] str = s.split("(\\s+\\W+)|[\\s+\\W+]");
				for (int i = 0; i < str.length; i++) {
					// 如果HashMap中已有该值,将值加1
					if (wordCount.containsKey(str[i])) {
						wordCount.put(str[i], wordCount.get(str[i]) + 1);
					} else {
						// 默认初始化该单词的出现次数为1
						wordCount.put(str[i], 1);
					}
				}
			}
		
		// 移除HashMap中的""空字符串
		wordCount.remove("");
		return wordCount;
	}

性能改进

  初始我将统计字符数、单词数与行数分开统计,代码如下:

            //统计字符数和单词数
	    FileReader fr = new FileReader(file);   
	    BufferedReader bfr = new BufferedReader(fr);
	 		char ch;
	 		char fch='A';
	 		//字符数
	 		int countc = 0;
	 		//英文单词数
	 		int countw = 0;
	 		//按字符读取文本内容
	 		while((ch = (char) bfr.read()) != (char)-1)
	 		{
	 			//统计文本中字符数
	 			if(ch != '\n' && ch != '\r') 
	 				//累计字符数
	 				countc++;
	 			if(!(ch>='a'&&ch<='z')&&!(ch>='A'&&ch<='Z')&&((fch>='a'&&fch<='z')||(fch>='A'&&fch<='Z')))
	 			{
	 				//累计单词数
	 				countw++;
	 			}
	 			fch=ch;
	 		} 		
	 		out.write("characters:  "+countc);
	 		out.newLine();
	 		out.write("words: "+countw);
	 		out.newLine();
	 		//System.out.println("characters:  "+countc);
	 		//System.out.println("words: "+countw);
		
	 		//统计文件的行数
	 		FileReader fr1 = new FileReader(file);
	 		LineNumberReader lnr = new LineNumberReader(fr1);
	 		int linenumber = 0;
	 		while (lnr.readLine() != null){
        	linenumber++;
	 		}
	 		
	 	out.write("lines:  " + linenumber);
	 	out.newLine();
	 	//System.out.println("Total number of lines : " + linenumber);
	 	lnr.close();

  虽然能成功实现功能,但是这样的代码十分冗长且性能低,在查阅了相关的代码后 ,我用了如下代码代替:

            int charnum= 0 ;
	    int wordsnum= 0;
	    int linenum = 0;
	    InputStreamReader isr = new InputStreamReader(new FileInputStream(docin));
	    BufferedReader br = new BufferedReader(isr);
	    while( br.read()!= -1) {
	    String s = br.readLine();
	    charnum += s.length();
	    wordsnum += s.split(" ").length;
	    linenum ++;
	    }
	    isr.close();
	    out.write("characters: " + charnum);
 		out.newLine();
 		out.write("words: " + wordsnum);
 		out.newLine();
	    out.write("lines: " + linenum);
	    out.newLine();

  初始我没有考虑到单词小于10的情况,会产生越界错误,经过修改后将单词分为10个及以上和十个以下两种情况讨论。

                int i = 0;
		if(wordsnum>=10) {
			for (Iterator<Map.Entry<String, Integer>> ite = list.iterator(); i < 10; i++) 			  {
				Map.Entry<String, Integer> maps = ite.next();
				out.write(maps.getKey() + ": " + maps.getValue());
				out.newLine();
				//System.out.println(maps.getKey() + "\t" + maps.getValue());
			}
		}
		else {
			    Iterator<Map.Entry<String, Integer>> ite = list.iterator();
			    if(ite.hasNext()) {
				Map.Entry<String, Integer> maps = ite.next();
				out.write(maps.getKey() + ": " + maps.getValue());
				out.newLine();
				//System.out.println(maps.getKey() + "\t" + maps.getValue());
			}		
		}

  在进行了以上优化后,再用JProfiler来测试性能,得到如下结果:

img


单元测试

  单元测试我选择的是一篇简单的英语作文,其中包含大小写、按字典排序等内容,具有常规性和代表性。

单元测试我选择的是一篇简单的英语作文,其中包含大小写、按字典排序等内容,具有常规性和代表性。

img

词频统计结果:

img

单元测试结果:

img

img

总结:

  由单元测试的详细内容可知,覆盖率已为100%。有一处不完全执行,功能将是大写转化为小写,因为所提供的词不全为大写,所以不完全执行为正常现象。由此可得可以暂时不用优化覆盖率。


异常处理说明

  本次程序的输入输出比较简单,所以利用的Exception有IOException和FileNotFoundException两种。即如果文件没有找到,或命令行参数输入参错误,就会抛出异常。


心路历程与收获

1.对大篇幅复杂工作的理解

  这两次作业的篇幅都很长而且很详细,因为是第一次接触这种布置形式,感觉很难下手。后来我总结出了阅读流程:从checklist入手,先大致知道自己该干什么,然后按照checklist的顺序慢慢扩充具体细节。还有很重要的一点是要分好part,不能心急,虽然有些环节可以同时进行,但是会造成作业效率的降低,所以以我个人角度还是应该一步一步来。

2.自学能力的提升

  大学很多东西都是靠自学,自学能力的提升主要还是要靠实践,纸上谈兵是没有用的。本次的git、github学习,我学习方式是先理解每个功能的作用再跟着教程实践一遍。感觉只要动手操作了,就学得挺快的。

3.编码能力的提升

  这里的编码能力也包括debug能力。本次的统计词频我用了map,详细了解后才发现之前对map的使用都是皮毛,所以在编码过程中遇到了很多错误,通过无数次的debug才能达到无误。还有一点是因为细节太多,会常常忘了某项功能的实现,所以本次作业是在完成大致框架的情况下,慢慢添加功能进去。

推荐阅读