作业要求地址 | https://www.cnblogs.com/harry240/p/11524113.html |
GitHub项目地址 | https://github.com/Untrara/WordCount |
合作同学地址 | https://www.cnblogs.com/cyh0813/p/11645041.html |
合作同学学号 | 201731031124 |
一、PSP表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
40 |
· Estimate |
· 估计这个任务需要多少时间 |
30 |
40 |
Development |
开发 |
1300 |
1250 |
· Analysis |
· 需求分析 (包括学习新技术) |
90 |
80 |
· Design Spec |
· 生成设计文档 |
60 |
50 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
60 |
70 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
20 |
· Design |
· 具体设计 |
300 |
280 |
· Coding |
· 具体编码 |
350 |
300 |
· Code Review |
· 代码复审 |
120 |
130 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
300 |
320 |
Reporting |
报告 |
120 |
125 |
· Test Report |
· 测试报告 |
40 |
45 |
· Size Measurement |
· 计算工作量 |
20 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
60 |
60 |
合计 |
1450 |
1415 |
二、计算模块的设计与实现过程
1.分析题目要求后,有三个难点需要解决
1)命令行读入参数
2)提取符合规则的单词
3)统计单词出现次数,并需要按照字典序进行排列
分别采用args[n],正则,以及Dictionary对象解决上述难点,将其余功能分类进行编写
如上图所示,共设计了7个类,具体功能及对应类如下图所示:
2.关键函数
GetTen.cs:统计各个单词出现的次数,并按照数量的高低以及字典序进行排列,返回一个Dictionary的对象。
WordString.cs:利用正则表达式,帅选出符合规则的,正确的单词
3.如何体现“Design by Contract”、“Information Hiding”、 “Interface Design”、 “Loose Coupling”等原则?
Design By Contract(契约式设计):
按照某种规定对一些数据等作出约定,如果超出约定,程序将不再运行。一个契约设计,就是约束了某个方法调用的要求、以及返回的承诺。那么,设计在正确的输入下,能够得到正确的输出,否则程序将报错。
Information Hiding(信息隐藏)
在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。
Interface Design(接口设计):
对接口的名字,功能,接口与接口间的继承关系进行设计;好的接口设计可以增强代码可读性,易用性,可更改性。
用规范的命名来设计接口的名字,对接口与接口之间的关系进行具体的设计,避免了接口的混乱。
Loose Coupling (松耦合)
软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
通过增加接口,根据方法功能增加分类的方式实现松耦合
三、部分代码实现
1.统计字符串中所有字符总数
1 public class CountWord 2 { 3 /// <summary> 4 /// 统计字符串中所有字符总数 5 /// </summary> 6 /// <param name="text">要统计字符数的字符串</param> 7 /// <returns>字符总个数</returns> 8 public int charactersNum(string text) 9 { 10 int ch = 0; 11 //\S——匹配任何非空白字符(除了空格、换行、制表符等的任何字符) 12 //|——匹配二选一 13 ch = Regex.Matches(text, @"[\S| ]").Count; 14 return ch; 15 } 16 }
2.统计单词出现次数并按照字典序进行排序
1 public class GetTen 2 { 3 /// <summary> 4 /// 计算频率最高的单词 5 /// </summary> 6 /// <param name="text"></param> 7 /// <returns></returns> 8 public Dictionary<string, int> getWordNum(List<string> wordString) 9 { 10 Dictionary<string, int> wordNum = new Dictionary<string, int>(); 11 12 foreach (string str in wordString) 13 { 14 15 if (wordNum.ContainsKey(str)) 16 { 17 wordNum[str]++; 18 } 19 else 20 { 21 wordNum[str] = 1; 22 23 } 24 } 25 return wordNum.OrderByDescending(r => r.Value).ToDictionary(r => r.Key, r => r.Value); 26 } 27 }
3.词组 -m统计函数
1 public class Cizu 2 { 3 public string numOfCiZu(List<string>wordString, int m) 4 { 5 6 List<String> ans = new List<string>(); 7 Dictionary<string, int> letter = new Dictionary<string, int>(); 8 string ret = null; 9 int len = wordString.Count(); 10 for (int i = 0; i <= len - m; i++) 11 { 12 string tem = null; 13 for (int j = i; j < i + m; j++) 14 { 15 //if (j >= len-m) 16 // break; 17 tem += wordString[j] + " "; 18 } 19 if (tem != null) 20 ans.Add(tem); 21 } 22 foreach (string str in ans) 23 { 24 if (letter.ContainsKey(str)) 25 { 26 letter[str]++; 27 } 28 else 29 { 30 letter[str] = 1; 31 } 32 } 33 foreach (KeyValuePair<string, int> kvp in letter) 34 { 35 ret += kvp.Key + ": " + kvp.Value.ToString() + "\r\n"; 36 } 37 return ret; 38 } 39 }
4.写入文件
1 class WriteFile 2 { 3 /// <summary> 4 /// 将一个字符串写入指定路径的文件中 5 /// </summary> 6 /// <param name="path">指定路径的文件</param> 7 /// <param name="str">要写入文件的字符串</param> 8 public void fileWrite(string path, string str) 9 { 10 FileStream fs = new FileStream(path, FileMode.Create); 11 StreamWriter sw = new StreamWriter(fs); 12 sw.Write(str); 13 //写入信息完毕 14 sw.Close(); 15 fs.Close(); 16 } 17 }
四、代码复审
查看队友的代码时,对他写的读入“命令行输入参数”的代码产生了疑惑,因为如果操作时,没有给出参数,而直接回车的话,会导致程序出错,而且不知道错在哪里,于是我提议利用try catch对这种异常进行处理,这样即使没有输入参数,程序会立马弹出错误信息,提示这个值不能为null,提高了程序的安全性。
我在编写统计单词个数的代码时,再次调用队友写的写入文件方法,这样会导致我的输出覆盖掉队友输出的内容,为了避免多次打开关闭文件和覆盖内容,经过代码复审,决定将我和队友的输出内容合并为一个字符串,再写入文件中。
利用正则表达式筛选出的列表,里面还包含了空格,逗号,句点;在与队友交流后,决定不更改正则表达式,但是在统计单词数量而产生的字典序列表中,将多余的符号删除。
为了减少主函数的负担,再次进行代码重构,将正则表达以及写入文件方法单独提出来,新建了两个类。
在我编写额外功能,统计词组时,结果出现丢弃符合规则的词组情况出现,自己编写时没发现,被队友发现并进行了纠正,完成功能。
其它还有诸如代码变量命名格式,参数传递等,经过代码复审后,得到了统一。
代码规范
C#代码规范:https://wenku.baidu.com/view/b5be911b6bd97f192279e9bd.html
五、单元测试
对于我们的函数我们分别测试了数字符个数与数单词个数的两个函数。通过Assert.Arequal断言函数进行测试,手动提前输入好一段有效的英文,用Assert.Arequal函数提前设定好结果,测试发现结果无误,代码通过
测试通过
六、异常处理
命令行程序的异常处理:对输入的字符进行判断
窗口程序的异常处理:
七、性能分析及改进
在第一个版本完成后,进行的性能分析如下
函数时间图如下
可以看出主函数负担过重,于是我们进行重构,之后分为了五个类,但过程中又发现,使用了多次正则表达式的结果,并且为了方便之后第三版本的输出,最后额外再分出了两个类,总计7个类
改进后性能分析如下
八、附加功能
在所有指令正确的情况下,点击“执行指令”按钮后,可以看到右边输出框输出我们想要的内容
点击输出框下的“结果导出”,程序会提示“已成功导出”,文件内容如下
九、结对过程
结对之后,我和队友曹欢专门挑选了一个下午的时间坐在一起进行讨论需求,初期代码设计,具体功能实现,将要求分为了三个阶段,每个阶段上传github一次代码。分工进行方法的编写,完成后进行合并,代码审查,彼此向对方讲解传入,输出,功能,类型等信息,方便理解代码,遇到卡壳的地方,也与队友进行交流,阐述思想和错误点。两个人坐在一起工作效率很高,代码复审后,初期版本很快得以实现,之后对代码进行重构,划分多个类,再增添新功能,调试,修改,复审等过程循环,单元测试在上述过程中多次使用,之后进行性能分析,改进,最后形成最终版本。
十、总结
看到作业要求的时候,我感觉太多了,密密麻麻的需求点,如果是个人完成,我花费的时间肯定会很久,因此我觉得此次结对编程的效果,是1+1>2的。我和队友曹欢合作默契,互相能很快读懂对方的代码,出现问题时交流也很有效,往往对话就是,“我的想法是这样的。。。但是有一个问题。。。”“我明白你的意思了。。。我觉得可以这么做。。。”解决不了的就疯狂百度。期间对一些要求产生了分歧,经过讨论和协商后,统一了意见并开始更改代码。在这期间学到了很多,总的来说,是一次十分有意义的结对编程经历。