《构建之法》——第四次个人作业

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时,不会看见我们的代码。

三、代码复审

    代码复审过程是一个相互学习的过程,这次复审了项目的每个模块,对代码的写法进行优化,这里只举了一个比较严重的问题。

    索引超界问题

    在实现输出指定长度的词组功能时,发生了“偏移量和长度超出数组界限”的问题。
在这里插入图片描述
    在查看ArrayList相关资料时,发现了Capacity与count的区别,我们所使用的的capacity是ArrayList可以包含的元素个数,而count是ArrayList实际包含的元素个数,这就导致了越界问题。

    将capacity换为count

在这里插入图片描述

四、模块接口性能改进

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

在这里插入图片描述

  • 对效能分析结果发现characSum函数占用较多CPU,修改前后效能分析对比如下
    在这里插入图片描述
    在这里插入图片描述

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

    内存溢出问题

    在进行小文本测试时,代码可以正常实现预定功能,并输出;当我们使用一个大小为28MB的txt文本时,经过进2分钟的运行,抛出内存溢出错误。
在这里插入图片描述
    在多方查找资料以及严密分析之后,发现了我们代码中存在的问题。在characSum函数中,对大量数据进行处理时,需要连续不断的实例化非常多个对象,并且它们一直会在缓存区,直到函数执行完毕才会被释放,这样就会导致内存一直被挤压,最后就会导致内存不够用,进而抛出内存溢出的错误。

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

在这里插入图片描述

五、单元测试

  • 测试规则

    测试三个基本功能。

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

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

    1)大小写同时出现。

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

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

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

在这里插入图片描述

六、模块异常处理

  • 命令行输入异常

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

  • 内存溢出异常

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

七、结对过程

这是结对过程,共同完成了代码的编写以及博客共同部分的编写。

结对照片:
在这里插入图片描述

# 八、附加功能

用户交互界面绘制

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

在这里插入图片描述

  • 直接输入单词

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

  • 导出统计结果

在这里插入图片描述

总结

    结对感受:第一次进行结对编程,只能说感受良多,同时也用亲身的体验回答了自己对第一篇个人博客里面提出的疑问在这里插入图片描述
    现在我的回答是两个人坐在一起,确实会有人力资源浪费的情况,也会存在着偷闲的借口。再说效率问题,对于这次作业,如果一个人会比两个人快吗,我觉得会快,因为一个人写的话效率会高很多,两个人的话,有适应过程,有协商的过程,这个过程会存在什么,偷闲的借口。那么结对编程的优势体现在哪里呢?那就是互相学习,从对方身上看到优点,见贤思齐焉,见不贤而内自省也,还有就是两个人看一个东西,实现的方式会更多一些,思路更多一些。1+1等于多少?1+1等于1,两个人融为一体,步调一致,才能够完美发挥结对编程的目的。

队友辛苦了。

posted @ 2019-10-13 17:07  恭喜發財  阅读(185)  评论(1编辑  收藏  举报