盒子
盒子
文章目录
  1. 项目地址
  2. 已完成需求与分析
    1. 需求分析
    2. 注意
    3. 截图效果
    4. 参数违规
    5. 正常
    6. 采用 stop list文件
  3. PSP表格
  4. 解题思路
  5. 类图
  6. 程序设计与代码说明
  7. 测试过程
    1. 思路
    2. 侧重点
    3. 文件用例
    4. 执行用例
  8. 其他

WordCount 项目总结

项目地址

https://github.com/hqweay/WordCount

已完成需求与分析

需求分析

基本功能:统计文件的字符数,单词数,行数。

扩展功能:统计代码文件的注释行数,空行数,代码行数,使用忽略词组统计单词数

将统计结果按照一定格式写入输出文件。

打包为可执行文件 wc.exe

执行方式:wc.exe 参数

注意

  • 空格,水平制表符,换行符,均算字符。
  • 由空格或逗号分割开的都视为单词,且不做单词的有效性校验,例如:thi#,that视为用- - 逗号隔开的2个单词。
  • -c, -w, -l参数可以共用同一个输入文件,形如:wc.exe –w –c file.c 。
  • -o 必须与文件名同时使用,且输出文件必须紧跟在-o参数后面,不允许单独使用-o参数。

截图效果

参数违规

bugs

正常

normal

采用 stop list文件

stoplist

PSP表格

PSP2.1 PSP阶段 预估耗时(分钟) 实际耗时(分钟)
· Planning · 计划 30 20
· Estimate · 估计这个任务需要多少时间 20 10
· Development · 开发 24*60 10*60
· Analysis · 需求分析 (包括学习新技术) 50 40
· Design Spec · 生成设计文档 10 10
· Design Review · 设计复审 (和同事审核设计文档) 10 0
· Coding · 代码规范 (为目前的开发制定合适的规范) 10 10
· Code Review · 具体设计 20 30
· Test · 具体编码 24*60 15*60
· Reporting · 代码复审 2*60 50
· Test Report · 报告 2*60 1.5*60
· Size Measurement · 测试报告 30 10
· Postmortem & Process · 计算工作量 10 15
· Improvement Plan · 事后总结, 并提出过程改进计划 10 20
· 合计 3310 1825

解题思路

统计字符与行数,第一感觉需要用到文件的输入输出知识,然后按行读取,按字符来进行一些判断,看是否满足需求。

类图

uml

程序设计与代码说明

在正式写代码之前,我先简单地组织了一下项目。

如类图所示,首先我考虑到需求分析时的基本功能,扩展功能,高级功能这三块,我注意到每一个模块要求的功能并不多,所以打算按模块建立功能类,这样扩展就只需要新建类,当我完成基本功能去实现高级功能时,就不需要修改完成的代码。

比如我在 BasicFeature.class 中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 基本功能
* 统计 字符,单词,行数
*/
public class BasicFeature {
Util util = new Util();

/**
* @param file
* @return string of countChar
* @throws IOException
*/
public String countChar(File file) throws IOException {

}

/**
* @param file
* @return string of countWord
* @throws IOException
*/
public String countWord(File file) throws IOException {

}

/**
* @param file
* @return string of coutLine
* @throws IOException
*/
public String countLine(File file) throws IOException {

}
}

对应于需求分析中基本功能的三个功能。

其次,需求分析中涉及到对某个名词的定义,比如以什么来分隔单词,什么是空行。于是我把这类定义放在一个配置类 Config.class 中,可以方便地进行修改。

比如需求中提到:

由空格或逗号分割开的都视为单词,且不做单词的有效性校验

于是我在 Config.class 写了

1
2
3
4
5
6
//统计单词的分隔符
//按需求,只需要区分 逗号,空格
//这样定义便于扩展
public final static String[] supportSplits = {
",", " ", "."
};

