《构建之法》--第四次做业--结对编程

这个做业属于哪一个课程 课程的连接
这个做业要求在哪里 做业要求的连接
GIT项目地址 WordCount
结对伙伴做业地址 linls
我的博客主页 Vchopin

咱们这一次的github项目是从做业指导中Fork下来到本身的项目中的,用的git地址,是个人项目地址。此次结对编程个人结对伙伴是linls,技术牛逼,python机器学习大佬一枚,如下简称为俊老板html

系统分析

原本还想进行需求需求了解的,结果仔细一看做业指导中,北航老师已经清清楚楚写的明明白白了,因此和俊老板就决定直接开始分析python

分析

第一步

  做业中须要在第一步完成wordCount的基本功能,也就是在命令行中统计一个txt文本中的所有字符数以及其余要求。
1.命令行参数。对于命令行参数的处理,C#能够直接从Main函数中的args中直接获取git

2.汉字的处理。要实现只处理ascii而不处理汉字,其实能够经过汉字的unicode对其进行过滤。可是这个时候俊老板提出,一段文本里面可能不仅是有汉字和英文,也可能有日语或者其余什么语言,因此光去除汉字不行,应该是保留全部的ascii去除不是ascii的文字。固然能够用正则表达式,也能够直接用.Net自带的Encoding类过滤。github

3.空格、制表符和换行符的处理。刚开始一看很懵...这咋知道\t是否是四个space啊。后来俊老板提醒,这些都是电脑判断,咱们只须要判断他这个字符是\t仍是space就能够了。是四个space那也不用当成\t面试

4.对于file123和123file的判断处理。其实这个要点我和俊老板发生了意见冲突,我认为经过C#的字符串方法string.StartWith()这个方法就能够进行判断求解的。可是俊老板坚持认为,使用正则表达式可以更加快速以及效果显著的完成统计。由于具体两种方法都是能够实现的,因此咱们都仍是各自保留了本身的想法,到编码的时候在具体选择。正则表达式

5.单词的统计。我和俊老板一看题,这个单词是按照分割符进行统计的,但是分隔符是包括了全部的非字母数字符号,咱们一致用正则表达式的硬编码匹配那就很难受。后来我想可不能够直接用while遍历所有的字符,将那些ascii码值大于等于97小于等于122的那些当作分隔符,出现就将其两边的字母做为前一个单词结尾和后一个单词开始。算法

6.统计频率最高出现的10个单词。俊老板在看到的时候认为,这个能够新增一个类,将单词的内容和频次做为这个类的属性。可是后来,我以为不行,要是统计的文章有个一万多个字,那就要构造一万多个对象,先不说内存花费,构造对象的时间都有够呛。咱们考虑了用数组,泛型等等。发现数组浪费的空间很大,泛型在执行效率方面比较低下。最后想到用Dictionary或者HashSet能够完美解决效率和空间的问题。编程

7.按照字典序输出。我想的是,在统计完成以后,若是发现有几个统计频率是同样的单词,就让他们在按照升序排一次序就行。可是我初步估算的话,这样的时间复杂度会比较大,若是按照冒泡排序来讲,就多是O(n^4)的时间复杂度...我和俊老板在这里对于具体怎么实现都还不清楚,咱们也仍是决定到时候具体编程的时候在具体解决。windows

第二步

  这个部分主要是将第一步中的统计字符数统计单词数统计最多的10个单词及其词频这三个功能进行剥离,造成dll,以便于在命令行、图形界面、网页程序和手机App上面使用。而且这个dll可以提供相应的api供其余辅助调用。首先要保证这三个功能的彻底正确性,因此对其进行单元测试保证可用性。
  那么这个问题我和俊老板都是决定经过从新写一个类,里面的public方法就是分别是上面三个功能的入口,其他的辅助函数都写成private防止暴露。最后利用VS工具生成动态连接库(DLL)就能够完成封装。当咱们在其余地方进行调用的时候,将该dll添加到引用,并引入命名空间,便可开始调用里面的方法完成计算。固然,第二步是须要咱们写单元测试保证该dll稳定,因此,最主要的仍是在单元测试中引入相关代码并完成测试。若是有时间,俊老板打算在移动应用或者ASP.Net上面试试。api

