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

222200115吴俊斌 2024-09-10 23:24:42
这个作业属于哪个课程软件工程实践-2024学年-W班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标收集数据、编写代码、单元测试、性能改进、异常处理、撰写博客
其他参考文献单元测试与回归测试源代码管理工程师的能力评估和发展

目录

一、项目地址

CodeArts地址

二、PSP表格

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

三、解题思路描述

3.1 需要的数据文件

在官方网站打开开发者工具,找到以下两份可格式化的json文件。

  1. medals.json
  2. medalists.json
    该网站(JSON在线)将其格式化。

解析后替换为更利于文件流读取与分析的txt文件。
4. Nationtotal.txt 用于实现功能1
5. ResultDate.txt 用于实现功能2
6. NationInfo.txt 用于实现功能3

3.2 需要的功能函数

  1. total 则会输出所有国家得奖情况。
rank1:string
gold:number
silver:number
bronze:number
total:number
-----
  1. result 0810(date) 则会输出8月10日的所有人员获奖情况。
winner:string
nationality:string 
gold:number
silver:number
bronze:number
total:number
-----
  1. PK 中国(countryname) 美利坚合众国(countryname) 霹雳舞(eventDescription) 则会输出2024年巴黎奥运会霹雳舞项目中国和美利坚合众国的奖牌情况。
stringVSstring string
gold:number/number
silver:number/number
bronze:number/number
total:number/number
-----

4.出现无法识别的指令,则输出Error;
result后的日期不在限定时间内(7月27日-8月11日),则输出N/A。

3.3 需要的I/O函数

  1. 通过命令行指令传入文件。
Java -jar OlympicSearch.jar input.txt output.txt
  1. 读取input中的指令:使用Files.readAllLines逐行遍历input.txt的指令,并存储到CommandList中。
  2. 根据指令List获取对应数据:首先获取CommandList,然后遍历并辨别指令,根据指令获取对应的数据,调用相应的功能函数。
  3. 将数据写入output文件:获得对应数据后通过BufferedWriter写入文件中。

四、接口设计与实现过程

src
 │  OlympicSearch.java     # 主程序所在模块,读命令,调用函数
        public static List<String> readCommands(String input):读取文件中的命令
        public static void main(String[] args) : 入口函数,调用不同的功能函数
        
 │  NationTotal.java     # 响应total指令
        public static List<CountryMedal> readMedalData(String filePath) :获取NationTotal文件中的国家奖牌数据,转换成对象
        public static List<CountryMedal> sortByMedals(List<CountryMedal> countries)  :按奖牌数量逐级降序排列
        public static void writeOutput(List<CountryMedal> countries, BufferedWriter writer) : 获得最终的数据并写入output
        
│     ResultDate.java     # 响应result date指令
        public static Map<String, List<WinnerMedal>>readAllResults(String filePath):// 读取ResultDate文件,获得所有对象,按日期分组
        public static void ResultCommand(String date, String ResultDateFilePath, BufferedWriter writer) : // 处理主函数传来的ResultDate指令
        public static List<WinnerMedal> sortByMedals(List<WinnerMedal> winners):按奖牌数量逐级降序排列
        public static void writeOutput(List<WinnerMedal> winners, BufferedWriter writer): 获得最终的数据并写入output
        
│   NationPK.java     # 响应pk指令
        public static Map<String, Map<String, int[]>> readCountryPKData(String filePath): // 读取 NationInfo 文件,返回 <比赛项目,<国家,奖牌数[]>> 的数据结构,便于储存与比较
        public static void CountryPK(String country1, String country2, String project, String filePath, BufferedWriter writer) : 获取并PK两个国家在某个项目上的奖牌数,写入output

 ├─entity
         CountryMedal .java              # 国家-奖牌类    
         WinnerMedal.java           # 运动员-奖牌类

五、关键代码展示

