2021秋软工实践第一次个人编程作业

这个作业属于哪个课程 构建之法-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. 根据输入的等级要求调用不同的方法,最终结果输出在控制台

思考&疑惑

基本步骤理清之后,整个作业的结构已经差不多搭建好

2021秋软工实践第一次个人编程作业

虽然结构好像很清晰,但是心中还是很多对细节的疑惑,尤其是对步骤 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 流程图

2021秋软工实践第一次个人编程作业

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

2021秋软工实践第一次个人编程作业

五、单元测试和性能测试

5.1 单元测试

2021秋软工实践第一次个人编程作业

5.2 性能测试

2021秋软工实践第一次个人编程作业

六、总结

本次作业感觉对我这个初试 Java 的人来说还是有一定难度。但最难的感觉还是解题思路,到博文写完的这一刻 if-else 和 if-else if-else 匹配仍存在bug,如果有 if 没有 else 进行匹配的话答案就是错误的。尤其是性能测试和单元测试,看了一些博文还是挺懵的,仅仅是跟着示例当了一回机器人,更不要说性能优化了。希望在接下来的作业中能继续锻炼到我使用 Java 和数据结构知识解决实际问题的能力,据说 switch 嵌套很可怕,接下来除了解决上面的 bug 以外还会思考 switch 嵌套的问题,继续维护这份代码。

上一篇:${param.name}和${name}的区别


下一篇:[算法笔记] 割点与割边