WordCount-结对项目
说明
合作者:201631062306,201631062407
代码地址:https://gitee.com/hhhhhhh2/WordCount
本次作业的链接:https://edu.cnblogs.com/campus/xnsy/2018Systemanalysisanddesign/homework/2188
一、PSP表格
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
40 |
20 |
· Estimate |
· 估计这个任务需要多少时间 |
30 |
20 |
Development |
开发 |
420 |
300 |
· Analysis |
· 需求分析 (包括学习新技术) |
120 |
30 |
· Design Spec |
· 生成设计文档 |
0 |
0 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
25 |
10 |
· Design |
· 具体设计 |
90 |
40 |
· Coding |
· 具体编码 |
120 |
200 |
· Code Review |
· 代码复审 |
60 |
120 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
100 |
60 |
Reporting |
报告 |
100 |
60 |
· Test Report |
· 测试报告 |
80 |
40 |
· Size Measurement |
· 计算工作量 |
5 |
10 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
10 |
40 |
|
合计 |
1200 |
950 |
总结:对每部分预估耗时的时候我基本上都往上说,隐约会暗示自己在项目的完成过程中不至于太过紧张时间。在实际工作中,因为我们一开始就有了明确分工,直接分头着手自己负责的方法模块,所以具体编码和开发时间比预估的缩减了很多。但代码复审阶段多花费了些时间,其实也是比一个人要少很多时间的。在一起合并、检查代码的过程中,我们轮流交换驾驶员和导航员的身份,两个人轮流改bug,另一个人不断提出改进的意见。因为两个人能考虑得更全面,你一句我一句的讨论中也把逻辑、思路等理得更顺。
二、互审代码情况
·审查的模块名称:
1.BaseFunc类,主要是之前各自完成的基本功能。
2.ExtendFunc类,包含getFiles(String)和count_space_code_nodeNum(Count,String)方法。
·发现的问题:该同学的代码格式很工整美观,注释简单明了,所以在代码互审阶段一点也不吃力。发现的主要问题有:
1.基本功能的实现中,没有考虑到读取命令行中输出文件的目录,而是直接在代码中给输出文件赋一个固定的目录值。
2.在getFiles()方法中,没有考虑文件通配符的判断,直接拆分目标文件路径后遍历整个文件。所以在修改时,需要在最外层再加一层文件路径是否存在通配符的情况。
三、设计过程
1.结构设计:
整个WordCount项目主要分为三部分,图形化界面、后端代码、单元测试。
1)Count类: 用于接收文件统计后的各数据结果。属性包括需要对文件计算的所有数据:字符数,单词数,行数,代码行,注释行,空行。
2)WordCount类:属性有几个重要的文件路径变量和Countduixiang,包含以下五个函数,主要实现基本功能。
void main(String[]):主函数,对命令判断后对几个重要文件路径进行赋值,调用操作方法command(ArrayList<String>,String[])。
void command(ArrayList<String>,String[]):判断各命令,调用wc()、count_space_code_nodeNum()等方法进行不同操作。
void wc(String,String):计算字符数+单词数(是否有停用词表两种情况)+行数。
boolean inStop(String, String[]):读取目标文件中的每个单词和停用词表中所有单词依次比较,有相同的则该方法返回true。
void initFile(String):初始化存储结果数据的文件,即写入”“以覆盖结果文件的原内容。
3)ExtendFunc类:主要实现高级功能,实现递归遍历文件夹和计算功能。
ArrayList<String> getFiles(String):递归遍历目标文件夹路径下所有文件,包括判断与命令中有通配符的文件类型相匹配的所有文件,返回一个文件数组。
String count_space_code_nodeNum(Count,String):计算空行数+代码行+注释行。
4)MainView类:实现图形化界面的显示,主要有初始化建立窗口、将目标文件路径传到后台进行数据处理后再显示到界面上两个操作。
2.算法设计(流程图):
这里是整个程序的运行流程图。
四、代码说明
我负责的工作部分是WordCount项目中扩展功能的停用词表、处理文件名通配符和高级功能的实现。
1.MainView.java:显示图形化界面,主要是通过JFrame画出窗口,并放入JTextField、JButton、JTextArea等控件用于操作。
关键代码是actionPerformend()方法:会对两个按钮的点击事件做出反应,点击“选择文件”按钮后会将得到的文件路径传入WordCount对象和ExtendFunc对象分别进行计算操作,点击“确认”按钮后将读取Count对象中各计算结果的值并放入JTextArea中显示出来。