第三步

  这一步主要是在原有基础上的功能的拓展。对多参数处理词组统计自定义输出的实现。

  1. 对于多参数处理的实现。俊老板认为直接对获取到的每个参数进行处理,若是他是以-符号开头的,能够认为是一个标识符。而后获取到-后面的字母,在对其进行判断类型,同时,还得保证标识符的空格后面必须有合法的值。好比对于-i,后面就必须是一个存在的文件名,不然提示错误。
  2. 使用参数设定统计的词组长度。我认为这个是能够直接从处理事后的单词词组中进行选择,按照序号依次经过for循环输出,循环次数由输入的-m后面的参数决定。固然也须要完成对后面的数字的处理。
  3. 设定单词数量。和上面的思路基本同样,在第一步中是实现的前10个词频最高的单词,能够直接输出10个Console。可是如今经过变量运算来控制显示单词的个数,就须要有一个count单词来计数。当达到相应的数量以后就break

  其实最重要的我认为仍是最后一个多参数的混合使用,好比某个参数可能不出现,不出现就得有默认参数。而且参数之间的顺序也不固定,不能按照顺序对其进行取值。俊老板的想法是将全部输入参数组成一个字符串,最后经过断定特定字符是否在字符串中进行参数存在断定。

第四步

  这一步其实没有什么好说的,利用winform或者wpf直接拖控件按照对应的功能和所须要求完成界面设计,计算单词的算法仍是用第一步中基本核心算法功能,加上第四步中的加强功能改为GUI的方式完成设计。

第五步

  这一步是进行单元测试。是对前面全部的部分进行单元测试,包括核心算法功能,额外加强功能和GUI的附加功能。咱们认为对于核心算法功能和额外加强功能都比较好记性测试,但是对于图形化界面如何进行测试呢?查询资料后发现网络上面基本没用对GUI进行测试的...不是由于很难,由于没有必要...可是仍是发现有这样的工具,好比Nunit能够对winform和wpf这种C#写的代码进行图形化测试。俊老板查询的简单粗暴,直接用Rebot类自动测试。其实我以为均可以。最重要的仍是对于单词的计算的功能的测试。咱们决定对如下几个地方进行测试:

  1. 输出格式测试
  2. 字母、单词、行数统计的测试
  3. 前10个频次最高按照字典排序的单词测试
  4. 对于非ascii码的处理测试
  5. 多参数读入测试
  6. 读入文件输出文件非法文件名测试
  7. 词组长度测试
  8. 输出单词数量的测试
  9. 意外状况处理测试
  10. 输入错误的处理

    第六步

      效能分析咱们决定在代码写出来以后再利用vs的效能分析软件查看他的性能,对严重拖慢程序运行进度的进行修改和优化。

    代码规范

      咱们认为,要作就要作好。因此,咱们的代码规范都是按照互联网上通用规则进行编写:

    注释

    1> 若是处理某一个功能须要不少行代码实现,而且有不少逻辑结构块,相似此种代码应该在代码开始前添加注释,说明此块代码的处理思路及注意事项等
    2> 注释重新行增长,与代码开始处左对齐  
    3> 双斜线与注释之间以空格分开

    命名规则

    部分参考https://blog.csdn.net/tieshuxianrezhang/article/details/51960039

4> 类和接口命名
  l 类的名字要用名词;
  l 避免使用单词的缩写,除非它的缩写已经广为人知,如HTTP。
  l 接口的名字要以字母I开头。保证对接口的标准实现名字只相差一个“I”前缀,例如对IComponent接口的标准实现为Component;
  l 泛型类型参数的命名:命名要为T或者以T开头的描述性名字,例如:
    public class List
    public class MyClass
  l 对同一项目的不一样命名空间中的类,命名避免重复。避免引用时的冲突和混淆;
5> 方法命名
  l 第一个单词通常是动词;
  l 若是方法返回一个成员变量的值,方法名通常为Get+成员变量名,如若返回的值 是bool变量,通常以Is做为前缀。另外,若是必要,考虑用属性来替代方法;
  l 若是方法修改一个成员变量的值,方法名通常为:Set + 成员变量名。同上,考虑 用属性来替代方法。