5.1 读取并辨识指令,流程的入口函数

        try {
            // 读取指令
            List<String> commands = readCommands(inputFilePath);

            // 创建BufferedWriter,写入的内容将覆盖原output.txt
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath,false))) {
                for (String command : commands) {
                    // 去除单行首尾空格
                    command = command.trim();

                    // 若指令可以满足NationTotal的1个参数
                    if ("total".equalsIgnoreCase(command)) {
                        //调用NationTotal
                        List<CountryMedal> countries = NationTotal.readMedalData(NationTotalFilePath);
                        List<CountryMedal> sortedCountries = NationTotal.sortByMedals(countries);
                        NationTotal.writeOutput(sortedCountries, writer);

                    }

                    // 若指令可以满足ResultDate的2个参数
                    else if (command.startsWith("result")) {
                        //根据空格数拆分part
                        String[] parts = command.split("\\s+");
                        if (parts.length >= 2) {
                            String date = parts[1].trim();
                            // 调用ResultDate
                            ResultDate.processResultCommand(date, ResultDateFilePath, writer);
                            // 处理错误参数
                        } else {
                            writer.write("Error\n");
                            writer.write("-----\n");
                        }

                        // 若指令可以满足NationPK的4个参数
                    }  else if (command.startsWith("PK")) {
                        //根据空格数拆分part
                        String[] parts = command.split("\\s+");
                        if (parts.length >= 4) {
                            String country1 = parts[1].trim();
                            String country2 = parts[2].trim();
                            String project = parts[3].trim();
                            NationPK.processComparison(country1, country2, project, countryVsFilePath, writer);
                        } else {
                            writer.write("Error\n");
                            writer.write("-----\n");
                        }
                    // 错误指令
                    } else {
                        writer.write("Error\n");
                        writer.write("-----\n");
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

5.2 处理ResultDate指令

    public static void processResultCommand(String date, String ResultDateFilePath, BufferedWriter writer) {
        try {
            // 读取所有对象及其对应日期
            Map<String, List<WinnerMedal>> allResults = readAllResults(ResultDateFilePath);

            // 转换日期格式,如 "0809" => "8.9"
            String month = date.substring(0, 2).replaceFirst("^0+", "");
            String day = date.substring(2, 4).replaceFirst("^0+", "");
            String formattedDate = month + "." + day;

            // 获取指定日期的对象
            if (allResults.containsKey(formattedDate)) {
                List<WinnerMedal> winners = allResults.get(formattedDate);

                // 根据金牌、银牌、铜牌排序
                List<WinnerMedal> sortedWinners = sortByMedals(winners);

                // 输出结果到 BufferedWriter
                writeOutput(sortedWinners, writer);
            } else {
                // writer.write("未找到指定日期的比赛结果:" + formattedDate + "\n");
                writer.write("N/A" +  "\n");
                writer.write("-----\n");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 按照运动员获得的奖牌数量,逐级降序排列
    public static List<WinnerMedal> sortByMedals(List<WinnerMedal> winners) {
        return winners.stream()
                .sorted(Comparator.comparingInt((WinnerMedal  w) -> w.gold)
                        .thenComparingInt(w -> w.silver)
                        .thenComparingInt(w -> w.bronze).reversed())
                .collect(Collectors.toList());
    }

5.3 处理CountryPK指令

   public static void CountryPK(String country1, String country2, String project, String NationInfoFilePath, BufferedWriter writer) {
        try {
            // 读取 NationInfo 文件
            Map<String, Map<String, int[]>> NationInfoData = readCountryPKData(NationInfoFilePath);

            if (NationInfoData.containsKey(project)) {      //包含指令要求的项目
                Map<String, int[]> countryData = NationInfoData.get(project);

                // 在该项目中寻找这两个国家的奖牌数据,如果没有则默认奖牌数为0
                int[] country1Data = countryData.getOrDefault(country1, new int[]{0, 0, 0, 0});
                int[] country2Data = countryData.getOrDefault(country2, new int[]{0, 0, 0, 0});

                // 将PK结果写入 BufferedWriter
                writer.write(country1 + " VS " + country2 + " " + project + "\n");
                writer.write("gold: " + country1Data[0] + " / " + country2Data[0] + "\n");
                writer.write("silver: " + country1Data[1] + " / " + country2Data[1] + "\n");
                writer.write("bronze: " + country1Data[2] + " / " + country2Data[2] + "\n");
                writer.write("total: " + country1Data[3] + " / " + country2Data[3] + "\n");
                writer.write("-----\n");
            } else {
                writer.write("N/A" + "\n");
                writer.write("-----\n");
            }

        } catch (IOException e) {
            try {
                //writer.write("Error occurred during comparison\n");
                writer.write("Error\n");
                writer.write("-----\n");
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }

六、性能改进

  1. 使用StringBuffer 处理字符串

    使用StringBuffer拼接字符串不会产生新对象,减少频繁创建对象,配合BufferedWriter的缓冲功能,可以提高读写效率。

  2. 使用HashMap缓存数据

    当读取一组关系对象时可存入HashMap中,可以减少读取数据文件的时间。

  3. 将指令用List存储

    将input中的内容一次性读取出来用List<String>存储,可以减少读取指令文件的时间。

七、单元测试

以下包含10+各测试用例。

功能1测试(input-output)

功能1测试imput

功能1测试output

功能2测试(input-output)

功能2input


功能2output

功能3测试(input-output)

功能3input


功能3output

八、异常处理

读取指令可能存在异常,因此使用try-catch语句来包含文件读取语句。

尝试读取文件

当指令参数异常时,应作业要求,不选择try报错处理,而是在output文件中打印“Error”字样。

处理错误参数

当日期出现非法输入时,应作业要求,不选择try报错处理,而是在output文件中打印“N/A”字样。

处理错误日期

PK功能可能出现的异常状态很多,根据类型选用"Error"或"N/A"或e.printStackTrace()反馈异常。

PK异常

九、心得体会

这次实践涉及的技术多且杂,包括网站数据的爬取与解析,文件格式化与读写,数据的存储与处理,异常处理,单元测试,Git的使用等等,会遇到很多很多的感到一时无语,束手无策的困难。但在同学的帮助与自己的查找下解决了大部分的困难。经过这次实践,我更深刻地理解到了软件工程领域的浩瀚无边,大一大二时所忙碌的编写代码环节,在一个工程中所耗费的时间与精力竟然也并非重点。查找与学习永无止境。

...全文
103 1 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
助教袁金凡 助教 2024-10-16
  • 打赏
  • 举报
回复

你的博客文档标题的跳转地址似乎错了

239

社区成员

发帖
与我相关
我的任务
社区管理员
  • FZU_SE_teacherW
  • 助教赖晋松
  • D's Honey
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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