1 @Override
2 public void actionPerformed(ActionEvent e) {
3 JButton button = (JButton)e.getSource();//得到事件源
4 WordCount wc = new WordCount();
5 ExtendFunc ef = new ExtendFunc();
6 File file = null;
7
8 //点击"选择文件"按钮时
9 if(button == button_select){
10 int select = jf.showOpenDialog(this); //显示打开的文件对话框
11 if(select == JFileChooser.APPROVE_OPTION){ //判断选择的是否为”确认“
12 file = jf.getSelectedFile();
13 System.out.println("目标文件是:" + file);
14 String targetFile = "";
15 String stopFile = "";
16 targetFile += file;
17 System.out.println("targetFile:" + targetFile);
18 try {
19 wc.wc(targetFile, stopFile);
20 ef.count_space_code_nodeNum(wc.getCount(), targetFile);
21 } catch (IOException e1) {
22 e1.printStackTrace();
23 }
24
25 if (file != null) {
26 fileText.setText(file.getAbsolutePath());
27 }//of if
28 }else{
29 System.out.println("打开操作被取消");
30 }//of if-else
31 }//of if
32
33 if(button == button_ok){
34 StringBuilder text = new StringBuilder();
35
36 String charCount = String.valueOf(wc.getCount().charCount);
37 String wordCount = String.valueOf(wc.getCount().wordCount);
38 String lineCount = String.valueOf(wc.getCount().lineCount);
39 String spaceCount = String.valueOf(wc.getCount().spaceCount);
40 String codeCount = String.valueOf(wc.getCount().codeCount);
41 String nodeCount = String.valueOf(wc.getCount().nodeCount);
42
43 text.append("字符数:").append(charCount).append("\n");
44 text.append("单词数:").append(wordCount).append("\n");
45 text.append("行数:").append(lineCount).append("\n");
46 text.append("空行数:").append(spaceCount).append("\n");
47 text.append("代码行数:").append(codeCount).append("\n");
48 text.append("注释行数:").append(nodeCount).append("\n");
49
50 resultText.setText(text.toString());
51 }//of if
52 }
2.WordCount.java:
(ps.因为我在完成基本功能时就开始着手扩展功能,所以停用词表部分并没有单独放在扩展功能的类中。)
1),main(String[])方法中进行了修改,因为文件路径未初始化为报错(空指针异常),所以在main方法中先对三个主要文件路径变量进行了赋值,主要是通过文件后缀、-e和-o和-s三个命令操作进行判断。

