word count 项目总结

一、github地址

https://github.com/hyt1022/wordcount

 

二、PSP表格

PSP2.1

PSP阶段

预估耗时

(分钟)

实际耗时

(分钟)

Planning

计划

30

30

· Estimate

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

30

30

Development

开发

840

1200

· Analysis

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

60

60

· Design Spec

· 生成设计文档

60

60

· Design Review

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

30

30

· Coding Standard

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

30

30

· Design

· 具体设计

120

120

· Coding

· 具体编码

360

600

· Code Review

· 代码复审

60

60

· Test

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

120

240

Reporting

报告

160

240

· Test Report

· 测试报告

100

150

· Size Measurement

· 计算工作量

30

30

· Postmortem & Process Improvement Plan

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

30

60

 

合计

1030

1470

 

三、解题思路介绍

 拿到题目后。首先对程序的准备进行了思考。

这次用Java进行编程,选用的编译器是Myeclipse10,配置的jdk版本是   。因为有一段时间没有写java,稍微有些生疏。因此查阅了大二时面向对象程序设计【1】。这本书对于java的基本语法和常用类进行了总结和用法指导,深入浅出。

后面考虑到要生成exe文件,因此在网上查阅了如何将java项目打包生成exe文件【2】。

考虑到要将本地项目上传到github,在网上查阅了用命令行上传github的方法。【3】

再对程序的实现进行分析。

看到程序很容易就想到去年编译技术课程。这次的程序也可以沿用编译技术的部分的思想。

对于基础功能。最直接的想法是,通过将需要分析的* .c文件读入。用一个字符数组进行存储。对数组从到头尾扫一遍,在扫的过程中,分析统计文件的字符,单词数,行数等。

对于拓展功能。在基础功能的基础上,在扫的过程中考虑更多的因素。对于停用词表,现将词表读入文件,分成每一个停用词,存入一个动态的字符数组。在扫待测数组时,将读到的每个单词与停用词表进行比较,统计停用词的个数。统计代码行,空行,数据行的基本思路也和基础功能的一致。但是需要考虑更多的状态,以分析判断此行到底属于哪一种类型。

对于基本输入输出。输入方式通过main函数自带的参数,分析args[],来判断输入的指令。输出则通过输入的文件名,将输出结果写入txt文件。

 

四、代码说明

public void analyse(String fileName,String stoplistFile,boolean f5)
{
    Readfile(fileName);
    if(f5)
    {
        ReadStoplist(stoplistFile);
    }
    file = fileName;
    String tempword;
    String tep = new String(tempchars);
    int flag = 0;//判断上一个字符的标志,用来推断单词数是否增加;进入单词分隔符则为0,进入可显示字符则为1
    while(tempchars[p] != '\0')
    {
        if(tempchars[p] == '\n')
        {
            flag = 0;
            l++;
            p++;
        }
        else if(tempchars[p] == '\r')
        {
            flag = 0;
            p++;
        }
        else if(tempchars[p] == '\t')
        {
            flag = 0;
            p++;
        }
        else if(tempchars[p] == ' ')
        {
            flag = 0;
            p++;
        }
        else if(tempchars[p] == ',')
        {
            flag = 0;
            p++;
        }
        else//如果是可显示的字符
        {
            if(flag == 0)//如果上一个是单词分隔符则单词数加一
            {
                w++;
                if(f5)//如果有停用词表则进行分析
                {
                    int a=p;
                    while(tempchars[p] != '\n'&&tempchars[p] != '\r'&&tempchars[p] != '\t'&& tempchars[p] != ' '&& tempchars[p] != ','&&tempchars[p]!='\0')
                    {
                        p++;
                    }//将这个单词的开始位置和结束位置做标记
                    tempword = tep.substring(a, p);//将这个单词截取下来
                    for(int i=0;i<stopword.size();i++)//将单词与停用词表进行对比
                    {
                        if(tempword.equals(stopword.get(i)))
                        {
                            s++;
                        }
                    }
                    p=p-1;//当前p指向单词间的分隔符,退回到此字符的前两个
                }
            }
            flag = 1;
            p++;
        }
    }
}
View Code

此段代码的主要逻辑是一个while循环,循环分析每一个字符,以及当前对应的的状态。

读到每一个字符则字符数加一。

读到每一个换行符则行数加一,最后的行数还要加上第一行。

读到每一个可显示字符,则判断前一个字符是不是单词分隔符,如果是则单词数加一;如果不是则前一个也是可显示字符,则为同一个单词,单词数不变。如果有停用词表,则将单词截取下来和词表中的单词进行对比。