6> 变量命名
  l 按照使用范围来分,咱们代码中的变量的基本上有如下几种类型,类的公有变量;类的私有变量(受保护同公有);方法的参数变量;方法内部使用的局部变量。    这些变量的命名规则基本相同,见标识符大小写对照表。区别以下:
    a) 类的公有变量按一般的方式命名,无特殊要求;
    b) 类的私有变量采用两种方式都可:采用加“m”前缀,例如mWorkerName;
    c) 方法的参数变量采用camalString,例如workerName;
  l 方法内部的局部变量采用camalString,例如workerName。
  l 不要用_或&做为第一个字母;
  l 尽可能要使用短并且具备意义的单词;
  l 单字符的变量名通常只用于生命期很是短暂的变量:i,j,k,m,n通常用于integer;c,d,e 通常用于characters;s用于string
  l 若是变量是集合,则变量名要用复数。例如表格的行数,命名应为:RowsCount;
  l 命名组件要采用匈牙利命名法,全部前缀均应遵循同一个组件名称缩写列表

代码编写

思路分析

  我和俊老板仔细研究题目后,认为咱们俩的水平仍是不太行,总体来写的话颇有难度,因而咱们决定按照题目要求一个点一个点来编写,逐个击破。因为做业指导中已经将具体代码优化步骤已经给出,因此,咱们在第一步的基本功能的实现方面就将所有方法糅杂在一个Main函数中。而后在第二步中根据题目要求和咱们代码具体实现进行拆分解耦。在第三步在根据第二步拆分的进行拓展。后面的步骤基本就按照做业指导走就好了。附上一块儿讨论编程的合做照片




合做照片

编码

第一步

  首先实现的是最最基本的利用正则表达式将所有的非字母数字的所有替换成为#,而后在用字符串的Split('#')方法将一个字符串拆分到数组中,就造成一个一个的单词。在根据单词前四个必须是字母完成对不符合的要求的筛选。代码以下

string regexStr = Regex.Replace(readLine, @"[^a-zA-Z0-9]+", "#");//过滤
string[] wordsArr1 = regexStr.Split('#');  
charactersCount += readLine.Length;//统计每行的字符数 最后只需再加上每行的字符数就是总字符数

foreach (string newWord in wordsArr1)
{
    if (newWord.Length != 0)
    {
        char[] temparr = newWord.ToCharArray();
        if ((newWord.Length >= 4) && (char.IsLetter(temparr[0]) && char.IsLetter(temparr[1]) && char.IsLetter(temparr[2]) && char.IsLetter(temparr[3])))
        {
            lists.Add(newWord.ToLower());
        }
    }
}

  代码流程图以下




流程图

  完成对单词的提取以后,接下来是对单词的频率统计和排序。在需求分析里面咱们讨论了如何是实现单词内容和频率的关联,考虑到最后输出的每一个单词都是不可能同样的(惟一性),可是频率有多是同样的,这个属性彻底符合字典的key-value模型,所以咱们决定是使用具备KeyValuePairDictionary类来对其绑定实现。循环迭代上面的lists中的单词,没有出如今字典中的,就直接按照频率为1加入到字典集合中,出如今字典中的,对该个keyvalue加一操做,这样就能够完成单词的统计。字典的统计单词频率代码以下:

Dictionary<string, int> wordsCount = new Dictionary<string, int>();

