软件工程实践第二次作业——个人实战

222200315张俊腾 2024-09-09 23:12:13
这个作业属于哪个课程https://bbs.csdn.net/forums/2401_CS_SE_FZU
这个作业要求在哪里https://bbs.csdn.net/topics/619294904
这个作业的目标爬虫、理解并学会单元测试、输出2024巴黎奥运会的奖牌总榜以及每日赛程
其他参考文献

目录

  • 项目地址
  • PSP表格
  • 解题思路描述
  • 功能需求
  • 相关资料
  • 设计部分
  • 设计与实现过程
  • 项目组织结构
  • 关键函数和独到之处
  • MedalTally 类
  • Schedule 类
  • OlympicSearch 类
  • 程序性能改进
  • 日期验证优化
  • 文件读取优化
  • 单元测试展示
  • 1.测试 total 命令
  • 2.测试 schedule 合法日期
  • 3.测试非法命令
  • 4.测试 schedule 非法日期
  • 5.测试混合命令
  • 6.测试 total 和非法 schedule 日期组合
  • 7.测试空文件输入
  • 8.测试额外参数
  • 9.测试没有命令
  • 10.测试 schedule 边界日期
  • 异常处理
  • 心路历程与收获

项目地址

助教仓库链接

PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2020
• Estimate• 估计这个任务需要多少时间2020
Development开发800900
• Analysis• 需求分析 (包括学习新技术)6060
• Design Spec• 生成设计文档4030
• Design Review• 设计复审2020
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)3030
• Design• 具体设计120100
• Coding• 具体编码300240
• Code Review• 代码复审6060
• Test• 测试(自我测试,修改代码,提交修改)170360
Reporting报告120200
• Test Repor• 测试报告60120
• Size Measurement• 计算工作量3030
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划3050
合计9401120

解题思路描述

题目中涉及到多个功能:读取奖牌数据、输出赛程信息、根据用户输入进行查询,还涉及到网站数据的爬取

功能需求

  • 对网站数据爬取

  • 查询奖牌榜时,程序需要从文件中读取并输出包含排名和奖牌数量的数据。

  • 查询赛程时,程序需要根据用户输入的日期,输出相应的赛事安排,如果输入的日期超出了范围或没有相应的赛程,程序需要返回 "N/A"。

  • 处理不同的命令,并确保程序的健壮性,用户输入不合法时,程序能正确处理并输出错误信息。

相关资料

  • 查询爬虫相关资料,利用浏览器自带的插件来实现对数据的爬取
  • 涉及到文件读取和写入操作,我首先查阅了Java中BufferedReaderBufferedWriter的使用方法,以确保正确实现读取和写入操作。
  • 通过Integer.parseInt()方法来解析日期中的月份和日子。为了验证日期是否在奥运会的有效范围内,参考了Java中的字符串处理和日期比较的方法。

设计部分

  • 将程序设计为多个类来实现不同的功能,以便模块化开发。将奖牌榜查询功能、赛程查询功能以及输入解析功能分别放入不同的类中,这样既便于扩展,也便于调试和测试。 MedalTally 类来处理奖牌数据,Schedule 类来处理赛程信息,OlympicSearch 作为主类负责处理用户输入。

设计与实现过程

项目组织结构

该项目的核心分为三个类:MedalTallyScheduleOlympicSearch

  • MedalTally 类:负责读取和输出奖牌榜信息。通过读取存储在 rank.txt 文件中的数据,将奖牌榜展示到输出文件中。
  • Schedule 类:负责读取并输出某一日期的比赛赛程。通过读取日期对应的赛程文件,展示相应的比赛信息。
  • OlympicSearch 类:负责根据用户输入执行相应的操作,调用 MedalTallySchedule 的方法,输出用户所需的数据。
  • 类之间的关系:OlympicSearch 类是项目的入口,它根据输入的命令行参数,调用 MedalTallySchedule 来执行相应的功能。MedalTallySchedule 类主要是提供数据处理和文件读取的功能。

关键函数和独到之处

