个人第四次作业——结对编程

GitHub项目地址 这里
合作同学作业地址 这里

一、PSP表格

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

二、计算模块接口的设计与实现过程

1.项目实现的基本思路

    1)首先我们需要读取指定的文件,并且将文本文件中的内容提出出来,进行操作。这里使用C#的IO流来进行文件操作。

    2)根据要求以及难易程度,我们首先解决文本的行数以及字符数的统计。这里使用正则表达式进行处理。

    3)对于单词的统计我们在完成行数和字符数统计之后,决定使用集合来处理单词,但是发现单词是能够处理了,但是得不到单词出现的次数,所以我们转而使用字典集(Dictionary)来进行处理,字典集为我们提供了很多方便的功能。

    先用ArrayList集合存储所有单词,包括重复的单词,都存进去,但是是按照要求存储单词,也就是说必须四个英文字母开头的单词我们才存储,这里就用到正则表达式来解决。

    将存储好的单词经过遍历放进字典集,这样我们就得到了符合要求的所有单词的一个字典集,并且也得到了它们出现的次数,然后在对它们进行排序,就可以得到最终符合要求的前10个单词了。

    4)基础功能实现后,又开始实现新添加的功能,命令行操作以及输出指定长度的词组。

    由于使用条件判断语句不能够很好的满足命令行操作的要求,我们经过查找资料,发现了可以使用一个第三方的工具包来帮助我们。这里@命令行解析,通过这个工具包,我们实现了命令行操作。

    对于指定长度的词组,实现方式和单词的存储大致相同。
    整体程序流程图如下图所示

2.部分代码以及代码规范

  • 通过正则表达式存储符合要求单词

      /*
       * 按要求存储可用单词
       */
      public ArrayList Splitwords(string text)
      {
          ArrayList al = new ArrayList();
          MatchCollection matchs = Regex.Matches(text, @"\b[a-zA-Z]{4,}\w*");
          foreach (Match match in matchs)
          {
              al.Add(match.Value);
          }
          return al;
      }
      public ArrayList Splitlenth(int lenth, string text)
      {
          string b = lenth.ToString();
          string pattern = "\\b\\w{"+b+"}\\s";
          ArrayList al = new ArrayList();
          MatchCollection matchs = Regex.Matches(text, pattern);
          foreach (Match match in matchs)
          {
              al.Add(match.Value);
          }
          return al;
      }
    
  • 统计每个单词出现的次数

      /*
       * 统计每个单词出现的次数
       */
      public Dictionary<string, int> countWords(ArrayList arrayList)
      {
          Dictionary<string, int> nary = new Dictionary<string, int>();
          foreach (string word in arrayList)
          {
              if (nary.ContainsKey(word))
              {
                  nary[word]++;
              }
              else
              {
                  nary.Add(word, 1);
              }
          }
          return nary;
      }
    
  • 按值排序

      /*
       * 按值排序 
       */
      public Dictionary<string, int> sort(Dictionary<string, int> nary)
      {
          var result = from pair in nary orderby pair.Value descending, pair.Key ascending select pair;
          Dictionary<string, int> bronary = new Dictionary<string, int>();
          foreach (KeyValuePair<string, int> pair in result)
          {
              bronary.Add(pair.Key, pair.Value);
          }
          return bronary;
      }
    
  • 指定输出词组长度

      /*
       * 指定词组长度 
       */
      public Dictionary<string,int> msort(ArrayList al,int size)
      {
          Dictionary<string, int> nary = new Dictionary<string, int>();
          ArrayList bl = new ArrayList();
          int i = 0;
          while(i<=al.Count-size)
          {
              string str = null;
              var result = al.GetRange(i, size);
              foreach (var n in result)
              {
                  str += n.ToString()+" ";
              }
              bl.Add(str);
              i++;
          }
          foreach (string word in bl)
          {
              if (nary.ContainsKey(word))
              {
                  nary[word]++;
              }
              else
              {
                  nary.Add(word, 1);
              }
          }
          return nary;
      }
    

    代码规范是程序员的一种编程习惯,良好的编程习惯,不仅能自然地产生几乎没有bug的代码,而且在代码交接时,也方便继任者的阅读.这是我们的代码规范

