结对编程
作业属性
作业课程 | 系统分析与设计 |
---|---|
作业要求 | 第四次个人作业 |
作业目标 | 体验结对编程 |
学号 | 201731062416 |
博客地址 | 博客园博客 |
结对编程仓库 | 仓库 |
结对伙伴学号 | 201731062415 |
结对伙伴博客 | 伙伴博客 |
结对编程初体验
编程规范的约定
规范主要参考博客
编程规范简单的来讲主要分为命名规范、布局规范和注释规范。
其中命名规范指出,Pascal命名方式对类、类型、枚举类型、属性事件进行命名。使用Camel命名法对参数、变量、字段进行命名。布局规范指出,代码缩进为4个空格(即一个tab),且每个花括号单独占用一行。在运算符两端均要加上空格。单行字符不超过120个字符。注释规范指出,对类要使用标准的类注册(在vs中在类名上方使用三个斜线会自动生成模板)。对于类中的属性也要使用标准的注册(同样可以使用vs快捷生成)。在代码比较关键的地方恰到好处的写上注释是很关键的。注释不是越多也好,恰到好处的注释非常的重要。
除了这些基础的规范外,我和我的小伙伴还约定我的变量和命名进行使用英文和易理解的英文缩写。
代码规范是增加系统可维护性的第一步,在结对编程中,对于帮助同伴理解自己的代码。
结对编程的方式
为了高效的结对编程,我们在结对编程之前进行了约定。在编程过程中不能然一个人一直霸占键盘,在编程之前就设定好切换角色的时间,这样两个人在项目中的参与感都非常地高。在结对编程地过程中,我们约定都不能被自己地私人事务分散注意力,要全身心地投入到项目中去,不管是“驾驶员”还是“领航员”都需要全身心地投入,避免一个人写代码,一个人在旁边玩耍地情况。当我们的意见遇到分歧地时候,我们都需要心平气和地阐述自己地方案地优缺点,避免意见分歧导致不愉快。
结对的PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
Estimate | 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 700 | 720 |
Analysis | 需求分析 (包括学习新技术) | 120 | 120 |
Design Spec | 生成设计文档 | 40 | 30 |
Design Review | 设计复审 (和同事审核设计文档) | 60 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 120 | 120 |
Coding | 具体编码 | 300 | 480 |
Code Review | 代码复审 | 60 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 60 | 120 |
Test Report | 测试报告 | 30 | 60 |
Size Measurement | 计算工作量 | 20 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 45 |
合计 | 1690 | 1955 |
系统设计和实现
系统设计
在系统地设计上,为了避免一个类的职责过重,让系统遵循开闭原则,我们对“职责链模式”进行了改良。在词频统计整个流程分解为了三个阶段。一个是系统的输入阶段,这个阶段主要进行单词文本数据的读取和预处理。第二个阶段是对信息统计阶段,这个阶段主要将文本中的数据进行统计。第三个阶段是结果输出阶段,这个阶段主要进行结果的整理(如排序)和输出。这三个阶段每个阶段的处理结果都保持在一个Context对象中,一个阶段处理完成后将结果放置在Context对象中传递给下一个阶段进行处理,每个阶段都有明确的目的。并且针对输入,和结果输出两个阶段需求容易变动的情况,我们抽取了公共类,方便以后拓展。在WordCount类中提供了使用该系统的接口。在实现命令行界面版本的时候,我们解析输入的参数,然后直接调用WordCount类调用词频统计模块。在实现图形界面程序的时候,也不需要改动WordCount类中的任何东西。
在设计中,每个模块都拥有明确的目标,内部运作的方式不被隐藏。各个模块的高内聚,低耦合。模块之间仅仅传递少量的数据。对外不暴露内部的实现细节,只提供使用的接口。
系统重要模块的流程梳理
- 程序的主干流程
代码实现
在整个系统中,统计单词、词组等是系统编码中实现难度较高的部分。经过查询资料,我们采用了正则表达式来实现这些功能。在编码的过程中C#类库中提供的字典也帮上了大忙。
统计单词的方法
/// <summary>
/// 统计每个单词出现的次数
/// </summary>
/// <param name="context">上下文</param>
public void countWords(Context context)
{
string[] words = Regex.Split(context.FileInfo, @"\W+");
context.BaseInfo.Add("words", 0);
foreach (string word in words)
{
if (word.Length >= 4)
{
context.BaseInfo["words"]++;
}
if(word != "")
{
if (context.WordCount.ContainsKey(word))
{
context.WordCount[word]++;
}
else
{
context.WordCount[word] = 1;
}
}
}
}
统计词组的方法
public void countWordGroup(Context context ,int m)
{
string[] words = Regex.Split(context.FileInfo, @"\W+");
for (int i = 0; i < words.Length - m; i++)
{
string wordGroup = null;
for (int j = i; j < i+m; j++)
{
wordGroup = wordGroup+words[j]+" ";
}
if (context.GroupCount.ContainsKey(wordGroup))
{
context.GroupCount[wordGroup]++;
}
else
{
context.GroupCount[wordGroup] = 1;
}
}
}
在实现统计这些信息的时候,我的队友起到了关键作用他对正则表达式的比较熟悉,使用正则表达式极大的降低了编码的难度。在使用c#的字典的过程中我们都不太熟悉,我们类比java中的map和查询相关资料,一起深入探讨很快就对这些类库的使用有了比较深入的了解。
异常处理
在文件读写的过程中,是出现异常的高发区。我们执行查询了相关方法的文档,了解其运行过程中可能抛出的异常,并进行了捕获处理,给用户提供明确的提示。
在参数解析中,也是很容易出现异常的。我们在这一部分首先进行对用户的输入进行了严格的校验,并且在参数解析的部分进行了严格的异常处理。
代码复审过程
代码复审我们主要针对以下几个方面进行:
- 是否遵循了约定的编码规范。
- 是否是按照设计进行的编码。
- 对代码参数是否进行了校验,对异常是否进行了处理。
- 代码是否有可以优化以提高性能。
- 代码是否是可测试的。
代码复审的工作其实在结对编程的过程中“领航员”一直都在做。但是为了提高代码的质量,我们在每天编码结束后都对系统进行了代码复审,在这个过程中,我们按照上面所说的五个标准进行逐行阅读代码,有修改意见时,经过大家讨论后再进行修改。
我们许多异常处理的问题都是在这个阶段发现的。比如我们在复审的过程中就发现,在解析参数的时候(m = int.Parse(args[i + 1]);
如果args[i+1]不为整数,就会出现异常),如果用户没有按照规定输入整数,那么系统将会出现异常并停止工作。我们讨论了解决方案后,在这个部分捕获了异常,并对用户进行了提示。
在复审过程中我们还调整了部分的注释,提高程序的可读性。
计算模块部分单元测试展示
统计字符模块的单元测试
- 编写单元测试代码
- 查看测试结果
统计单词模块的单元测试
- 编写测试代码
- 测试结果
统计词组模块的单元测试
- 编写测试代码
- 测试结果
计算模块部分的性能改进
效能分析
- 在进行效能分析之前首先要配置启动的命令行参数
因为我们这个项目是通过命令行启动的,所以需要配置命令行参数。
- 启动性能探查器,分析性能
通过查看分析报告,我们发现耗费较多资源的主要是统计单词,和统计词组的方法。
改进思路
在效能分析中,我们发现我们的程序在统计单词和词组的的时候非常的耗时。我认为可以在统计的时候,使用三个线程,分别来统计单词和词组、行数,字符。这样可以更好的利用CPU的计算性能,这三组方法之间没有资源的争夺,也不会出现线程安全问题,这样可以在统计超长的文本的时候缩短时间。
- 改进代码
- 使用超大文件进行测试
- 未改进的代码使用同样的文件和方法计算用时
通过对比,使用该优化思路,性能有比较少的提升。经过查询资料和咨询大佬,发现主要有两个方面的因素,一是系统在设计的时候就使用的是非线程安全的字典,系统中只使用了两个独立的字典。
命令行程序结果展示
图形界面程序设计与实现
实现方式
针对图形界面的输入和输出的方式不同,我们继承InputProcess
编写了TextBoxInputProcess
类,继承了ResultOutPutProcess
类编写了GUIOutPutProcess
类,这正好符合"开闭原则"。为了满足导入文件和手动两种输入方式。我们将导入文件的内容放置在手动的输入的文本框中,然后再从文本框中读取文件内容,进行统计。这样用户既可以使用导入文件输入,也可以手动再文本框中进行输入,同时还可以对导入的文件的内容在文本框中进行编辑后再进行统计。
得益于之前对单词统计接口的封装,再实现图形界面版本的时候,我们除了增加一些图形界面特有的类外,没有修改之前的任何代码。
图形界面程序结果展示
- 启动界面
2.点击导入单词文件选择文件,也可以直接在文本框中输入
2.输入相关参数。点击开始统计,显示结果
- 选择导出文件的位置
4.查看导出的文件
心得体会
这次结对编程是我从未体验过的全新感觉。在结对编程中,可以更好的群策群力写出设计更加合理的代码,并且两个人一些写代码避免了一个写代码的那种随意的感觉,这样有助于提高我们的编码规范的意识。在编码过程中,我和队友各取所长,相互学习。遇到困难的问题,就一起利用网络查询资料,一起攻克难关,在这次编程中,我和我的队友是1+1>2的。