//主函数
public static void main(String[] args) throws IOException
{
/*String[] str = "-s f:/test/*.c -w -o f:/result.txt".split(" ");
myMain(str);*/
//文件路径需要有默认的值,否则会报空指针异常
readPaths = new ArrayList<String>();
ifUseStopList = false;
stopListFile = "f:/default1.txt";
resultFile = "f:/default2.txt";
//先判断命令,对三个文件路径变量进行赋值
for(int i = 0; i < args.length; i++){
//通过后缀名判断目标文件
if(((args[i].endsWith(".c")) || (args[i].endsWith(".java")) || (args[i].endsWith(".js")) || (args[i].endsWith(".py")) || (args[i].endsWith(".htm")) || (args[i].endsWith(".txt"))) && !(args[i - 1].equals("-o")) && !(args[i - 1].equals("-e")))
{
readPaths.add(args[i]);
}
//判断形如wc.exe -e stopList.txt的命令,得到停用词表文件
if(args[i].equals("-e")){
ifUseStopList = true;
if((i != (args.length - 1)) && args[i+1].endsWith(".txt")){
stopListFile = args[i+1];//停用词表是命令语句-e的下一个字符串
}//of if
}//of if
//-o输出结果到指定文件
if(args[i].equals("-o")){
if(i != (args.length - 1)){ //检查-o后是否有文件路径
resultFile = args[i+1]; //-o后接的字符串即存储结果的文件
initFile(resultFile);
}//of if
}//of if
//-s递归处理目录下符合条件的文件
if(args[i].equals("-s") && args[i+1].split("\\.").length >= 1 ){
//正则表达式替换(对文件名通配符进行处理)
String wildCardChar = args[i + 1].replaceAll("\\*","\\.+").replaceAll("\\?","\\.");
System.out.println("wildCardChar:"+wildCardChar);
readPaths = ExtendFunc.getFiles(wildCardChar);
//readPaths = ExtendFunc.getFiles(args[i+1]);
}//of if
//-x程序显示图形界面
if(args[i].equals("-x")){
new MainView();
}//of if
}//of for i
command(readPaths, args);
}
2).-e停用词表的实现仍然是放在wc(String,String)中,在进行计算操作前,先读取停用词表中单词存入一个缓冲字符输入流,在对文件循环读取每行数据时进行切割,对每个单词是否存在于停用词表中作判断,以此处理单词数的计算。

1 public static void wc(String targetFile, String stopFile) throws IOException
2 {
3 count.charCount = 0;
4 count.wordCount = 0;
5 count.lineCount = 0;
6
7 String Sline = null;
8 String[] bufferL = null; //文件每行
9 String[] bufferS = null; //停用词表
10 File dir = new File(targetFile);
11 BufferedReader br = new BufferedReader(new FileReader(dir));//读取文件,生成缓冲字符输入流
12
13 //读取停用词表
14 if(ifUseStopList){
15 File dirr = new File(stopFile);
16 BufferedReader bff = new BufferedReader(new FileReader(dirr));
17 while((Sline = bff.readLine()) != null){
18 System.out.println("停用词表中的单词:" + Sline);
19 bufferS = Sline.split(",| ");
20 }//of while
21 bff.close();
22 }//of if
23 Sline = null;
24
25 //readLine()每次读取一行,br.readLine()=null代表数据读取完毕
26 while((Sline = br.readLine()) != null)
27 {
28 //System.out.println("目标文件中读取每一行内容:" + Sline);
29 bufferL = Sline.split(",| ");
30 for(int i = 0; i < bufferL.length; i++){
31 //使用停用词表时,计算单词数需处理
32 if(ifUseStopList){
33 if(!bufferL[i].equals("") && !inStop(bufferL[i], bufferS))//无停用表中单词
34 count.wordCount++;
35 }else{
36 count.wordCount++;
37 }//of if-else
38 }//of for i
39 if(bufferL.length != 0)
40 count.lineCount++;
41 count.charCount += Sline.length(); //字符个数就是每行字符串长度
42
43 }//of while
44
45 br.close();
46 }
47
48 public static boolean inStop(String str, String[] buffer){
49 int count = 0;
50 for(int i = 0; i < buffer.length; i++){
51 if(str.equals(buffer[i])){
52 count++;
53 }//of if
54 }//of for i
55
56 if(count > 0){
57 return true;
58 }else{
59 return false;
60 }//of if-else
61 }
3).[filename]可处理文件通配符,是通过一个正则表达式替换来对文件名进行处理的。
1 //-s递归处理目录下符合条件的文件
2 if(args[i].equals("-s") && args[i+1].split("\\.").length >= 1 ){
3 //正则表达式替换(对文件名通配符进行处理)
4 String wildCardChar = args[i + 1].replaceAll("\\*","\\.+").replaceAll("\\?","\\.");
5 //System.out.println("wildCardChar:"+wildCardChar);
6 readPaths = ExtendFunc.getFiles(wildCardChar);
7 }//of if
五、单元测试
1.WordCount_test类用于测试WordCount类中的方法wc(String,String)和inStop(String,String[])。
测试方法testWc():因为wc(String,String)没有返回值,它是计算文件的字符数、单词数和行数后将数据存储到WordCount类的Count对象中,所以通过读取WordCount对象的Count对象变量,判断yu'yu'qi'jie'guo'yi
测试方法testInStop():这里稍微有点复杂,因为待测试的方法参数中一个是对目标文件中内容切割后所有单词的数组,一个是停用词表中单词的数组。所以测试代码中也需要分别对目标文件和停用词表文件进行读入、切割、存储等操作,再传入待测试方法,打印出方法返回的字符串,看是否与预期结果一致。

