本项目Github地址:https://github.com/NNewBoy/wc
项目相关要求
基本功能:(已实现)
-c 统计文件字符数
-w 统计文件词的数目
-l 统计文件行数
扩展功能:(已实现)
-s 递归处理目录下符合条件的文件
-a 返回更复杂的数据(代码行 / 空行 / 注释行)
高级功能:(未实现)
-x 显示图形界面
解题思路
拿到项目后,首先理清项目需求,然后规划项目框架,根据框架设计功能函数,思考函数的实现方法,查找资料,本次项目查找了关于正则表达式的资料;设计部分完毕,开始代码编写,最后就是检测代码,修改代码。
设计实现过程
代码分成两大部分,5个类。
第一命令部分:Main类实现输入命令,Command类实现对命令的解析
输入命令后调用Command类解析命令,Command解析命令后调用功能类。
第二功能部分:Basicfunction类实现基本功能,Directory类实现-s功能,Increasedfunction类实现-a功能
基本功能通过正则表达式匹配每个字符实现;-s由于要递归目录里的多个文件,跟其他命令实现过程有所区别,因此分开编写,通过File类的listFiles()方法取得目录里符合条件的文件,然后各自调用Command类;-a也是使用正则表达式,统计代码行、空行、注释行,然后返回数目。
代码说明
命令解析:
public class Command { void command(String input,String filename,Matcher matcher) { try { Boolean s = false; List<String> commands = new ArrayList<>(); while (matcher.find()) { //将接收到的命令存入动态数组中 String command = matcher.group(); //判断是否接收到-s if (command.equals("-s")) { s = true; } else { commands.add(command); } } //实现-s命令 if (s) { new Directory().readDirectory(filename, input); } else { //实现其他命令 System.out.println("文件:" + filename); for (String command1: commands) { switch (command1) { case "-c": new Basicfunction().c(filename); break; case "-w": new Basicfunction().w(filename); break; case "-l": new Basicfunction().l(filename); break; case "-a": new Increasedfunction().a(filename); break; default: System.out.println("命令输入错误"); break; } } } } catch (Exception e) { System.out.println("输入路径出错"); } } }
对输入的命令,先判断是否有-s命令,是则调用Direction类遍历出符合条件的文件路径,否则当前路径就是目标文件的路径。
实现-s命令:
public class Directory { void readDirectory(String filename , String input) { Pattern pattern = Pattern.compile("\\-[cwla]"); Matcher matcher = pattern.matcher(input); File filedirectory = new File(filename); File[] files = filedirectory.listFiles(); for (File file : files) { //遍历路径里的文件夹 if (file.isDirectory()) { readDirectory(file.getPath(),input); } else { //实现-c -w -l -a 命令 new Command().command(input,file.getPath(),matcher); } } } }
遍历出符合条件的文件路径,对每个符合条件的文件调用Command类。
实现-c -w -l 命令:
public class Basicfunction { private String s1; private String s2; private int ccount; private int wcount; private int lcount; private void read(String filename) throws IOException { BufferedReader brin = new BufferedReader(new FileReader(filename)); while ((s1=brin.readLine())!=null) { ccount += s1.length();//统计字符数 s1 = s1 + " "; s2 += s1; lcount ++;//统计行数 Pattern pattern = Pattern.compile("\\w+"); Matcher matcher = pattern.matcher(s1); while (matcher.find()) { wcount++;//统计词数 } } brin.close(); } public void c(String filename) { try { read(filename); System.out.println("字符数:"+ccount); } catch (Exception e) { System.out.println("输入路径出错"); } } public void w(String filename) { try { read(filename); System.out.println("词的数目:"+wcount); } catch (Exception e) { System.out.println("输入路径出错"); } } public void l(String filename) { try { read(filename); System.out.println("行数:"+lcount); } catch (Exception e) { System.out.println("输入路径出错"); } } }
通过read()函数统计字符数、词数、行数,然后再返回。
实现-a命令:
public class Increasedfunction { private int emptyLine = 0,codeLine = 0,explainLine = 0,lineCount = 0; //通过正则表达式匹配指定字符 private int match(String regex , String sentence) { int i = 0; Pattern pattern1 = Pattern.compile(regex); Matcher matcher1 = pattern1.matcher(sentence); while (matcher1.find()) { i++; } return i; } private void read(String filename) throws IOException { //空字符数 int emptyCount; //各种标点符号数 int emptyCharCount; //词数 int displayCharCount; //单行注释符"//" int explainCount1; //多行注释开始"/*" int explainCount2; //多行注释结束"*/" int explainCount3; //含有注释符"/*"的代码行 int explainCount4; //含有注释符"//"的代码行 int explainCount5; String s1; Boolean explainValue = false; BufferedReader brin = new BufferedReader(new FileReader(filename)); while ((s1=brin.readLine())!=null) { emptyCount = match("\\s",s1); emptyCharCount = match("\\S\\W",s1); displayCharCount = match("\\w",s1); explainCount1 = match("\\/\\/",s1); explainCount2 = match("\\/\\*",s1); explainCount3 = match("\\*\\/",s1); explainCount4 = match("\\S{2,}.*\\/\\*",s1); explainCount5 = match("\\S{2,}.*\\/\\/",s1); if (explainCount2 != 0) { //多行注释开始,判断是注释行还是代码行 explainValue = true; if (explainCount4 != 0) { explainLine--; codeLine++; } } else if (explainCount3 != 0) { //多行注释结束 explainValue = false; explainLine++; } else if (explainCount1 != 0) { //含单行注释,判断是注释行还是代码行 if (explainCount5 != 0) { codeLine++; } else { explainLine++; } } else if (!explainValue && displayCharCount == 0 && (emptyCount != 0 || emptyCharCount <= 1)) { //空行情况 emptyLine++; } else if (!explainValue) { //剩下的是代码行 codeLine++; } if (explainValue) { //多行注释,超过两行的情况 explainLine++; } if (explainCount2 != 0 && explainCount3 != 0) { //多行注释只有一行的情况 explainLine--; } } brin.close(); } public void a(String filename) { try { read(filename); System.out.println("空行数:"+emptyLine); System.out.println("代码行数:"+codeLine); System.out.println("注释行数:"+explainLine); } catch (Exception e) { System.out.println("输入路径出错"); } } }
通过read()函数统计空行数、代码行数、注释行数;多行注释开始时explainValue=true,结束时为false,以此来判断注释行。
测试运行
空文件:
只有一个字符的文件:
只有一个词的文件:
只有一行的文件:
一个典型的源文件:
代码覆盖率:
PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
15 |
· Estimate |
· 估计这个任务需要多少时间 |
30 |
15 |
Development |
开发 |
680 |
1213 |
· Analysis |
· 需求分析 (包括学习新技术) |
120 |
157 |
· Design Spec |
· 生成设计文档 |
30 |
75 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
30 |
60 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
25 |
· Design |
· 具体设计 |
80 |
105 |
· Coding |
· 具体编码 |
300 |
566 |
· Code Review |
· 代码复审 |
60 |
105 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
30 |
120 |
Reporting |
报告 |
150 |
145 |
· Test Report |
· 测试报告 |
60 |
60 |
· Size Measurement |
· 计算工作量 |
30 |
25 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
60 |
60 |
合计 |
860 |
1373 |
项目小结
从PSP表格可以看出,我在学习新技术和具体编码上花的时间最多,这是因为我对java这门语言掌握的不是很到位,有很多常用库函数的用法都没记住,平时做项目练习太少,此后应多加训练敲代码的能力。此次项目运用JUnit的进行单元测试,很方便地得出了代码的一些bug,这比以前的检查bug的方法都要快捷很多,不过本次项目没有采用“测试驱动开发”(TDD)进行,下次项目尝试TDD,以使项目更加便捷地完成。本次项目最大的收获就是学会用科学的开发模式进行开发,了解到需求分析、设计与测试对一个项目的重要性。