MedalTally 类

  • outputMedalTally(String outputFilePath)函数:读取文件,根据不同的内容(如“rank”、“gold”等关键词)判断并输出对应的数据。

  • 为了防止文件被覆盖,每次写入前会清空之前的输出文件内容。采用流式处理机制,保证了文件读取与写入的效率,并且根据关键词识别和处理不同的奖牌数据。

    public class MedalTally {
    private static final String MEDAL_TALLY_FILE = "src/data/rank.txt";
    
    public static void outputMedalTally(String outputFilePath) {
        try (BufferedReader br = new BufferedReader(new FileReader(MEDAL_TALLY_FILE));
             BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath))) {
    
            String line;
            while ((line = br.readLine()) != null) {
                if (line.startsWith("rank")) {
                    // 输出排名信息
                    bw.write(line);
                    bw.newLine();
                } else if (line.startsWith("gold") || line.startsWith("silver") || line.startsWith("bronze") || line.startsWith("total")) {
                    // 输出奖牌数据
                    bw.write(line);
                    bw.newLine();
                } else if (line.equals("-----")) {
                    // 输出分隔线
                    bw.write(line);
                    bw.newLine();
                } else {
                    // 遇到未知格式的行
                    bw.write("Error");
                    bw.newLine();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

Schedule 类

  • outputSchedule(String date, String outputFilePath):函数根据日期生成相应的文件名,读取文件后输出对应的赛程信息。如果文件不存在,则输出 N/A。写入赛程时,会在赛程信息末尾添加一条分隔线 -----,以区分不同的赛程信息。

  • 确保了在不同查询之间不会覆盖文件内容(通过追加模式 true 打开文件),并且处理了文件不存在的情况,提供了错误提示。

    public class Schedule {
    private static final String DATA_DIRECTORY = "data/";
    
    public static void outputSchedule(String date, String outputFilePath) {
        String fileName = DATA_DIRECTORY + "d" + date + ".txt";
        File scheduleFile = new File(fileName);
    
        if (!scheduleFile.exists()) {
            try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath, true))) {
                bw.write("N/A");
                bw.newLine();
                bw.write("-----");
                bw.newLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }
    
        try (BufferedReader br = new BufferedReader(new FileReader(scheduleFile));
             BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath, true))) {
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
            }
            bw.write("-----");
            bw.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

OlympicSearch 类

  • main(String[] args):该函数解析输入文件,逐行读取命令,并根据不同的命令执行不同的功能。如果是 total 命令,则调用 MedalTally,如果是 schedule 命令,则调用 Schedule 类的相关方法。
  • 将所有功能集中在一个入口点进行调度,简化了命令解析的逻辑,同时确保每个功能模块独立封装,便于后续的扩展与维护。

程序性能改进

日期验证优化

  • 问题:原日期验证逻辑仅检查了日期格式,没有对日期的实际有效性进行验证,导致输入无效日期时程序可能崩溃。
  • 改进措施:详细日期范围检查:OlympicSearch.isValidDate()方法中,我添加了对日期范围的详细检查,确保日期在奥运会的实际时间范围内,避免了由于无效日期导致的崩溃问题,确保了程序的稳定性
  • private static boolean isValidDate(String date) {
    if (!date.matches("\\d{4}")) {
        return false;
    }
    int month = Integer.parseInt(date.substring(0, 2));
    int day = Integer.parseInt(date.substring(2, 4));
    return (month == 7 && day >= 24 && day <= 31) || (month == 8 && day >= 1 && day <= 11);
    
    }

文件读取优化

  • 问题:原始程序在每次读取文件时都会打开和关闭文件,文件操作频繁
  • 改进措施:
  • 使用追加模式:在 MedalTally.outputMedalTally() 中,我将文件写入模式改为追加模式 (new FileWriter(outputFilePath, true))。
  • 缓存机制:在 Schedule.outputSchedule() 方法中,我优化了文件读取流程,将数据先缓存到内存中,然后再写入输出文件
try (BufferedReader br = new BufferedReader(new FileReader(fileName));
     BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath, true))) {
    // 读取文件内容到缓存中
    StringBuilder content = new StringBuilder();
    String line;
    while ((line = br.readLine()) != null) {
        content.append(line).append(System.lineSeparator());
    }
    // 将缓存内容写入文件
    bw.write(content.toString());
    bw.write("-----");
    bw.newLine();
}

单元测试展示

1.测试 total 命令

@Test
  public static void test1() throws IOException {
        String simulatedInput = "total\n";
        simulateInput(simulatedInput);
        System.out.println("Test 1 Passed: Total Command");
    }

2.测试 schedule 合法日期

@Test
public static void test2() throws IOException {
    String simulatedInput = "schedule 0724\n";
    simulateInput(simulatedInput);
    System.out.println("Test 2 Passed: Schedule Valid Date");
}