//单词出现频率统计
foreach (string li in lists)
{
    if (wordsCount.ContainsKey(li))
    {
        wordsCount[li] ++;
    }
    else
    {
        wordsCount.Add(li, 1);
    }

}

  统计频次完成以后,就须要对其进行排序,按照频次从大到小,若是频次相同,就要按照字典序对单词排序。这里其实涉及到两种排序,一开始俊老板是想将其挨个取出放在List中,排序以后在放回Dictionary里面。这是能够实现的,可是空间复杂度和时间复杂度都是至关的高。咱们后面继续查阅资料(参看博客http://www.javashuo.com/article/p-ekjdzdap-da.html)发现C#的Dictionary类是自带排序的,属于链式编程正好完美解决降序排一次在升序排一次。代码以下:

Dictionary<string, int> sortedWord = wordsCount.OrderByDescending(p => p.Value).ThenBy(p => p.Key).ToDictionary(p => p.Key, o => o.Value);
foreach (KeyValuePair<string, int> item in sortedWord)
{
    Console.WriteLine("word:{0} ; count:{1}",item.Key, item.Value);
}

  这样就基本完成第一步的代码编写了。那么是骡子是马,上图溜溜:
首先是咱们的测试文件图片




测试用例

  而后是代码操做运行截图




测试页面


  新增两个换行符以及hello字符。预测行数不会增长,字符数增长7个就是98个字符。



修改测试用例


  在此运行wordCount进行单词统计,获得:



修改后的运行结果


  与预期不一致,检测代码发现是最后的字符数是跟有效行数挂钩的,致使只有有效行数的 /n换行符被统计。



错误代码


  俊老板仔细考虑了一下,在计算有效行数的时候不须要空白行,可是在计算字符数的时候就须要空白行了,因此这里须要分开进行计算。修改代码为:



俊老板修改的代码

  不看却是没什么,仔细一看,这个代码问题大得很。ReadToEnd()的这个方法,直接就所有读完,以前一行一行的判断直接到了末尾。如今在用这个方法等于没有读到任何字符。若是必定要用这个方法,只能用两个StreamReader分别读取测试文件。最后咱们决定采起一个折中的办法,浪费空间,换取对从新读取文件的时间。新增一个lines表明所有行数,每读一次就自增1.最后就能够得出所有行数。试验效果:




俊老板修改的修改代码


  这样就所有完成基本功能验证,符合预期。

  第一个版本作出来,就准备开始上传Git了。仍是按照做业2的步骤上传Git,放到这里出现问题了...不可以将要上传的VS目录添加到暂存空间。




GIT Error

  屡次查阅资料(参考博客http://www.javashuo.com/article/p-ewxyoaoi-dp.html)以后发现VS目录中的隐藏文件夹.VS是没法读取上传的。经过输入git add --ignore-errors .就能够忽略不能读取的进行上传。
使用git commit上传




GIT Commit

  在使用git remote add origin https://github.com/vchopin/WordCount.git而后使用git push又出现错误了




GIT Push Error

  根据英文意思,我猜想是没有和远端仓库合并代码,因此接下来先执行git pull拉去仓库到本地合并。




GIT Pull

  完成合并以后,在继续git push推送到仓库中




GIT Push


  登陆github查看上传状况,已经成功上传



GIT页面

第二步

  第二步是对原有代码进行差分解耦。我和俊老板决定按照不一样功能分别用抽象实现顶部封装,便于往后的升级和代码规范。最最重要的仍是要将统计字符数统计单词数统计最多的10个单词及其词频这三个功能进行独立出来,我和俊老板想的是若是给每个功能都新增抽象类,那么类就会很庞大。因此最后采起维护基本功能、抽象核心计算功能。
  首先是对文件输入和输出的剥离,将读取字符功能和打印前十个单词的功能抽象为一个接口中的两个方法以下:

interface IDataIO
{
    /// <summary>
    /// 从文件中读取所有字符
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    string ReadFromFile(string path);

    /// <summary>
    /// 打印前maxline个排序后的单词。为0则所有打印
    /// </summary>
    /// <param name="sortedWord"></param>
    /// <param name="maxline"></param>
    void Print(Dictionary<string,int> sortedWord, int maxline=0);
}

  而后在实现这个接口

class DataIO:IDataIO
    {
        public static string ReadFromLittleFile(string path)
        {
            return File.ReadAllText(path, Encoding.ASCII);
        }
        public static string ReadFromLargeFile(string path)
        {
            return File.ReadAllText(path, Encoding.ASCII);
        }

        /// <summary>
        /// 将文件所有读成string类型进行传递
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public string ReadFromFile(string path)
        {
            return File.ReadAllText(path, Encoding.ASCII);
        }
        /// <summary>
        /// 打印前maxline个排序后的单词。为0则所有打印
        /// </summary>
        /// <param name="sortedWord"></param>
        /// <param name="maxline"></param>
        public void Print(Dictionary<string, int> sortedWord, int maxline = 0)
        {
            if (maxline != 0)
            {
                int i = 0;
                foreach (KeyValuePair<string, int> item in sortedWord)
                {

                    Console.WriteLine(item.Key + " " + item.Value);
                    if (i == maxline)
                        break;
                    i++;
                }
            }
            else
            {
                foreach (KeyValuePair<string, int> item in sortedWord)
                {
                    Console.WriteLine(item.Key + " " + item.Value);
                }
            }
            
        }
    }

  这样就将命令行的输入输出剥离开来。

  接下来就是对核心计算功能的剥离了。为了保证代码后续的升级,因此经过接口定义三个功能函数:

interface ICore
{
    /// <summary>
    /// 得到所有字母数量
    /// </summary>
    /// <returns></returns>
    int GetCharNum();

    /// <summary>
    /// 得到所有单词数量
    /// </summary>
    /// <param name="wordsCount"></param>
    /// <returns></returns>
    int GetWordNum(Dictionary<string, int> wordsCount);

    /// <summary>
    /// 获取排序后的单词集
    /// </summary>
    /// <param name="wordsCount"></param>
    /// <returns></returns>
    Dictionary<string, int> SortAndGetWord(Dictionary<string, int> wordsCount);
}

  这三个方法就是对须要剥离的三个功能的规范抽象。在这三个方法下面对三个功能进行详细实现。代码和第一步彻底同样,只是从新拆分开了,因此就不在赘述。
俊老板比我细心,基本都是我敲错了他一眼就发现了,因此代码编写起来比较快速。
而后是对核心功能进行测试,一共三个功能。因此分别写了三条测试语句来对核心计算进行测试。首先是生成动态连接库。在VS中的项目属性修改输出类型为类库,




项目属性

  在从新生成一次,到Debug文件夹中进行查看,就已经生成DLL了。




WordCount动态连接库

  在单元测试中添加对这个DLL的引用,在添加using wordCount使用命名空间以后,就能够调用方法验证算法是否正确。可是...尽管我对其添加了引用,最后没法使用命名空间。




没法引用空间

  查阅资料无果后,俊老板和我分开尝试怎么样才能使用dll。最后我发现当新建的项目是动态连接库项目的时候,就可以正常引用dll。最后没有办法,只好新建一个动态连接库项目,而后将类拷贝过去。




项目结构

  最后在wordCounter里面完成单元测试

  1. 统计字符数。这个是真的深有体会,不作不知道,一作吓一跳,原来代码有这么多错误的地方以前没观察到。主要出现的错误有若是只有一行会多算一个字符、中文字符也被计算在内。一一改正以后进行测试,测试字符数所用代码以下:
[TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            string content = File.ReadAllText("test.txt");
            
            ICore core = new Core(content);
            Assert.AreEqual(93, core.GetCharNum());
        }
    }

测试经过

2.统计单词数。相似于第一个测试,测试代码以下:

[TestMethod]
public void TestMethod2()
{
    string content = File.ReadAllText("test.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    Assert.AreEqual(6, core.GetWordNum(words));
}

  本觉得也会完美经过,结果出现错误。




测试失败


  调试测试以后发现,是测试一修改的中文字符去除的时候出现问题,修改以后,在此测试,完美经过



测试成功

3.统计前10个单词的输出。字符串匹配我门还真不知道怎么测试,因此对一开始打算直接输出,俊老板说那根本就不是测试...最后,我和俊老板得出一个折中的方案,用StringAssert测试字符串,输出的字符串使用foreach拼接

[TestMethod]
public void TestMethod3()
{
    string content = File.ReadAllText("test.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    core.GetWordNum(words);
    string test="file1234 2\ndsfdsfsd5421 1\nhello 1\nwindows2000 1\nwindows95 1\nwindows98 1\n";
    words = core.SortAndGetWord(words);
    string actual = "";
    foreach (KeyValuePair<string, int> pair in words)
    {
        actual += pair.Key + " " + pair.Value + "\n";
    }

    StringAssert.Equals(test, actual);
}

测试经过

  测试完成以后,基本的计算功能就有了保障。赶忙提交到Git进行保存。




测试经过

  接着开始对其进行功能上面的拓展。
  新增的-i-m-n-o这四个参数匹配就只有设定单词词组和输出到文件是新的须要实现的功能,首先是对单词词组的实现,由于涉及到词组的频次,故仍是使用Dictionary<string,int>来保存数据,而对于词组的构造,就是根据设定的长度,利用双重for循环进行词组拼接。代码以下

/// <summary>
/// 得到指定长度的词组
/// </summary>
/// <param name="words"></param>
/// <param name="len"></param>
/// <returns></returns>
public static Dictionary<string, int> GetWordGroup(List<string> words, int len = 3)
{
    Dictionary<string, int> wordsGroup = new Dictionary<string, int>();
    for (int j = 0; j<words.Count;j++)
    {
        string wordsRelation = "";
        if (j <= words.Count - len )
        {
            for (int i = j; i < j+len; i++)
            {
                wordsRelation += words[i] + " ";
            }

            if (wordsGroup.ContainsKey(wordsRelation))
            {
                wordsGroup[wordsRelation]++;
            }
            else
            {
                wordsGroup.Add(wordsRelation, 1);
            }
        }
    }
    return wordsGroup;
}

  对于-o的写出倒没有什么问题,读入在以前就已经实现了,如今写出到文件和其原理基本类似。写出的代码以下:

public void WriteToFile(string path,string content)
{
    using (System.IO.StreamWriter file = new System.IO.StreamWriter(path))
    {
        string line = "";
        using (StringReader sr = new StringReader(content))
        {
            while ((line = sr.ReadLine()) != null)
            {
                file.WriteLine(line);
            }
        }
    }
}

  传入写出到文件的content就须要本身构造了。利用字符串拼接,按照做业指导中的格式,构造出符合规范的词组,构造方法以下:

int charNum = core.GetCharNum();
int wordNum = core.GetWordNum(words);
words = core.SortAndGetWord(words);
wordsGroup=GetWordGroup(((Core)core).Lists,m);
string wordsGroupContent = "";
foreach (KeyValuePair<string, int> wordsPair in wordsGroup)
{
    wordsGroupContent += wordsPair.Key + ": " + wordsPair.Value + "\n";
}
string wordsCountContent = io.Print(words, n);

string fullContent = "characters: " + charNum + "\n" +
    "words: " + wordNum + "\n" +
    "lines: " + ((Core)core).LineCount + "\n\n" +
    wordsGroupContent + "\n"+
    wordsCountContent;

  再次提交到Git完成保存,就准备完成图形化界面绘制。




Git推送成功


Git推送成功

  接下来进入到第四步,是对图形化界面的实现。咱们采用WinForm的形式完成对图形化界面的绘制,这部分主要是由俊老板实现,我对他代码进行审核。
下面是俊老板绘制的图形界面




wordCounter GUI

  关于事件的基本就是一个输入输出OpenFileDialogSaveFileDialog进行保存,其他的统计都是在前面第三步的wordCount项目中作好了的,直接调用就行了。因此,图形化界面制做总体比较简单。可是调试的时候遇到一个有趣的问题。如图:




wordCounter GUI

  右边的统计结果很明显没有了换行,但是刚刚第三步的代码中我明明添加了\n换行符,而且在命令行中也可以正常显示。查阅资料后得知(参考博客http://www.javashuo.com/article/p-gplibcus-du.html),Windows的界面换行符是\r\n,而命令行中是任意的,就是\n\r\n都是能够的。所以,修改原有代码,成功解决问题。




wordCounter GUI

  最后上传Git完成编写工做。




Git项目界面

两点分析

总结

  编写代码这个部分多是结对编程最大的意义所在了。我和俊老板从开始的争争吵吵,互不相让慢慢的开始变得有默契。最后一个眼神就知道该换位置了。实际上,刚开始的效率比较低下,后来咱们慢慢的熟悉以后,写出来的代码真的是质量高,不多会有二次改动,这也是咱们星期四才开始写代码,星期天就所有作好的根本缘由。在写代码的时候也发现结对编程的问题所在,好比编累了,容易一块儿打游戏,以及若是一方情绪控制很差,容易撂挑子,另一我的就很被动...

单元测试

  按照需求分析,咱们在单元测试这里准备了10个测试用例来保证程序健壮性

  1. 输出格式测试
    测试用例:



    测试用例

测试代码:

string content = File.ReadAllText("test.txt");
Dictionary<string, int> words = new Dictionary<string, int>();
ICore core = new Core(content);
core.GetCharNum();
core.GetWordNum(words);
string test="file1234 2\ndsfdsfsd5421 1\nhello 1\nwindows2000 1\nwindows95 1\nwindows98 1\n";
words = core.SortAndGetWord(words);
string actual = "";
foreach (KeyValuePair<string, int> pair in words)
{
    actual += pair.Key + " " + pair.Value + "\n";
}

StringAssert.Equals(test, actual);

测试结果:




测试结果

  1. 字母、单词、行数统计的测试
    测试用例:



    测试用例

测试代码:

[TestMethod]
public void TestMethod1()
{
    string content = File.ReadAllText("test.txt");
    
    ICore core = new Core(content);
    Assert.AreEqual(93, core.GetCharNum());
}

[TestMethod]
public void TestMethod2()
{
    string content = File.ReadAllText("test.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    Assert.AreEqual(6, core.GetWordNum(words));
}

测试结果:




测试结果

  1. 前10个频次最高按照字典排序的单词测试
    测试用例:



    测试用例

测试代码:

[TestMethod]
public void TestMethod4()
{
    string test = "confidence 3\nyourself 3\nadmiration 1\nahead 1\narrogant 1\nchallenges 1\ndizzy 1\nenergy 1\nextremely 1\nfarewell 1\n";
    string content = File.ReadAllText("test1.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    core.GetWordNum(words);
    words = core.SortAndGetWord(words);
    string actual = "";
    foreach (KeyValuePair<string, int> pair in words)
    {
        actual += pair.Key + " " + pair.Value + "\n";
    }

    StringAssert.Equals(test, actual);
}

测试结果:




测试结果

  1. 对于非ascii码的处理测试
    测试用例:



    测试用例

测试代码:

[TestMethod]
public void TestMethod5()
{
    string test = "sdfs 2\ndfdsf 1\ndffs 1\nsdfsf 1";
    string content = File.ReadAllText("test2.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    core.GetWordNum(words);
    words = core.SortAndGetWord(words);
    string actual = "";
    foreach (KeyValuePair<string, int> pair in words)
    {
        actual += pair.Key + " " + pair.Value + "\n";
    }

    StringAssert.Equals(test, actual);
}

测试结果:




测试结果

  1. 读入文件非法文件名测试
    测试用例:



    测试用例

测试代码:

[TestMethod]
public void TestMethod6()
{
    string content = "";
    if (File.Exists("test10.txt"))
    {
        content = File.ReadAllText("test10.txt");

    }
    else
    {
        content = "文件名不正确请检查...";
    }
    StringAssert.Equals("文件名不正确请检查...", content);
}

测试结果:




测试结果

  1. 输出文件非法文件名测试

测试用例:




测试用例

测试代码:

[TestMethod]
public void TestMethod7()
{
    IDataIO data = new DataIO();
    string path = "??T>>Txsd>test.txt";
    data.WriteToFile(path, "hello");
    bool exist = File.Exists(path);
    Assert.AreEqual(false, exist);
}

测试结果:




测试结果

  1. 词组长度测试

测试用例:




测试用例

测试代码:

[TestMethod]
public void TestMethod8()
{
    string test = "dffs sdfs sdfsf : 1\nsdfs sdfsf dfdsf: 1\nsdfsf dfdsf sdfs: 1\n";
    string content = File.ReadAllText("test1.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    Dictionary<string, int> wordsGroup = new Dictionary<string, int>();
    ICore core = new Core(content);
    IDataIO io = new DataIO();
    int charNum = core.GetCharNum();
    int wordNum = core.GetWordNum(words);
    words = core.SortAndGetWord(words);
    wordsGroup = Program.GetWordGroup(((Core)core).Lists, 3);
    string wordsGroupContent = "";
    foreach (KeyValuePair<string, int> wordsPair in wordsGroup)
    {
        wordsGroupContent += wordsPair.Key + ": " + wordsPair.Value + "\n";
    }
    StringAssert.Equals(test, wordsGroupContent);
}

测试结果:




测试结果

  1. -m -n 后不是数字的处理



    测试用例

测试代码:

wordCount.exe -i test2.txt -m heelo -n fdsfsf -o output.txt

测试结果:




测试结果

  1. 意外状况处理测试



    测试用例

测试代码:

[TestMethod]
public void TestMethod9()
{
    string content = File.ReadAllText("test3.txt");
    ICore core = new Core(content);
    Assert.AreEqual(46, core.GetCharNum());
}

测试结果:




测试结果

  1. 输入错误的处理

    因为没法对命令行进行单元测试,这里使用人工测试

测试用例:




测试用例

测试代码:
wordCount.exe -i test2.txt -x -xdsdsfd -o output.txt
测试结果:




测试结果

效能分析

  因为命令行工具在效能分析中没法使用,因此为了方便查看效率,就将输出目录和输入目录直接硬编码在程序中,本次效能分析使用小说《苏菲的世界》英中对照版进行测试




测试用例

  在性能查看器中选择查看CPU效率,分析结果以下图:




性能消耗分析

  从图中能够看到,CPU开销最大的就是Main()函数,固然这是由于Main()函数中包括了所有的调用方法。双击进入Main()函数:




性能消耗分析

  生成详细报告以后,查看执行单个工做最多的函数:




性能消耗分析

  从上图咱们能够看到,调用最多的是字符串拼接函数Concat(),应该是我在输出到output.txt中的时候,为了使格式统一,用了大量的字符串拼接。可是虽然调用次数多,效率不必定低。因此继续查看

  点击查看消耗最大的Main()函数,查看代码占用效率:




代码消耗

  从图中能够看出来,消耗主要是在GetCharNum()wordsGroupContent += wordsPair.Key + ": " + wordsPair.Value + "\n";string wordsCountContent = io.Print(words, n);这三句。咱们挨个进行分析。

  首先进入GetCharNum()函数查看:




代码消耗

  这里消耗最大的代码就是正则表达式...这可咋优化啊。正则表达式的主要消耗在于它的“回溯”匹配,只要减小“回溯”次数,就可以提升效率。可是匹配Ascii之外得字符除了string regexStr1 = Regex.Replace(line, @"[^\u0000-\u007F]+", string.Empty);这一句,也没有其余更好的方法了。只能转而优化其余。

  查看另外两个消耗比较大的代码,得出一个惊人的发现。消耗最大的都是字符拼接处理:




Print函数最高消耗


wordGroupContent消耗

  我和俊老板想了一下,修改C#的字符串拼接方式应该能够改进性能。所以,参考博客https://blog.csdn.net/yeshennet/article/details/51435409后决定,将两个“重灾区”代码改用StringBuilder.Append()函数进行优化。




修改拼接


修改拼接

  从新探查性能以后以后查看分析报告:




性能查看

  性能大大提高,提升了程序效率。

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 15
· Estimate · 估计这个任务须要多少时间 20 15
Development 开发 4320 6182
· Analysis · 需求分析 (包括学习新技术) 720 720
· Design Spec · 生成设计文档 240 120
· Design Review · 设计复审 (和同事审核设计文档) 240 120
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 240 120
· Design · 具体设计 1080 2880
· Coding · 具体编码 360 1440
· Code Review · 代码复审 720 60
· Test · 测试(自我测试,修改代码,提交修改) 720 720
Reporting 报告 720 660
· Test Report · 测试报告 360 360
· Size Measurement · 计算工做量 180 180
· Postmortem & Process Improvement Plan · 过后总结, 并提出过程改进计划 180 120
合计 5060 6917

总结

  结对编程从编程效率上来讲,确实不容易出现错误和低质量代码。但是在作需求分析的时候,两我的作分析容易致使意见分歧,若是没有第三我的,就很容易互相僵持,走入死胡同。老师上课讲的,两我的互相监督的效果咱们也没有达到,应该说不是体制问题,是咱们自身要求没有达到。致使后面快作完的时候,效率很是低下,都一块儿打游戏了,正好双排上分。因此,我认为,在编程环节,是1+1>2的,可是项目需求分析和项目收尾的时候,每每会出现1+1<1的效果。

相关文章
相关标签/搜索