1 public class WordCount_test {
2 @Test
3 public void testWc() throws Exception {
4 WordCount wordCount = new WordCount();
5 Class<?> clazz = wordCount.getClass();
6 Field field = clazz.getDeclaredField("count");
7 field.setAccessible(true);
8 //String string = (String) field.get(wordCount);
9 wordCount.wc("f:/a.c", "f:/stopList.txt");
10 assertEquals(2, wordCount.getCount().getLineCount());
11 assertEquals(8, wordCount.getCount().getCharCount());
12 assertEquals(5, wordCount.getCount().getWordCount());
13 }
14
15 @Test
16 public void testInStop() throws Exception{
17 WordCount wordCount = new WordCount();
18 String[] bufferL = null;
19
20 File dirr = new File("f:/stopList.txt");
21 String[] bufferS = null;
22 String Sline = null;
23 BufferedReader bff = new BufferedReader(new FileReader(dirr));
24 while((Sline = bff.readLine()) != null){
25 //System.out.println("停用词表中的单词:" + Sline);
26 bufferS = Sline.split(",| ");
27 }//of while
28
29 File dir = new File("f:/a.c");
30 BufferedReader br = new BufferedReader(new FileReader(dir));
31
32 while((Sline = br.readLine()) != null)
33 {
34 //System.out.println("目标文件中读取每一行内容:" + Sline);
35 bufferL = Sline.split(",| ");
36 for(int i = 0; i < bufferL.length; i++){
37 boolean s = wordCount.inStop(bufferL[i], bufferS);
38 System.out.println(s);
39 }
40 }
41 }
42 }
2.ExtendFunc_test类用于测试ExtendFunc类中的方法getFiles(String)和count_space_code_nodeNum(Count,String)。
测试方法getFiles(String):对待测方法传入一个文件夹的路径,通过assertEquals()方法与预期结果(即该文件夹中所有文件集合)进行比较,测试成功不报错。
测试方法count_space_code_nodeNum(Count,String):对待测方法传入一个目标文件路径,通过assertEquals()方法与预期结果(该方法计算后应该返回的空行数+代码行+注释行)进行比较,测试成功不报错。

1 public class ExtendFunc_test {
2
3 @Test
4 public void testGetFiles() {
5 ArrayList<String> filenames = ExtendFunc.getFiles("F:/simulate");
6 System.out.println(filenames);
7
8 String[] test = "F:\\simulate\\a.c".split(", ");
9 assertEquals(test, filenames.toArray());
10 }
11
12 @Test
13 public void testCount_space_code_nodeNum() throws Exception {
14 String result = new ExtendFunc().count_space_code_nodeNum(new Count(), "f:/a.c");
15 System.out.println(result);
16 String test = "空行:0" + "\r\n" + "代码行:2" + "\r\n" + "注释行:0" + "\r\n";
17 assertEquals(result, test);
18 }
19
20 }
六、项目效果
基本功能
扩展功能
高级功能
七、总结
这是我第一次接触结对编程,确实有很多问题,毕竟每个人想法、思考方式、编程习惯都不同,但也正因为每个个体都不同,所以两个人出现完全相同的问题的可能性很小,大大地提高了代码改进、测试等阶段的效率。我与队友的角色在不断交换,由观察和说话的人主导,由操键盘的人做实现。由于人的思维速度是快于输入代码的速度的,所以观看的人可以有空闲的时间做额外的思考,观察代码写的有没有问题、结构有没有问题等。
这是一次新的尝试,1+1确实很多时候大于2。