第三次作业-结对编程

Github项目地址:https://github.com/WHYNOTEN/WordCount.git

合作同学作业地址:https://www.cnblogs.com/Mchandu/p/10657935.html

 一.PSP表格

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

890  1435

· Estimate

· 估计这个任务需要多少时间

890  1435

Development

开发

770   1270

· Analysis

· 需求分析 (包括学习新技术)

40   50

· Design Spec

· 生成设计文档

30   35

· Design Review

· 设计复审 (和同事审核设计文档)

50   60

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

20   15

· Design

· 具体设计

30   50

· Coding

· 具体编码

420   900

· Code Review

· 代码复审

60   40

· Test

· 测试(自我测试,修改代码,提交修改)

120   120

Reporting

报告

120   165

· Test Report

· 测试报告

 60  80

· Size Measurement

· 计算工作量

30   60

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

30   25
 

合计

900   1435

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 1.需求分析(由两人共同商议)

  基础功能

    1.统计文件字符数(包括字母/数字/特殊符号/空白符)

    2.统计单词总数(单词必须四个英文字母开头,后面可以为数字,不区分大小写)

    3.统计文件中有效行数

    4.统计文件中单词单词总数,按频率排序,对于相同频率单词按字典顺序排列

    5.按照字典顺序输出到文件中

    6.调用程序时,通过命令行传入文件读取和写入路径

  新功能:

    1.输出前n个频率最高的单词

      2.指定一个词组长度,输出词组及其出现频率

      3.多参数混合使用

实现所有需求思路:

  1. 需求主要是对于文本中内容的提取,由于之前学习过爬虫,自然想到了利用正则表达式对内容提取,但是由于C#和Python还是存在一些区别同时对于正则的支持不太一样,所以需要学习一下C#中对正则表达式的使用。
  2. 对于行数提取则可以通过读取文件(按行提取内容)时,判断提取内容是否为null来解决。
  3. 统计频率可以通过字典这种Key,Value这种存储方式来进行统计,排序则通过字典排序实现。
  4. 对于命令行传入参数可以通过Main(string[] args)中对args进行解析来实现。
  5. 新功能输出前n个频率最高可以直接输出字典内容即可。
  6. 对于第二个新功能最初不是很理解,最后询问助教老师后确定同样可以利用字典统计词组并输出
  7. 第三个功能主要是对于Main方法args参数的解析,可以利用字典保存参数及其内容

最终确定主要写两个.cs文件,其中一个对功能实现,另外一个进行功能类调用,这样测试时即只需要对功能类函数进行测试即可,基本功能由笔者实现,对于新功能有合作者实现,最终整合在功能中:

Function.cs:主要实现对文本内容的提取筛选实现需求功能;

Program.cs:对命令行参数进行分析(GetMand()),调用Function.cs对文本内容分析,实现功能(Main()).

 算法关键在于正则表达式的书写以及将内容写入字典整合:

 

文件读取代码:

//读取文件中的字符数目并保存文件内容
        public void GetChar()
        {
            //打开文件
            FileInfo file = new FileInfo(this.path);
            //定义读取文件对象
            StreamReader sw = file.OpenText();
            //按行进行读取,不为空行记录行数并保存内容,返回null打断循环
            while (true)
            {
                //对文件读取内容进行判断,如果不为空用变量接收,行数加一
                string temp = sw.ReadLine();
                if (temp != null)
                {
                    account++;
                    this.content += temp;
                }
                //为空则停止
                else
                {
                    break;
                }

            }
        }
View Code

正则匹配函数:

public void ExtractChar()
        {
            //利用正则进行匹配,以字母开头,可以数字结尾
            MatchCollection rel = Regex.Matches(this.content, @"([a-zA-Z]{4}\w*)");
            for (int i = 0; i < rel.Count; i++)
            {
                //匹配到的单词进入列表
                this.result.Add(Convert.ToString(rel[i]));
            }
        }
View Code

字典写入代码:

//利用字典统计单词出现次数
        public void Statistical()
        {
            //建立临时字典保存单词以及出现次数
            Dictionary<string, int> words = new Dictionary<string, int>();
            for (int i = 0; i < this.result.Count; i++)
            {
                if (words.ContainsKey(this.result[i]))
                {
                    words[this.result[i]]++;
                }
                else
                {
                    words[this.result[i]] = 1;
                }
            }
            //对字典内容排序,并赋值给类变量
            this.words_sort = words.OrderByDescending(p => p.Value).ToDictionary(p => p.Key, o => o.Value);
            //清空临时字典内容
            words.Clear();

        }