public void analyseAddtion()
{
    int i=0;
    int flag = 1; //此行是否分配
    int cflag = 0; //此行是否处于注释里
    int dflag = 0; // 此行是否是‘*/’的注释行
    int count =0; //每行有效字符计数
    while(tempchars[i] != '\0')
    {
        if(tempchars[i] == ' '||tempchars[i] == '\t'||tempchars[i] == '\r')//如果是空格一类的直接跳过
        {
            i++;
        }
        else if(tempchars[i] == '/')
        {
            count++;
            if(tempchars[i+1] == '/')
            {
                if(flag == 1 && count<3 && cflag == 0)//此种情况可直接判断为注释行
                {
                    comment++;
                    flag = 0;
                }
            }
            else if(tempchars[i+1] == '*')
            {
                cflag = 1;
                i++;//跳过一个字符,防止下一个是‘/’的情况
            }
            i++;
        }
        else if(tempchars[i] == '*')
        {
            count++;
            if(tempchars[i+1] == '/')
            {
                if(cflag ==1)
                {
                    cflag = 0;
                    i++;//跳过一个字符,防止下一个是‘*’的情况
                    count = 0;
                    dflag = 1;
                }
            }
            i++;
        }
        else if(tempchars[i] == '\n')//到每行末尾,若此行没有分配则根据当前状态进行分配
        {
            if(flag == 1 && cflag == 0 && dflag == 0 && count<2)
            {
                empty++;
                flag = 0;
            }
            if(flag == 1 && cflag == 0 && dflag == 0 && count>1)
            {
                code++;
                flag = 0;
            }
            if(flag == 1 && cflag == 1)
            {
                comment++;
                flag = 0;
            }
            if(flag == 1 && cflag == 0 && dflag == 1)
            {
                comment++;
                flag = 0;
                dflag = 0;
            }
            count = 0;
            flag = 1;
            i++;
        }
        else//如果是其他字符
        {
            count++;
            if(count > 1 && flag == 1 && cflag == 0)//此种情况可直接判断为代码行
            {
                code++;
                flag = 0;
            }
            i++;
        }
    }
    //若最后一行没有分配,会通过当前状态进行分配
    if(flag == 1 && dflag == 1)
    {
        comment++;
    }
    if(flag == 1 && cflag == 1)
    {
        comment++;
    }
    if(flag == 1 && cflag == 0 && dflag == 0 && count<2)
    {
        empty++;
    }
    if(flag == 1 && cflag == 0 && dflag == 0 && count>1)
    {
        code++;
    }
}
View Code

此段代码的主要逻辑也是while循环,循环分析每一个字符,以及当前对应的的状态。

如果读到空格,制表符和回车符,则直接跳过,这个对判断行没有影响。

如果读到’/’,则判断下一个是不是’/’,如果是且此行没有确定是什么行,而且此行当前的可显示字符数不超过两个并且不在注释范围内 ,则可判断是注释行。如果下一个是’*’,则此行进入注释范围内(需要’*/’才能消除此注释范围)。

如果读到’*’,则判断下一个是不是’/’,如果是,判断是否在注释范围。如果在,退出注释范围,将当前可显示字符数清零,不在则当做两个可显示字符即可。

如果读到‘\n’,到了每行末尾,如此行还没确定是什么行,则根据当前的状态判断,即是否在注释范围,是否是’*/’的注释行,是否是空行等。

最后在退出时,也会判断最后一的状态,确定是什么行。

for(int i=0; i<args.length;i++)//循环判断每一个字符串
        {
            if(args[i].equals("-c"))
            {
                func[0] = true;
                if((i+1) == args.length)//如果此字符串为最后一个,则不符合格式,下同
                {
                    error = true;
                    break;
                }
                if(args[i+1].charAt(0) != '-')//如果下一个不是'-'开头的指令,则默认为文件名,进行读取,下同
                {
                    i++;
                    filename = args[i];
                }
            }
            else if(args[i].equals("-w"))
            {
                func[1] = true;
                if((i+1) == args.length)
                {
                    error = true;
                    break;
                }
                if(args[i+1].charAt(0) != '-')
                {
                    i++;
                    filename = args[i];
                }
            }
            else if(args[i].equals("-l"))
            {
                func[2] = true;
                if((i+1) == args.length)
                {
                    error = true;
                    break;
                }
                if(args[i+1].charAt(0) != '-')
                {
                    i++;
                    filename = args[i];
                }
            }
            else if(args[i].equals("-o"))
            {
                func[3] = true;
                if((i+1) == args.length)
                {
                    error = true;
                    break;
                }
                i++;
                outputfile = args[i];//'-o'指令后必须接输出文件名,进行读取
            }
            else if(args[i].equals("-e"))
            {
                func[4] = true;
                if((i+1) == args.length)
                {
                    error = true;
                    break;
                }
                i++;
                stoplistfile = args[i];//'-e'指令后必须接停用词表文件名,进行读取
            }
            else if(args[i].equals("-a"))
            {
                func[5] = true;
                if((i+1) == args.length)
                {
                    error = true;
                    break;
                }
                if(args[i+1].charAt(0) != '-')
                {
                    i++;
                    filename = args[i];
                }
            }
            else if(args[i].equals("-s"))
            {
                func[6] = true;
                if((i+1) == args.length)
                {
                    error = true;
                    break;
                }
                if(args[i+1].charAt(0) != '-')
                {
                    i++;
                    filename = args[i];
                }
            }
            else
            {
                error = true;
                break;
            }
        }
