这个作业属于哪个课程 | 构建之法-2021秋-福州大学软件工程 |
---|---|
这个作业要求在哪里 | 2021秋软工实践第一次个人编程作业 |
这个作业的目标 | 实现一个程序功能,它可以对读入的C或C++代码文件进行不同等级的关键字提取 |
学号 | 031902641 |
一、 PSP 表格
PSP Stages | 预估耗时(小时) | 完成时间(小时) |
---|---|---|
计划 | 0.5 | 0.5 |
时间预估 | 0.1 | 0.2 |
开发 | - | - |
需求分析 | 1.5 | 2 |
生成设计文档 | - | - |
设计复审 | 0.4 | 0.3 |
代码规范 | 1 | 1 |
具体设计 | 2 | 3 |
具体编码 | 15 | 20 |
代码复审 | 0.2 | 0.25 |
测试 | 1 | 2 |
报告 | 2 | 2.5 |
测试报告 | 0.5 | 0.5 |
计算工作量 | 0.1 | 0.1 |
总结与提高 | 0.5 | 1 |
总计 | 24.8 | 33.35 |
二、解题思路描述
题目要求:
实现一个程序功能,它可以对读入的C或C++代码文件进行不同等级的关键字提取。
-
基础要求:输出关键字统计信息
-
进阶要求:输出有几组switch case结构,同时输出每组对应的case个数
-
拔高要求:输出有几组if else结构
-
终极要求:输出有几组if,else if,else结构
在完成更高要求的情况前,需完成前置的要求。
解题思路
基本步骤
1. 选择用 Java 编写本次作业
2. 从键盘读入文件路径和等级要求,要使用 Scanner 类进行接收
3. 读入 C 或 C++ 代码,那么就要使用文件流进行读操作,并且把读入的内容存储到字符串中
4. 要统计关键词,要进行关键词匹配和统计,可以使用字符串的方法 equals() 进行比较
5. 统计 switch case 结构数,以及对每组对应的 case 个数进行统计
6. 统计 if-else 和 if-else if-else 结构,产生的嵌套匹配可能需要用到数据结构的知识
7. 根据输入的等级要求调用不同的方法,最终结果输出在控制台
思考&疑惑
基本步骤理清之后,整个作业的结构已经差不多搭建好
虽然结构好像很清晰,但是心中还是很多对细节的疑惑,尤其是对步骤 5 和 6,总觉得很复杂。
疑惑一:c 或者 cpp 文件的内容直接全部拼接成一个字符串会不会超过 String 能存的最大长度?分多个字符串存?String 类型数组?
解答:选用 StringBuilder 的 append() 方法,具体可参考
String、StringBuffer与StringBuilder之间区别
疑惑二:如何计算每个 switch 块里的 case 个数? 用 default 来判断当前代码块结束?那要是没 default 怎么办?
解答:咨询带佬之后决定使用括号匹配。每个 switch 后的作用域都是由一个 {}
围起来的,把 switch 后的 {
入栈,遇到 }
出栈,栈空之时代表当前 switch 作用域结束。
疑惑三:如何区别 if-else 和 if-else if-else?
解答:使用栈进行匹配和区分,将 if 和 else if 都压栈,遇到 else 出栈,如果栈顶是 if ,则为 if-else 结构;如果栈顶是 else if,则为if -else if-else 结构。
疑惑四:单元测试和性能测试是什么,使用什么工具?
解答:单元测试使用 ,具体参考Java单元测试初体验
性能测试工具用的是JProfilerIntelliJ IDEA集成JProfiler,入门教程
三、代码说明
完整代码在keyWords
1. 对文本进行预处理,去掉注释和字符串,防止关键字误判(基本思路都在注释里)
/*
* 对文本进行预处理
* */
while(line != null) {
// 去掉字符串
char quotationMark = '"';
if(line.indexOf(quotationMark) != -1) {
CharBetween charBetween = new CharBetween(quotationMark, line);
line = charBetween.getBetweenString();
}
// 去掉单行注释
String noteInline = "//";
if(line.contains(noteInline)) {
int tmp = line.indexOf(noteInline);
if(tmp == 0) {
line = bufferedReader.readLine(); // 读取下一行
continue;
}
else line = line.substring(0, tmp);
}
// 拼接字符串
fileContent.append(line);
line = bufferedReader.readLine(); // 读取下一行
}
// 去掉多行注释
fileContent = new TrimNote().trimNote(fileContent);
去除多行注释的方法
public StringBuilder trimNote (StringBuilder string) {
String noteBlockStart = "/*";
String noteBlockEnd = "*/";
while(string.indexOf("/*") != -1) {
int startIndex = string.indexOf(noteBlockStart);
int endIndex = string.indexOf(noteBlockEnd, startIndex + 1);
if(startIndex < 0 || endIndex < 0) break;
string.replace(startIndex, endIndex + 2, "");
}
return string;
}
2. 统计关键字和统计每个 switch 对应的 case 数量
2.1 流程图
2.2 代码
for (String keyWord : keyWords) {
String tmp = fileContent.toString();
count = 0;
caseCountTmp = 0;
int flag = 0;
while (tmp.contains(keyWord)) {
// 判断关键字是否包含在变量中 如果有这样的变量则删去
char pre = tmp.charAt(tmp.indexOf(keyWord) - 1);
char after = tmp.charAt(tmp.indexOf(keyWord) + keyWord.length());
if (judgeItemInString(pre) || judgeItemInString(after)) {}
else {
count++;
if (keyWord.equals("case") && flag == 0) {
String tmp2 = tmp;
for(int i = tmp2.indexOf("switch"); i < tmp2.length() && tmp2.contains("switch"); i++) {
if(tmp2.charAt(i) == '{') {
bracket.push("{");
}
if(tmp2.charAt(i) == '}') {
bracket.pop();
if(bracket.size() == 0) {
caseCount.add(caseCountTmp);
caseCountTmp = 0;
tmp2 = tmp2.substring(i + 1);
i = tmp2.indexOf("switch");
}
}
if(tmp2.contains("switch")) {
if(tmp2.charAt(i) == 'c' && tmp2.charAt(i+1) == 'a' && tmp2.charAt(i+2) == 's'
&& tmp2.charAt(i+3) == 'e') {
caseCountTmp++;
}
}
}
flag = 1;
}
}
tmp = tmp.substring(tmp.indexOf(keyWord) + keyWord.length());
}
map.put(keyWord, count);
}
3. 统计和区分 if-else 和 if-else if-else
3.1 思路
将 if 和 else if 都压栈,遇到 else 出栈,此时进行计数。如果栈顶是 if ,则为 if-else 结构;如果栈顶是 else if,则为if -else if-else 结构。
3.2 代码
public void statisticsKeyWord(int level) {
int ifElseCount = 0;
int ifElseIfElseCount = 0;
for(int i = 0; i < this.keyWordTmp.size(); i++) {
if(this.keyWordTmp.get(i).equals("1")) this.keyWord.push("1");
if(this.keyWordTmp.get(i).equals("2")) {
this.keyWord.push("2");
}
if(this.keyWordTmp.get(i).equals("3")) {
if(this.keyWord.peek().equals("2")) {
while(this.keyWord.peek().equals("2")) this.keyWord.pop();
ifElseIfElseCount++;
} else
ifElseCount++;
this.keyWord.pop();
}
}
if(level == 3) {
System.out.print("if-else num: " + ifElseCount);
}
if(level == 4) {
System.out.println("if-else num: " + ifElseCount);
System.out.println("if-elseif-else num: " + ifElseIfElseCount);
}
}
四、迭代过程
看 commit 的时间图基本可以看出迭代的过程
搭好基本的功能模块,实现输入 -> 实现文本预处理 -> 完成 level1 -> 完成 level2 -> 完成 level3 -> 完成 level4 -> 复盘、修复 bug
五、单元测试和性能测试
5.1 单元测试
5.2 性能测试
六、总结
本次作业感觉对我这个初试 Java 的人来说还是有一定难度。但最难的感觉还是解题思路,到博文写完的这一刻 if-else 和 if-else if-else 匹配仍存在bug,如果有 if 没有 else 进行匹配的话答案就是错误的。尤其是性能测试和单元测试,看了一些博文还是挺懵的,仅仅是跟着示例当了一回机器人,更不要说性能优化了。希望在接下来的作业中能继续锻炼到我使用 Java 和数据结构知识解决实际问题的能力,据说 switch 嵌套很可怕,接下来除了解决上面的 bug 以外还会思考 switch 嵌套的问题,继续维护这份代码。