3.测试非法命令

@Test
public static void test3() throws IOException {
    String simulatedInput = "invalidCommand\n";
    simulateInput(simulatedInput);
    System.out.println("Test 3 Passed: Invalid Command");
}

4.测试 schedule 非法日期

@Test
public static void test4() throws IOException {
    String simulatedInput = "schedule 9999\n";
    simulateInput(simulatedInput);
    System.out.println("Test 4 Passed: Schedule Invalid Date");
}

5.测试混合命令

@Test
public static void test5() throws IOException {
    String simulatedInput = "total\nschedule 0801\nschedule 1234\n";
    simulateInput(simulatedInput);
    System.out.println("Test 5 Passed: Multiple Commands");
}

6.测试 total 和非法 schedule 日期组合

@Test
public static void test6() throws IOException {
    String simulatedInput = "total\nschedule 9999\n";
    simulateInput(simulatedInput);
    System.out.println("Test 6 Passed: Total and Invalid Schedule Commands");
}

7.测试空文件输入

@Test
public static void test7() throws IOException {
    String simulatedInput = "";
    simulateInput(simulatedInput);
    System.out.println("Test 7 Passed: Empty File Input");
}

8.测试额外参数

@Test
public static void test8() throws IOException {
    String simulatedInput = "total extra_arg\n";
    simulateInput(simulatedInput);
    System.out.println("Test 8 Passed: Extra Arguments");
}

9.测试没有命令

@Test
public static void test9() throws IOException {
    String simulatedInput = "\n";
    simulateInput(simulatedInput);
    System.out.println("Test 9 Passed: No Commands");
}

10.测试 schedule 边界日期

@Test
public static void test10() throws IOException {
    String simulatedInput = "schedule 0731\n";
    simulateInput(simulatedInput);
    System.out.println("Test 10 Passed: Schedule Edge Date");
}

img

异常处理

  • 文件读取和写入异常:在处理文件时,程序可能会遇到 IOException。通过捕获这些异常,确保程序能够处理文件操作错误。
public static void outputMedalTally(String outputFilePath) {
    try (BufferedReader br = new BufferedReader(new FileReader(MEDAL_TALLY_FILE));
         BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath, true))) {

        String line;
        while ((line = br.readLine()) != null) {
            if (line.startsWith("rank") || line.startsWith("gold") || line.startsWith("silver") ||
                line.startsWith("bronze") || line.startsWith("total") || line.equals("-----")) {
                bw.write(line);
                bw.newLine();
            } else {
                bw.write("Error");
                bw.newLine();
            }
        }
        bw.flush();
    } catch (IOException e) {
        System.err.println("File I/O error: " + e.getMessage());
        // Log error or handle error accordingly
    }
}
  • 处理文件的存在性检查和内容读取,处理了文件不存在的情况,并在文件存在的情况下处理读取和写入操作,确保程序能够优雅地处理异常情况。
public static void outputSchedule(String date, String outputFilePath) {
    String fileName = DATA_DIRECTORY + "d" + date + ".txt";
    File scheduleFile = new File(fileName);

    if (!scheduleFile.exists()) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath, true))) {
            bw.write("N/A");
            bw.newLine();
            bw.write("-----");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            System.err.println("File I/O error: " + e.getMessage());
            // Log error or handle error accordingly
        }
        return;
    }

    try (BufferedReader br = new BufferedReader(new FileReader(scheduleFile));
         BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath, true))) {

        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
        }
        bw.write("-----");
        bw.newLine();
        bw.flush();
    } catch (IOException e) {
        System.err.println("File I/O error: " + e.getMessage());
        // Log error or handle error accordingly
    }
}

心路历程与收获

  • 之前我没有太重视测试的重要性,太过麻烦。这次作业之后我发现它能帮我们发现潜在的问题,确保代码运行正确。在这次作业里,我设计了各种测试用例,像是参数不足、输入文件不存在、混合命令等。通过这些测试,我不仅验证了程序在正常情况下的表现,还确保了它在异常情况下也能稳住。这让我深刻认识到,系统性的测试真的很重要,它能有效提高代码的质量,减少发布后遇到的问题。
...全文
97 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

111

社区成员

发帖
与我相关
我的任务
社区描述
202401_CS_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_TeacherL
  • 言1837
  • 防震水泥
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