View Code

 

三.代码复审

  代码规范:详情请查看链接

  各自完成代码交换代码,对代码内容进行查看,基本均按照代码规范进行编码,对于不懂部分进行了增添注释,使代码得到可读性提高。同时对命名部分进行了详细检查,修改了部分变量名。

 

四.性能改进

从性能分析图看出Program.Main()与Function.ToFile()分配最多,但由于这两个函数都是调用了其余函数,所以减去其余调用函数分配,得出Function.ToFile()分配最多,为786,其次是Function.ExtractChar()

 //将内容写入文件中
        public void ToFile(string path)
        {
            //运行先行函数,将需要的文件内容保存至变量中
            this.GetChar();
            this.ExtractChar();
            this.ToLow();
            this.Statistical();
            //获取当前文件路径
            string filepath = Directory.GetCurrentDirectory();
            //定义文件输出路径
            filepath += path;
            FileInfo file = new FileInfo(@filepath);
            StreamWriter sw = file.AppendText();
            sw.WriteLine("characters: {0}", this.CharNum());
            sw.WriteLine("words: {0}", this.WordsNum());
            sw.WriteLine("lines: {0}", this.account);
            //遍历字典将内容写入文件
            foreach(KeyValuePair<string,int> kvp in this.words_sort)
            {
                sw.WriteLine("{0,-10}:{1,-3}", kvp.Key, kvp.Value);
            }
            //关闭文件
            sw.Close();
            Console.WriteLine("结果文件保存于:{0}", filepath);
        }
View Code

分析代码发现在ToFile()函数中进行了当前文件路径读取,读取文件,并多次写入文件(特别是遍历字典同时进行写入文件),由此可见循环写入文件是导致性能变差的原因。改进方法是将需要写入的内容保存至一个临时字符串中,遍历字典后再对整个字符串进行写入文件操作。ExtractChar()函数使用了正则进行匹配,由于对Regex类具体实现不清楚,暂时无法进行性能优化。

 

 五.单元测试

     由于项目中函数依赖关系,故只需要对部分函数进行单元测试即可,即对CharNum()和WordsNum()两个函数查看返回结果是否正确即可,若返回字符数与单词数均正确,则单元测试通过。但是有一个缺陷那就是对于命令行输入内容的测试,没有找到方法对于.exe运行时接收的外部参数进行测试判断.同时部分函数是将内容写入文件中,没有找到合适的途径进行测试。另外由于VS2017社区版本无自动输出代码覆盖率功能,无法判断代码覆盖率是否符合要求。下面是部分测试代码:

 

 

六.异常处理

异常处理主要针对命令行传入参数的判定:

1.文件传入路径

//对得到内容进行处理 - i参数确定文件是否存在
            try
            {
                StreamReader sw = new StreamReader(command["-i"]);
            }
            catch
            {
                Console.WriteLine("需处理的文件不存在!请检查路径重新运行!");
            }

2.-m 与-n 参数内容是否为整数

//对-m -n 参数内容进行强制转换看是否为整数
if (command.ContainsKey("-n") && command.ContainsKey("-m"))
            {
                try
                {
                    int nums = Convert.ToInt32(command["-n"]);
                    int length = Convert.ToInt32(command["-m"]);
                }
                catch
                {
                    Console.WriteLine("参数输入有误,请重新运行!");
                }
            }
            else if (command.ContainsKey("-m") && !command.ContainsKey("-n"))
            {
                try
                {
                    int length = Convert.ToInt32(command["-m"]);
                }
                catch
                {
                    Console.WriteLine("参数输入有误,请重新运行!");
                }

            }
            else if (!command.ContainsKey("-m") && command.ContainsKey("-n"))
            {
                try
                {
                    int length = Convert.ToInt32(command["-n"]);
                }
                catch
                {
                    Console.WriteLine("参数输入有误,请重新运行!");
                }
            }
            else { }

 

七.结对过程

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

 

八.总结

  这是第一次与人进行真正意义上的结对编程,最大的感觉就是比单独一个人编程轻松许多,同时更容易发现问题所在,在代码注释方面也明显比单独编程时多了许多,代码可读性更高,变量命名更加规范。同时也有一些小问题,两个人的想法会有不同,对于同一个问题的理解有一些差别,而这个意见统一过程花费的时间显然更多。但总的来说,这次的结对编程收获颇丰。

posted @ 2019-04-05 09:55  派生C  阅读(233)  评论(3编辑  收藏  举报