软件工程作业二
码云项目地址
https://gitee.com/holmec/PersonalProject-Java
PSP表格
PSP2.1 | 个人开发流程 | 预估耗费时间(分钟) | 实际耗费时间(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
· Estimate | 明确需求和其他相关因素,估计每个阶段的时间成本 | 20 | 15 |
Development | 开发 | 350 | 390 |
· Analysis | 需求分析 (包括学习新技术) | 40 | 35 |
· Design Spec | 生成设计文档 | 20 | 15 |
· Design Review | 设计复审 | 20 | 15 |
· Coding Standard | 代码规范 | 20 | 10 |
· Design | 具体设计 | 30 | 50 |
· Coding | 具体编码 | 120 | 180 |
· Code Review | 代码复审 | 40 | 15 |
· Test | 测试(自我测试,修改代码,提交修改) | 60 | 70 |
Reporting | 报告 | 90 | 90 |
· | 测试报告 | 40 | 40 |
· | 计算工作量 | 20 | 20 |
· | 并提出过程改进计划 | 30 | 30 |
解题思路描述
- 刚看到题目的时候先分析了一下这道题的需求:
- 统计文本的字符数
- 统计合法的单词数
- 统计合法行数
- 统计最多的10个单词及其词频
- 可见要统计首先得先从文件中获取数据,因为考虑到需要统计合法行数,所以我是想按行读取数据,而不是全部读取再来判断换行符之类的。按行读取数据后存储在ArrayList中,以便后续统计时调用,防止多次打开关闭文件造成不必要的异常。
- 统计文本字符数:遍历列表,转换成字符串再转换成字符数组,遍历字符数组,判断是否是合法字符。
- 统计合法单词数:遍历列表,转换成字符串,先判断字符串长度是否大于等于4(如果小于4,那肯定不是单词),通过split方法分割每行字符串。遍历分割后的字符串,定义一个标识符来标识是否合法,判断前四个字符串是否是英文字母,若为合法单词,添加到map里。
- 统计合法行数:遍历列表,转换成字符串,判断字符串长度是否大于0,若大于0则为合法行。
- 统计最多的10个单词及其词频:利用比较器进行排序。
设计实现过程
一、相关类的设计
- File类:读取文件数据,写入数据到文件
- Count类:统计函数的实现
- Main类:其他类和函数的调用
二、相关函数的设计
1. File类:
- readfile函数:从文件中按行读取数据并保存到ArrayList中
- writefile函数:将统计得到的结果写入文件中
2. Count类:
- CountChars函数:统计文本字符数
- CountWords函数:统计文本合法单词数
- CountLine函数:统计文本合法行数
- WordTop函数:统计最多的10个单词及其词频
代码说明
1. CountChars函数:
public int CountChars(){ //统计文本字符数
int count=0;
for(int i=0;i<line.size();i++){ //遍历文本数据
String str=line.get(i);
for(int j=0;j<str.length();j++){ //遍历每行字符串
char c=str.charAt(j); //转换成字符
if(('A'<=c&&c<='Z')||('a'<=c&&c<='z')||c=='\n'||c=='\r'||c=='\t'){
count++; //若为合法字符则计数
}
}
}
return count;
}
2. CountWords函数:
public int CountWords(){ //统计文本合法单词数
int count=0;
for(int i=0;i<line.size();i++){ //遍历文本数据
String str=line.get(i);
if(str.length()>=4){ //若该行字符串长度大于等于4,则该行可能存在合法单词
String[] words=str.split("[^a-zA-Z0-9]+"); //通过正则表达式匹配分隔符来分割字符串
for (String word:words){ //遍历分割后的字符串数组
int flag=0; //标识符标识是否为合法单词
char[] w=word.toCharArray();
for(int j=0;j<4;j++){ //遍历字符串数组的前四个字符
if(!(('A'<=w[j]&&w[j]<='Z')||('a'<=w[j]&&w[j]<='z'))){
flag=1;
break; //若不为英文字母则标识符赋值为1并跳出循环
}
}
if(flag==0){ //若为合法单词
if(!map.containsKey(word)){ //该单词第一次出现
count++; //单词数加1
map.put(word, 1); //将键、值添加进map中
}
else{
int num=map.get(word); //该单词已出现过
map.put(word, ++num); //单词词频加1
}
}
}
}
}
return count;
}
3. CountLine函数:
public int CountLine(){ //统计文本合法行数
int count=0;
for(int i=0;i<line.size();i++){ //遍历文本数据
String str=line.get(i);
if(str.length()>0){ //若该行字符串长度大于0,则计数
count++;
}
}
return count;
}
4. WordTop函数:
public ArrayList WordTop(int count){ //统计最多的10个单词及其词频
ArrayList<Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(map.entrySet()); //定义一个list来存放排序后的单词及其词频
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() { //重写比较器的排序函数
@Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
if (o1.getValue() == o2.getValue()) {
return o1.getKey().compareTo(o2.getKey());
}
return o2.getValue() - o1.getValue();
}
});
if(count<=10){ //若单词数不大于10,则直接返回排序后的list
return list;
}
else{ //若单词数大于10,则将第10位以后的单词从list中移除再返回list
for(int i=list.size();i>=10;i--){
list.remove(i);
}
return list;
}
}
单元测试
设计了10个测试点:
- 测试文件不存在
- 测试空白行
- 测试字母大小写是否区别
- 测试长度小于4的行是否合法
- 测试空白文件
- 测试纯中文文件
- 测试无合法单词
- 测试合法单词数不大于10
- 测试合法单词数大于10
- 测试特殊分隔符
测试代码
package Test;
import static org.junit.Assert.*;
import java.util.ArrayList;
import org.junit.Test;
import WordCount.Count;
import WordCount.File;
public class CountTest {
String NoExist="D:\\java练习\\PersonalProject-Java\\201621123033\\NoExist.txt";
String test1="D:\\java练习\\PersonalProject-Java\\201621123033\\test1.txt";
String Blank="D:\\java练习\\PersonalProject-Java\\201621123033\\blank.txt";
String Chinese="D:\\java练习\\PersonalProject-Java\\201621123033\\test2.txt";
String NoWord="D:\\java练习\\PersonalProject-Java\\201621123033\\noword.txt";
String more10="D:\\java练习\\PersonalProject-Java\\201621123033\\test3.txt";
@Test
public void testreadfile() {
File file=new File();
ArrayList<String> line1=new ArrayList<>();
line1=file.readfile(NoExist);
boolean test1=(line1.equals(null));
assertEquals(test1,true);
}
@Test
public void testCountChars() {
File file=new File();
ArrayList<String> line1=new ArrayList<>();
ArrayList<String> line2=new ArrayList<>();
line1=file.readfile(Chinese);
line2=file.readfile(Blank);
Count count1=new Count(line1);
Count count2=new Count(line2);
int CountChars1=count1.CountChars();
int CountChars2=count2.CountChars();
assertEquals(CountChars1,0);
assertEquals(CountChars2,0);
}
@Test
public void testCountWords() {
File file=new File();
ArrayList<String> line=new ArrayList<>();
line=file.readfile(test1);
Count count=new Count(line);
int CountWords=count.CountWords();
int CountLine=count.CountLine();
assertEquals(CountWords,5);
assertEquals(CountLine,5);
}
@Test
public void testCountLine() {
File file=new File();
ArrayList<String> line=new ArrayList<>();
line=file.readfile(test1);
Count count=new Count(line);
int CountLine=count.CountLine();
assertEquals(CountLine,5);
}
@Test
public void testWordTop() {
File file=new File();
ArrayList<String> line=new ArrayList<>();
line=file.readfile(more10);
Count count=new Count(line);
int CountWords=count.CountWords();
int WordTop=count.WordTop(CountWords).size();
assertEquals(WordTop,10);
}
}
结果出现了error
检查代码发现
1.CountWords函数没有考虑到字符数小于4的字符串的情况↓
更改后↓
2.还有WordTop函数
更改后↓
3.还有大小写也有问题
更改后↓
4.增加了文件不存在时的提示信息
再次测试
分支覆盖率
效能分析
下了JProfiler,然而并不会用...再研究一下
心得小结
以前写代码的时候都没有太注意代码规范,命名类、函数、变量等都很随意,一开始写倒还好,写到后面就很容易忘记这个类、函数、变量是什么含义。这次按照要求,命名时规范化了,思路清晰了很多,就算不是一次性写完这个程序,后面再继续写的时候也很容易就能回忆起前面的内容。其实这个程序从实现上来说不难,但是后面测试的时候发现了很多逻辑上错误的地方。以前没有使用过测试工具来进行测试,也没有特别去想一些测试点来测试,这次学习了后觉得测试真的作用还是挺大的。还有之前写代码的时候都是直接大概有个思路就开始写,没有像老师作业要求里说的那样按步骤来写程序,这次按着步骤写程序,感觉确实比较有条理性,甚至感觉写出来的代码都比以前更有条理了。