View Code

循环判断,主函数自带的参数,是一个字符串数组。由此分析指令。’-c’, ’-w’, ’-l’, ’-a’指令后面若接文件名则是待分析的文件名,进行读取。‘-o’后接的文件名必然是输出文件名,’-e’后接的文件名必然是停用词表。分析后则可得到程序需要完成哪些功能,并接受到指令中相应的文件名。

 

五、测试设计过程

通过11个测试用例和13个执行语句来对程序进行测试。每个测试用例均对于不同的功能有所侧重,覆盖了系统能实现的绝大部分功能。根据白盒测试的思想,测试用例能也能覆盖绝大部分的条件判断语句。用例中出现换行和文件结尾时容易导致程序高风险,用例的字符数过高引起数组越界也会造成高风险。此测试方案基本能满足对此程序功能测试要求。以下会对每个测试用例进行阐释和具体说明。

测试1:

用例:file1.c

用例说明:用于测试字符统计的基本功能,此用例包含了空格和制表符,测试能否正确统计空格即制表符。

执行语句:-c file1.c -o output1.txt

预计输出:file1.c,字符数:15

 

测试2:

用例:file2.c

用例说明::用于测试字符统计的基本功能,此用例包含空行,测试能否统计换行字符。

执行语句:-c file2.c -o output2.txt

预计输出:file2.c,字符数:22

 

测试3:

用例:file3.c

用例说明:用于测试单词统计的基本功能,此用例包含换行,空格,逗号隔开,测试能否正确统计单词数。

执行语句:-w file3.c -o output3.txt

预计输出:file3.c,单词数:16

 

测试4:

用例:file4.c

用例说明:用于测试停用词表功能,此用例测试基本功能,测试能否将例子中需要屏蔽的单词屏蔽。

执行语句:-w file4.c -e stoplist.txt -o output4.txt

预计输出:

file4.c,单词数:13

file4.c,停用后单词数:10

 

测试5:

用例:file5.c

用例说明:用于测试停用词表功能,此用例包含单词中含有停用词表的单词但不等于此单词,测试是否会误判断屏蔽。

执行语句:-w file5.c -e stoplist2.txt -o output5.txt

预计输出:

file5.c,单词数:20

file5.c,停用后单词数:20

 

测试6:

用例:file6.c

用例说明:用于测试行统计功能,此用例测试基础功能,包含空行,只有一个字符的行等。

执行语句:-l file6.c -o output6.txt

预计输出:file6.c,行数:9

 

测试7:

用例:file7.c

用例说明:用于测试代码行和注释行,此用例包含容易混淆的代码行和注释行的情况,测试能否正确统计行数。

执行语句:-a file7.c -o output7.txt

预计输出:file7.c,代码行/空行/注释行: 6/0/3

 

测试8:

用例:file8.c

用例说明:用于测试代码行和空行,此用例包含很多容易混淆的空行和注释行的情况,测试是否能正确统计行数。

执行语句:-a file8.c -o output8.txt

预计输出:file8.c,代码行/空行/注释行: 0/1/4

测试9:

用例:file9.c

用例说明:用于测试空行和代码行,此用例包含容易混淆的代码行和空行的情况,测试时候能正确统计行数。

执行语句:-a file9.c -o output9.txt

预计输出:file9.c,代码行/空行/注释行: 2/4/0

 

测试10:

用例:file10.c

用例说明:用于测试代码行,空行,注释行。此用例为综合用例,包含各种可能混淆的情况,测试能否正确统计行数。

执行语句:-a file10.c -o output10.txt

预计输出:file10.c,代码行/空行/注释行: 11/1/16

 

测试11:

用例:file1.c

用例说明:用来测试在不提供制定输出文件下,能否将结果输出在默认文件result.txt中。

执行语句:-l file1.c

预计输出:file1.c,字符数:15

 

测试12:

用例:当前文件夹中所有“.c”文件

用例说明:用于测试能否递归处理当前文件夹中所有符合条件的文件。

执行语句:-w -s .*.c -o output12.txt

预计输出:(较多不予以显示)

 

测试13:

用例:file11.c

用例说明:用于测试全功能,包含所有的功能指令,给出的用例中也包含了各种可能处理的情况,测试能否正常运行并给出正确的结果。

执行语句:-c -w -l -a -s *.c -e stoplist.txt -o output13.txt

预计输出:(较多不予以显示)

 

六、参考文献

【1】面向对象程序设计教程,任宏萍编著。

【2】https://www.cnblogs.com/yxwkf/p/4609765.html

【3】https://jingyan.baidu.com/article/0202781145eaab1bcc9ce5f0.html

posted @ 2018-03-20 22:41  honeytea  阅读(458)  评论(1编辑  收藏  举报