Word Count 第二周作业
GitHub地址
GitHub 地址为:https://github.com/Lovegoodstudy/WordCount
PSP 表格
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
20 | 20 |
· Estimate |
· 估计这个任务需要多少时间 |
20 | 20 |
Development |
开发 |
1100 | 1370 |
· Analysis |
· 需求分析 (包括学习新技术) |
240 | 280 |
· Design Spec |
· 生成设计文档 |
100 | 120 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
40 | 40 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 | 20 |
· Design |
· 具体设计 |
120 | 150 |
· Coding |
· 具体编码 |
280 | 400 |
· Code Review |
· 代码复审 |
100 | 60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
200 | 300 |
Reporting |
报告 |
150 | 180 |
· Test Report |
· 测试报告 |
100 | 100 |
· Size Measurement |
· 计算工作量 |
20 | 20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 | 60 |
合计 |
1270 |
1570 |
解题思路
为保证程序扩展性,通过一个专门的类来获取命令行参数,转化为可以与主类交互的参数。主类中负责处理经过序列化的参数,一遍读取文件后即处理和统计所有的结果并通过字符串数组返回结果,主函数再将结果和参数传给输出函数。输出函数根据参数决定哪些结果输出到文件中,哪些不输出。
参数序列化时,
<输入串> -> [-c][-w][-l][-a][-s] <输入文件名> [<-e><停用词文件名>] [<-o><输出文件名>]
中使用case语句进行跳转,需要Java7及以上环境支持。
获取inputFile的列表后,对每个列表读文件,读取字符通过'\n'字符识别换行,' ', '\t', ','字符来识别单词。识别成功即做统计(判断)和清空。一遍读取,对于长文件来说效率极高。之后所有统计结果也由其生成,但通过output函数携带参数来决定哪些结果会被输出。
代码说明
import java.util.ArrayList; import java.awt.Frame; import java.awt.FileDialog; public class Params { public static boolean charParam = false, wordParam = false, lineParam = false, detailParam = false, exceptWordListParam = false, recursionParam = false; public static String outputFile = "outputFile.txt", exceptWordListFile = ""; public static ArrayList<String> inputFile = new ArrayList<String>();; Params(String[] args) { for (int i = 0; i < args.length; i++) { switch (args[i]) { case "-c": this.charParam = true; break; case "-w": this.wordParam = true; break; case "-l": this.lineParam = true; break; case "-a": this.detailParam = true; break; case "-s": this.recursionParam = true; break; case "-x": this.charParam = true; this.wordParam = true; this.lineParam = true; this.detailParam = true; Frame frame = new Frame(); FileDialog openFile = new FileDialog(frame, "打开文件", FileDialog.LOAD); openFile.setVisible(true); String dirName = openFile.getDirectory(); String fileName = openFile.getFile(); System.out.println(dirName + fileName); this.inputFile.add(dirName + fileName); frame.dispose(); break; case "-o": this.outputFile = args[i + 1]; i = i + 1; break; case "-e": this.exceptWordListParam = true; this.exceptWordListFile = args[i + 1]; i = i + 1; break; default: this.inputFile.add(args[i]); break; } } } }
此为Params类,通过构造函数构造出一系列参数并设定初始值,以保证程序运行时错误和异常可以被侦测并解决。
主要比较有意思的是在-x选项中,也就是高级功能,通过GUI创建一个窗体,再在窗体作为parent调用FileDialog,实现了可视化的选择文件。可以看到,通过这个类,可以轻松实现一些功能的复用,比在主类中就对参数处理并设定要容易的多。
然后再贴一段核心计数代码
public static ArrayList<String> count(ArrayList<String> filepath, String[] exceptWordList) { ArrayList<String> countResult = new ArrayList<String>(); // Result init for (int i = 0; i < filepath.size(); i++) { String filepath0 = filepath.get(i); Integer charCount = 0, wordCount = 0, lineCount = 0, codeLineCount = 0, blankLineCount = 0, noteLineCount = 0; // Count init try { File file = new File(filepath0); BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); int tempChar = 0; char c; String word = "", line = ""; //Line and word string init while ((tempChar = reader.read()) != -1) { c = (char)tempChar; if (c == '\r') continue; charCount = charCount + 1; if (c == '\n') { lineCount = lineCount + 1; //Line end and get the type switch (getLineType(line)) { case 0: blankLineCount = blankLineCount + 1;break; case 1: codeLineCount = codeLineCount + 1;break; case 2: noteLineCount = noteLineCount + 1;break; } line = ""; if (word.length() > 0) { //Word end and find if it is a except word if (isNotExceptWord(word, exceptWordList)) wordCount = wordCount + 1; word = ""; } } else { line = line + c; if ((c == ' ') || (c == ',') || (c == '\t')) { if (word.length() > 0) { if (isNotExceptWord(word, exceptWordList)) wordCount = wordCount + 1; word = ""; } } else { word = word + c; } } } if (word.length() > 0) { boolean flag = true; if (isNotExceptWord(word, exceptWordList)) wordCount = wordCount + 1; word = ""; } if (line.length() > 0) { lineCount = lineCount + 1; switch (getLineType(line)) { case 0: blankLineCount = blankLineCount + 1;break; case 1: codeLineCount = codeLineCount + 1;break; case 2: noteLineCount = noteLineCount + 1;break; } line = ""; } } catch (IOException e) { System.out.println("Input Filepath Error!"); System.exit(0); } countResult.add(filepath0 + ", 字符数" + charCount); countResult.add(filepath0 + ", 单词数" + wordCount); countResult.add(filepath0 + ", 行数" + lineCount); countResult.add(filepath0 + ", 代码行/空行/注释行: " + codeLineCount + '/' + blankLineCount + '/' + noteLineCount); } // System.out.printf("%d,%d,%d,%d,%d,%d\n", charCount, wordCount, lineCount, codeLineCount, blankLineCount, noteLineCount); // Result return countResult; }
主要就是通过c一个一个读字符,在line后面按字符拼接。根据换行符来决定行line是否拼接完毕,若拼接完毕则进行行类型的判断——是否为代码行,是否为空行,是否为注释行。我认为使用'/*', '*/'字符注释是不通用的,在别的一些语言中不会用这个注释。并且在定义嵌套方面存在一些争议,所以此处没有支持这种类型的注释,只支持了'//'的注释。
对于word单词,通过换行符、空格、格式符、逗号等进行分割。当读到这类字符就说明单词结束。单词结束后会用一个函数判断是否在exceptWordList中,也就是读取后处理成字符串数组的停用词表。如果在,那就不会进行单词计数。
比较重要的是在最后读到文件结束符,仍然不要忘记对line和word处理。
测试设计过程
由于不能使用测试框架,未使用IDE环境。所以并没有进行自动化测试。大致说一下一些设计的测试吧。
白盒测试是需要覆盖到所有的判断分支,所以首先要测试的是各种参数是否能转换格式,即对Params类进行测试。
<输入串> -> [-c][-w][-l][-a][-s] <输入文件名> [<-e><停用词文件名>] [<-o><输出文件名>]
通过各种组合来测试,检查Params类初始化的各项属性是否符合主类需要。
-c -w -l -a 可覆盖所有输出格式控制,也可以单独做样例。
-c -w -l -a -o output.txt input.txt 指定输入输出
-c -w -l -a -s *.c 测试递归是否传所有符合条件文件参数
-c -w -l -a -o output.txt -e stopList.txt input.txt 指定停用词表
以上测试具有4个
然后对于主类测试,测试各种功能是否完善,主要包括
-c
-w
-l
这些没什么好说的,单独做3次测试。
测试-a
会有一些边界,例如
}//This is a note
这是注释
}}//This is code
这是代码
/
这是空行
//
这是注释
OK,有4个。
其他就是一些功能测试,递归所有文件功能,制定输出文件,停用词表,图像界面,都测一遍,4次。
在加上基础的指定输入文件功能测试。
基本上都测完了,switch的话也可以测,但是这里都是有规律的switch,风险比较小。
没有接触过真实项目测试,所以我也不知道怎么测,最后我没有生成exe,直接使用java命令。所以需要有java环境支持,其实本来java就是需要虚拟机跑的,exe是多此一举。
参考链接
[1] java 递归获取一个目录下的所有文件路径 https://www.cnblogs.com/zhouyalei/p/3324491.html