3.接口设计以及封装

    1)接口设计:将三个基本功能,分为三个类,降低耦合度,便于进行单元测试,以及代码的修改,这样就可以在修改其中一个功能的时候,不影响其他功能。但是也并没有把所有的功能都分离开,统计单词里面就有很多个功能,类多了会增加整个程序的繁琐程度,也会降低代码的可读性。

    2)功能封装:按照要求将三个基本功能进行封装,封装成一个ClassLibrary,并且让它生成dll文件,这样程序就可以直接引用封装好的功能,更能减少代码量,降低耦合度。

    在我们的Winform程序当中,我们引用了封装好的统计功能,减少了重复代码量。

    接口的封装让我们的功能的实现更加的隐蔽,用户在引用我们的dll时,不会看见我们的代码。

三、代码复审

    我在审查队友代码的时候,发现“统计单词”这一模块的排序实现有问题,不满足给定的要求。排序功能只实现了词频的排序,但是有相同词频的不同单词没有按照字典序排序。

最初版本运行截图:

    可以看到,在第一个版本这里,"child"和"little"的词频都是出现一次,但是按照字典序,"child"应该排在"little"前面输出。
改进后版本运行截图:

    改进后,"again"和"made"词频都是出现两次,实现了字典序输出相同词频的单词排序。

四、模块接口性能改进

  • 在完成所有要求之后,对代码进行效能分析如下。

  • 对效能分析结果发现characSum函数占用较多CPU,修改前后效能分析对比如下

  • 发现的问题以及解决措施

    内存溢出问题

    在进行小文本测试时,代码可以正常实现预定功能,并输出;当我们使用一个大小为28MB的txt文本时,经过进2分钟的运行,抛出内存溢出错误。

    在多方查找资料以及严密分析之后,发现了我们代码中存在的问题。在characSum函数中,对大量数据进行处理时,需要连续不断的实例化非常多个对象,并且它们一直会在缓存区,直到函数执行完毕才会被释放,这样就会导致内存一直被挤压,最后就会导致内存不够用,进而抛出内存溢出的错误。

    在了解了问题触发的机制后,在处理数据时,我们不再使用实例化对象的方式,而是通过统计字符串的整个长度来得到字符数的大小,并且经过实验,达到了预期目的。

五、单元测试

  • 测试规则

    测试三个基本功能。

    对于行数以及字符数测试,我们进行简单的数据构造,测试数据要能够靠人工数出来。

    对于单词统计测试,我们同样进行简单的数据构造。

    1)大小写同时出现。

    2)数据中要有file123和123file这类词。

    3)数据中心要有小于4个字符的词。

  • 测试截图如下(本次测试共进行10组测试,由于本机安装社区版VS2017,不能查看测试代码覆盖率)

六、模块异常处理

  • 命令行输入异常

    命令行输入不符合要求的话,会提示出错,需要重新输入命令行。

  • 内存溢出异常

    根据代码效能分析的结果,设置内存溢出异常处理。

七、结对过程

    在结对之后,选定了两方都有空的时间进行讨论,根据PSP表格预估时间,讨论出项目需求,代码设计,根据各自水平进行分工,完成代码先进性自省,然后交换代码进行复审,最后汇总生成.exe文件进行单元测试并不断提交进度,最后撰写博客。

    这是我们在一起进行代码测试分析

八、附加功能

用户交互界面绘制

  • 导入单词文本(-i命令行)

  • 直接输入单词

  • 自定义词组长度(-m)

  • 自定义输出高频单词数量(-n)

  • 导出统计结果

总结

1、 两人在合作过程中彼此交流,能更敏锐地发现代码中出现的漏洞,及时改正错误,提高工作效率;

2、 两人合作相较于多人团队合作而言,更能促进彼此的交流,因为两人合作过程中,有什么问题可以直接提出,并在两人商讨之后得到满意的结论,若参与者数量太多,反而不好调配;

3、 虽然我们两个人的水平有一定差距,但是我们彼此信任,共同努力,最终还是实现了一个比较理想的算法,这是我们共同的劳动结果;

4、能和同伴一起交流,一同解决问题是一件挺好的事,两个人一起写代码思路也会更开阔。我觉得如果当一个人没有思路了,或者实现一个比较复杂的逻辑部分代码的时候,可以采用结对编程的方式。

posted @ 2019-10-13 16:53  大魏御宇921  阅读(163)  评论(1编辑  收藏  举报