一、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++; } } }
此段代码的主要逻辑是一个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++; } }
此段代码的主要逻辑也是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; } }
循环判断,主函数自带的参数,是一个字符串数组。由此分析指令。’-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