在编码过程中,我注意到在每个小功能中,都会涉及到文件的读取操作,于是我建立了一个配置类 Util.class ,抽象了一个用的最多的文件操作方法。(在之后,我还在此类中放了一些其他方法。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 把获取输入流独立出来
*
* @param file
* @return bufferedReader
*/
public BufferedReader getBufferedReader(File file) {
InputStreamReader inputStreamReader = null;
try {
inputStreamReader = new InputStreamReader(new FileInputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
//可能出错勒
}
return new BufferedReader(inputStreamReader);
}

这样,就把一个经常用到的流程抽象为了一个方法,可以方便地调用。

当我完成了各个模块,就准备将整个流程整合起来。

按需求分析所说,最终我们需要达到的是通过命令行窗口执行 wc.exe 参数列表

这个流程是一定的,所以我建立了一个操作类 Operator.class ,通过调用功能类的方法,达到了封装整个操作的效果。

根据用户的参数,我们需要判断出要执行哪些功能,所以我在此类中添加了如下 5 个属性。在解析用户输入的参数时,就解析出哪些操作是需要执行的。

然后在 excuteCountOperatorToOutput() 方法中,就开始通过调用功能类的方法执行相应功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Operator {
private boolean countChar = false;
private boolean countWord = false;
private boolean countWordByStopList = false;
private boolean countLine = false;
private boolean countCodeInfo = false;

/**
* 执行一系列的统计操作
*
* @param file
* @param stopListFile
* @return
*/
public ArrayList<String> excuteCountOperatorToOutput(File file, File stopListFile) {
ArrayList<String> outputTexts = new ArrayList<String>();
//功能类
BasicFeature basicFeature = new BasicFeature();
EXFeature exFeature = new EXFeature();
try {
if (countChar) {
outputTexts.add(basicFeature.countChar(file));
}
if (countWord) {
outputTexts.add(basicFeature.countWord(file));
}
if (countWordByStopList) {
outputTexts.add(exFeature.countWordByStopList(file, stopListFile));
}
if (countLine) {
outputTexts.add(basicFeature.countLine(file));
}
if (countCodeInfo) {
outputTexts.add(exFeature.countCodeInfo(file));
}
} catch (IOException e) {
e.printStackTrace();
}
return outputTexts;
}
}

整个流程有了,就可以进行阶段性的测试。我建立了一个测试类 Test.class,不过在实际开发过程,我都是直接在主函数中测试的…这一点做的不是很好。

最后,需要和用户交互,我建立了 Welcome.class 类,这个类主要用于解析用户输入的参数。

因为涉及到与用户的交互,我创建了一个内部类 paramException.class ,用于相关的错误提示。

1
2
3
4
5
6
7
8
9
10
11
class paramException {
private ArrayList<String> errors = new ArrayList<String>();

public ArrayList<String> getErrors() {
return errors;
}

public void addError(String error) {
this.errors.add(error);
}
}

解析用户的参数,需要解决这几个问题:

  1. 无意义的输入
  2. 参数的顺序(比如 -o 后面跟输出文件)
  3. 对用户进行提示

完成后需要封装为 exe 可执行文件,使用的工具是 exe4j ,用到了张波同学在 有关exe4j打包jar包控制台没有输出的补充 的内容。

然后涉及到了参数的解析,在命令行执行 wc.exe -a -p,’-a’,’-p’ 都会作为 args[] 参数传进来。

下面是大体流程,其中包含一些小方法,不赘叙。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public boolean paramsExecute(String[] args) {
for (int i = 0; i < args.length; i++) {
String param = args[i];
switch (param) {
case "-c":
countChar();
break;
case "-w":
countWord(args);
break;
case "-l":
countLine();
break;
case "-a":
countCodeInfo();
break;
case "-o":
if (i < args.length - 1 && args[i + 1].contains(".")) {
//输出文件 ok
} else {
String error = "没找到输出文件 " + args[i];
paramException.addError(error);
return false;
}
break;
case "-e":
if (i < args.length - 1 && args[i + 1].contains(".")) {
// count word by stop list
operator.setCountWordByStopList(true);
operator.setCountWord(false);
} else {
String error = "参数 " + args[i] + " 后没找到 stop list 文件 ";
paramException.addError(error);
return false;
}
break;
default:
//文件名或者报错
filePaths.add(param);
break;
}
}
return true;
}

测试过程

思路

整个开发流程无时无刻不在测试,系统的测试在基本功能完成后。

因为我的开发流程是:各个小功能,整体,打包 exe 。

所以测试流程也随之是先通过各个小功能,再整体流程测试,再打包为 exe 可执行文件,在命令行窗口执行。

最后一步的测试效果如 截图效果 所示。

如类图所示,我分了 BasicFeature.class,EXFeature.class 两个功能类,在这两个功能类里我通过创建 Main 方法实现了测试方法的效果,达到模块测试。

我还建立了一个 Test.class 作为后面的整体测试,不过没怎么用上,因为代码比较简单,直接在 main 函数执行比较方便。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Welcome welcome = new Welcome();

String[] argsd = {
"-c",
"-w",
"-l",
"-a",
"text.txt",
"-e",
"stopList.txt",
"-o",
"output.txt"
};
//解析参数
welcome.start(argsd);

侧重点

因为需求分析中没做非常严格的规定,所以每个小功能还是比较好实现的。比如单词的分隔仅仅要求以空格,逗号来分隔,不做检验。一般只需要按行读取再做匹配。

我在输入文件中写了一些字符做测试。

还有就是与用户的交互,用户执行操作的时候什么参数都可能传进来,所以要多考虑。

文件用例

text.txt

1
2
3
4
5
6
7
8
9
//public void 
/**
*
**/
ddddd
ssss File is a
dd ss

ssss

/**/ 多行注释并不会被统计为注释。

执行用例

  1. wc.exe -c -w -l -a text.txt -e stopList.txt -o output.txt
  2. wc.exe -xx
  3. wc.exe -c text.txt
  4. wc.exe -e stopList.txt
  5. wc.exe text.txt -o oup.txt
  6. wc.exe fffffffffffffffffffffffffffffffffffffffffffff&&*$##%&

在 5 这个测试中,代码执行会通过,没有指定参数,但是含有输入输出文件,程序就默认不做操作。(但是还是会进入上面提到的 Operator.class 操作类的方法中)

其他

gitee 管理项目,需要 git 相关知识。基本的配置见这篇文章

从Git合作管理gitee项目浅谈

因为管理不当,我在 push 代码的时候遇到了本地分支和远程分支冲突的问题,一直没弄好,最后强制上传,git push origin master -f 把我之前的 commits 覆盖完了…