第三次作业结对编程
GitHub项目 | https://github.com/LipeishanDawn/WordCount |
博客地址 | https://www.cnblogs.com/230332663abcd/p/10626290.html |
作业链接 | https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/homework/2882 |
一.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 30分钟 | 35分钟 |
·Estimate | ·估计这个任务需要多少时间 | 720分钟 | 750分钟 |
Development | 开发 | 60分钟 | 50分钟 |
·Analysis | ·需求分析(包括学习新技术) | 60分钟 | 70分钟 |
·Design Spec | ·生成设计文档 | 30分钟 | 30分钟 |
·Design Review | ·设计复审(和同事审核设计文档) | 60分钟 | 55分钟 |
·CodingStandard |
·代码规范(为目前的开发制定合适的规范) | 30分钟 | 30分钟 |
·Design | ·具体设计 | 60分钟 | 70分钟 |
·Coding | ·具体编码 | 240分钟 | 300分钟 |
·Code Review | ·代码复审 | 60分钟 | 45分钟 |
·Test | ·测试(自我测试,修改代码,提交修改) | 60分钟 | 70分钟 |
Reporting | 报告 | 30分钟 | 30分钟 |
·Test Report | ·测试报告 | 30分钟 | 30分钟 |
·Size Measurement | ·计算工作量 | 30分钟 | 30分钟 |
·Postnortem&Process Improvement Plan | ·事后总结,并提出过程改进计划 | 30分钟 | 25分钟 |
合计 | 810分钟 | 870分钟 |
二.描述结对过程及照片
我和我的队员共同看代码的要求,并对题目进行分析,各自阐述对题目的看法以及做法,并进行任务分工以及制定代码规范。各自来写自己有把握的部分,对于两个人都不懂的地方,我们通过问同学和查百度的方法,一起研究共同编写困难的地方。根据两个人编写代码量,较少的同学编写测试代码。在对接时,先分别对照代码规范修改对方没有达到要求的地方,再进行整合,在整合时遇到的问题共同进行修正,保证运行通过。
学习图片:
三.解题思路描述
首先是我考虑的是功能的需求。首先是读文件,然后考虑怎么读,要求读什么。第一就是读取文件中的信息,对文件的操作。我通过上网查询能够在日常操作中,快速读取,写入较大的文件的操作,CSDN(https://blog.csdn.net/wiltan/article/details/81005180)中介绍了文件流(FileStream)。所以我先考虑使用文件流读取给定文件。第二题目要求只统计ASCII码值,所以建立循环数组将文件内容放入,依次来判断是否在ASCII码表内。接下来是判断单词,要求是以4个字母打头,所以长度要大于4。判断前4个是不是字母,由于题目要求把字母都处理成小写,所以只判断ASCII码中小写的那部分就可以了。接下来处理的是单词数,因为考虑到既要记录单词名,又要统计个数。所以我查阅了C#的书籍,使用字典。对比字典里是否已经有这个单词,如果有就+1,如果没有就个数为1。在统计单个单词的基础上,它约束了个数,我根据C语言学过的排序,想到了用排序判断长度,先排序好就可以容易的取前十个单词。
四.设计实现过程
实现基本功能部分结构图:
对单词数进行限定的结构图:
独到之处:1.使用新学的文件流读取文件。2.使用字典的方式存储字母,既能存储单词,也能存储位置。3.用排序的方式来选取单词的长短
单元测试:
单元测试:
/**声明变量1:文件路径**/ public String inputFile; /**声明变量2:读取单词数**/ public int wordNumber; /**默认构造方法**/ public SimpleTest(){ } /**带参构造方法**/ public SimpleTest(String inputFile, int wordNumber) { this.inputFile = inputFile; this.wordNumber = wordNumber; }
/检测参数是否正确初始化 if (inputFile != null && inputFile.Trim().Length > 0 && wordNumber > 0) { CharNum charNum = new CharNum(); WordNum wordNum = new WordNum(); FileLines fileLine = new FileLines(); string outPut = ""; outPut += "characters:" + charNum.getCharCount(inputFile.Trim()) + "\n"; outPut += "words:" + wordNum.getWordCount(inputFile.Trim()) + "\n"; outPut += "lines:" + fileLine.getFileLineCount(inputFile.Trim()) + "\n"; wordNum.getWordCount(inputFile.Trim(), 4); Dictionary<string, int> dict = wordNum.getLinq(); int i = wordNumber; foreach (KeyValuePair<string, int> kvp in dict) { if (i == 0) break; else { outPut += "<" + kvp.Key + ">:" + kvp.Value + "\n"; i--; } } Console.WriteLine(outPut); }
测试目标:检测英文单词抓取程序,得出文件内总字符数,单词数量,行数,并输出前10个长度在4字符以上的英文单词。入参准备:参数1,检测目标文件,‘c:\input.txt' 参数2,输出排名靠前的10个单词
预期输出结果:characters:0-1000 words:0-500 lines:0-100
五.代码约束及互审
代码规范:
1.类型(类,结构,委托,接口),字段,属性,方法,事件命名
优先使用英文,如果实在没有合适的英文编写,可以使用拼音。
2.不使用缩写
所有类型,字段,属性,方法,事件,尽量不使用缩写,包括大家熟知的缩写,例msg
3.不使用单个字母变量
不使用单个字母变量,像i,m,,n使用index等来替换,用于循环迭代的变量除外。
for(int i=0;i<count;i++)
4.用tab作缩进,并设置缩进大小为4
5.注释
类型,属性,事件,方法,方法参数,根据需要添加注释。
如果类型,属性,事件,方法,方法参数的名称已经自注释了,不需要添加注释;
否则需要添加注释。
6.类型,名称和源文件名称一致
当使用名为product时,其源文件名只能是product.cs
7.所有命名空间使用,类型名称使用pascal风格
8.在一个类中,代码需要一空行
9.避免使用大文件。如果一个文件里的代码超过300-400行,必须考虑将代码分到不同的类。
10.一个方法只完成一个任务,不要把多个任务组合到一个方法。
11.调用类型成员内部其他成员,需加this,调用需要加base
12.不要捕捉异常却什么也不做
13.类型成员排列顺序
类型成员的排列顺序自上而下依次为:
字段:私有字段、受保护字段
属性:私有属性、受保护属性、公有属性
事件:私有事件、受保护事件、公有事件
构造函数:参数数量最多的构造函数,参数数量中等的构造函数,参数数量最少的构造函数
方法:重载方法的排列顺序与构造函数相同,从参数数量最多往下至参数最少
14.返回bool类型的方法属性命名
如果方法返回的类型是bool类型,则其前缀为Is,例如:IsHidden。
如果某个属性的类型为bool类型,则其前缀为Can,例如:CanHidden。
15.常见的字段,属性命名
代码互审情况:
我审查的部分是-m处理的内容和-i文件输出部分以及单词统计部分。情况:代码写的比较规范,逻辑较清晰,但存在少量书写不规范和考虑不到位的地方以及代码冗余的地方,合代码的时候比较节省时间。问题:1.没有设置主调入口,缺少类程序,导致在cmd窗口输出时,会出现“参数输入错误”的问题。2.题目要求单词个数可变,而代码实现的只能是单个单词,多个单词的无法实现。3.在统计单词方面,对单个单词统计多次,在整个程序运行中没有作用 3.在书写规范上,使用多个重复变量指代不明确。4.一行存在多个功能的语句。
六.程序的改进和性能分析
我在自己检查的时候发现以下的问题:1.在基本功能实现的时候,没有处理标点符号,没有考虑按照标点符号切词。只拆分空格的话,就会变成一个单词。解决思路:在进行每一行的拆分字符串的时候,从空格符扩展到常见的标点符号,这样拆字的准确率会提高。
性能分析图:
因为是把测试代码算进去,所以显示的占时间最多的是我的测试代码函数
七.代码说明
实现基本功能部分:
FileStream fsRead = new FileStream(FilePath, FileMode.Open);//流方式读取文件 int fsLen = (int)fsRead.Length;//判断文件长度 byte[] heByte = new byte[fsLen];//创建对应长度的byte数组 int r = fsRead.Read(heByte, 0, heByte.Length);//文件内容放到数组里 foreach(byte b in heByte) //循环数组判断 是不是在ASCII表内 { if (b >= 0 && b <= 126 ) { charCount++; // 是就+1 } }
读取文件后,建立动态数组,将文件中的内容放入数组,带入循环中判断是否是ASCII码,符合条件+1记录。
Dictionary<string, int> wordOccNum = new Dictionary<string, int>();//建立字典
因为考虑单词是否重复的问题,建立字典变量,它的存储类型正好满足(KEY value)的方式
int wordCount = 0; String line; StreamReader sr = new StreamReader(FilePath, Encoding.Default);//从文件中读取单词
foreach (string str in words) { char[] cc = str.ToCharArray(); if (cc.Length > 4)//要求以4个字开头,所以长度要大于4 { if (cc[0] >= 97 && cc[0] <= 122//判断前4个是不是字母 && cc[1] >= 97 && cc[1] <= 122 && cc[2] >= 97 && cc[2] <= 122 && cc[3] >= 97 && cc[3] <= 122)
由于字母都处理成了小写,所以只判断ASCII小写的部分(97-122)
{ if (wordOccNum.ContainsKey(str)) { wordOccNum[str]++;//有,把个数拿出来+1 没有就把单词放里面 个数为1 } else { wordOccNum.Add(str, 1); } wordCount++; } }
利用刚刚判断单词的逻辑,读取到一个单词,放到字典里,KEY是单词名,Value是个数。判断的意思是看字典里是否有这个单词,如果有,把个数拿出来+1,没就把单词放里面,个数为1.
实现增加新功能部分:先说我的基本思路 首先“统计”是需要每个单词的数量的个数。“最多”是需要清楚知道谁多谁少,所以我选择用排序的方式来判断(我觉得这是用的比较独到的地方)“10个”是不管文档里统计出来几个,只显示前10个。“词频”是单词出现的频率,也是要知道每个单词出现的次数。
以下是代码解释:
public Dictionary<string, int> getLinq() { //返回用 Dictionary<string, int> returnDict = new Dictionary<string, int>(); //按个数降序排列 Dictionary<string, int> tempOrder = wordOccNum.OrderByDescending(o => o.Value).ToDictionary(p => p.Key, o => o.Value); //临时转换用 Dictionary<string, int> temp = new Dictionary<string, int>(); int lastValue = 0; //循环降序排列的单词列表 foreach (KeyValuePair<string, int> kvp in tempOrder) { //只有在个数相同的时候才进行单词名称排序 //判断临时用字典表是不是空 , 空的就把当前单词和个数放进去 if (temp.Count == 0) { temp.Add(kvp.Key, kvp.Value); lastValue = kvp.Value; } else if (kvp.Value == lastValue)//不是空 判断是不是和上一个单词的个数相同 相同就放里 { temp.Add(kvp.Key, kvp.Value); } else if (kvp.Value != lastValue)//不是空 也不相同 把列表按照单词名称将序排列,结果放到返回用的字典里 { //把已经有的 放到返回里 temp.OrderByDescending(p => p.Key).ToList().ForEach(x => returnDict.Add(x.Key, x.Value)); temp.Clear();//清空 temp.Add(kvp.Key,kvp.Value);//把当前的放进去 lastValue = kvp.Value;//记录一下这次单词个数 用于后面判断 } } //循环跳出时临时字典里还有 就再处理一下 清空 防止数据忘记处理 if (temp.Count >0) { temp.OrderByDescending(p => p.Key).ToList().ForEach(x => returnDict.Add(x.Key, x.Value)); } return returnDict;//排序就结束了 } } }
主程序主要功能是程序顺序,从输入程序执行再到结果退出。输入是由主程序负责,Switch判断部分。程序执行可以定在主程序里,也可以抽象成类或者方法。
以下代码演示:
class Program { static void Main(string[] args) { CharNum charNum = new CharNum(); WordNum wordNum = new WordNum(); FileLines fileLine = new FileLines(); string filePath = "input.txt";//默认文件为 input.txt int wordLength = 0; int wordCount = 0; string outFile = ""; string outPut = ""; if (args.Length > 0 ) // 判断输入参数 { switch (args[0]) { case "-i": filePath = args[1]; break; case "-m": wordLength = int.Parse(args[1]); break; case "-n": wordCount = int.Parse(args[1]); break; case "-o": outFile = args[1]; break; }
wordNum.getWordCount(filePath, wordLength);//先调用方法把单词处理完成 Dictionary<string, int> dict = wordNum.getLinq();//再调用排序
FileStream fs = new FileStream(outFile, FileMode.Create); //获得字节数组 byte[] data = System.Text.Encoding.Default.GetBytes(outPut); //开始写入 fs.Write(data, 0, data.Length); //清空缓冲区、关闭流 fs.Flush(); fs.Close();
outPut +="characters:" + charNum.getCharCount(filePath) + "\n"; outPut += "words:" + wordNum.getWordCount(filePath) + "\n"; outPut += "lines:" + fileLine.getFileLineCount(filePath) + "\n";题目要求独立的三部分输出
八.收获及结对感受
首先这次的程序比上一次的要求更多,我认为首先拿到题目,入手点就是去分析它的功能,可以先画出或者总结出结构图。明白它的要求,可以在编译器中写出注释,理出解题思路,在注释下写代码,这样可以更有效的编译复杂度较高的代码。其次,就是上课听老师讲的代码规范问题,真正在和队友一起整理代码的时候发现,有明确注释,循环有缩进等这种规范性很高的代码可以很大程度上的提高整合代码的效率,在团队合作上,这项工作确实必不可少。这次编码是结对编程,我同意1+1>2的观点。经过两个同学的讨论和研究,可以让自己的思路更加的清晰,一起解决困难想出的办法也比较多。