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

222000330林诗怡 学生 2023-03-03 21:46:43
这个作业属于哪个课程2023年福大-软件工程;软件工程实践-W班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标 1、爬取澳网的选手信息和比赛结果数据
2、编写控制台程序根据输入文件输出相应的文件
3、性能分析和优化
4、使用单元测试
5、异常处理分析
6、撰写博客
其他参考文献CSDN、bilibili、bing搜索

目录

  • 一、Gitcode项目地址
  • 二、PSP表格
  • 三、解题思路描述
  • 1.流程分析
  • 2.数据获取
  • 3.解析数据
  • 4.输入文件解析
  • 5.写入输出文件
  • 6.单元测试、性能优化
  • 7.生成控制台程序
  • 四、接口设计和实现过程
  • 1.类设计
  • 2.函数设计
  • 3.实现过程
  • 五、关键代码展示
  • input_txt_analyse()
  • content_analyse()
  • players_analyse()
  • result_analyse(String date)
  • output_txt_operate(List input_content,String output_name)
  • 六、性能改进
  • 1.数据结构优化
  • 2.代码优化
  • 3.性能展示
  • 七、单元测试
  • 编写测试函数
  • 测试结果
  • 八、异常处理
  • 1.文件读取异常
  • 2.input.txt打开异常
  • 3.控制台传入参数个数异常
  • 九、心得体会

一、Gitcode项目地址

gitCode项目地址

二、PSP表格

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

三、解题思路描述

1.流程分析

img

2.数据获取

1月23-29日数据获取流程:
进入result

进入result



打开F12找到result文件

找到api



将获取的内容复制到在线json格式转换网站,保存转换后的文件到本地

在这里插入图片描述


在这里插入图片描述

3.解析数据

使用第三方的Gson库将Json文件反序列化,将需要用的数据封装到相应的JPlayers类和JResult类,再从对象中获取数据。

4.输入文件解析

按行读取输入文件,判断指令并保存到ArrayList集合中,供后续按照指令解析json。

5.写入输出文件

调用解析指令的函数,返回对应的内容。用StringBuilder数组保存,并判断之前是否解析过,用BufferWriter写入输出文件。

6.单元测试、性能优化

使用Junit自动生成测试函数,自行设计测试用例进行测试。
删除空行,和多余的大括号,将StringBuffer改为速度更快的StringBuilder。

7.生成控制台程序

将测试和优化完成的项目打包成Jar包,构建后将jar文件放在src同级目录下,检查输出文件是否正确

四、接口设计和实现过程

1.类设计

//主类,包括main函数,传入实参input.txt和outp.txt
public class AOSearch

//核心类,包括文件解析和指令解析的函数
public class Lib

//选手类,类成员名称与所需要的Json属性一一对应
public class JPlayers

//结果类,类成员名称与所需要的Json属性一一对应
public class JResult

2.函数设计

//功能:初始化用来存放选手信息和比赛结果的String列表
public Lib()//功能:打开input.txt,按行读取文件
//参数:输入文件的路径
//返回:存放每行内容的List<String>
public static List<String> input_txt_analyse(String path)//功能:解析input.txt的行内容
//参数:行内容
//返回:规定的指令类型
public static String content_analyse(String content)//功能:解析players.json
//返回:存放players内容的字符串
public static String players_analyse()//功能:解析result.json
//参数:日期
//返回:存放指定日期的比赛结果的字符串
public static String result_analyse(String date)//功能:返回team_id对应的player名字并判断人数
//参数:team_id和result类
//返回:人名简写
public static List<String> get_name(String team_id, JResult result)//功能:将指令结果存入output.txt
//参数:存放对应指令的List集合,output.txt文档名字
public static void output_txt_operate(List<String> input_content,String output_name)

3.实现过程

关键函数流程图

在这里插入图片描述

五、关键代码展示

input_txt_analyse()

关键:
1.跳过空行;2.使用List存储。

public static List<String> input_txt_analyse(String path) throws IOException {
        List<String>  input_content_list = new ArrayList();
        //判断文件是否为空
        File file = new File(path);
        if (!file.exists()) {
            System.out.println("no file");
            System.exit(0);
        }
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(path),"UTF-8"));
        //按行读取
        String file_content = bufferedReader.readLine();
        //判断空行
        while (file_content!=null) {
            if(!file_content.equals(""))//如果是空行不进入内容判断
                input_content_list.add(content_analyse(file_content)); //把每个指令的判断加入input_content_list中
            //空行直接读取下一行
            file_content = bufferedReader.readLine();
        }
        bufferedReader.close();
//        System.out.println(input_content_list);
        return input_content_list; //返回input.txt里面的输入指令所有内容
    }

content_analyse()

关键:
1.删去头尾空格;2.删去中间空格;3.将Q1-4转换成对应日期返回;4.区分result指令后的字符串是否为纯数字。 返回指令有四种:
1.“players”:输出所有选手的信息;
2.“四位数字”:表示日期,输出相应日期的比赛结果;
3.“error”:指令有误;
4.“N/A”:指令正确,日期有误。

public static String content_analyse(String content) {
        //首先对内容进行规范化,删去头尾空格
        content = content.trim();
        //判断players
        if (content.length() == 7) {
            if (content.equals("players")) return "players";
            else return "error";
        } else if (content.length() == 6) {//只有指令没有日期
            if (content.equals("result")) return "N/A";
        }else if (content.substring(0, 7).equals("result ")) {
                if (content.length() >= 9) {
                    //删去中间空格
                    content = content.replaceAll(" +", "");
                    if (content.length() == 10) {//四位数字
                        //日期在比赛期间
                        String temp = content.substring(6, 10);//截取日期
                        //字符串转整型
                        if (temp.matches("[0-9]+") == false) {
                            return "N/A";
                        } else {
                            int date = Integer.valueOf(temp);
                            if (116 <= date && date <= 129) return temp;
                            else return "N/A";
                        }
                    } else if (content.length() == 8) {//Q1-4
                        String temp = content.substring(6, 8);
                        if (temp.equals("Q1")) {
                            return "0109";
                        } else if (temp.equals("Q2")) {
                            return "0110";
                        } else if (temp.equals("Q3")) {
                            return "0111";
                        } else if (temp.equals("Q4")) {
                            return "0112";
                        } else return "N/A";

                    } else return "N/A";//日期在比赛时间外
                } else return "N/A";//result正确,指令短了
            } else return "error";//result指令错误
        return "error";
    }

players_analyse()

关键: 使用StringBuilder存储所有数据

public static String players_analyse() {

        //解析players.json
        Gson gson = new Gson();
        try {
//            System.out.println("0");
            JsonReader reader = new JsonReader(new FileReader("src/data/players.json"));
            //从json获取数据对应到Jplayers类
            JPlayers players = gson.fromJson(reader,JPlayers.class);
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < players.players.size(); i++) {//将相应字段写入stringbuffer
                //存入full_name
                stringBuilder.append("full_name:");
                stringBuilder.append(players.players.get(i).full_name);
                stringBuilder.append("\n");
                //存入性别gender
                stringBuilder.append("gender:");
                if (players.players.get(i).gender.equals("M"))
                    stringBuilder.append("male");
                else if (players.players.get(i).gender.equals("F"))
                    stringBuilder.append("female");
                stringBuilder.append("\n");
                //存入国籍nationality
                stringBuilder.append("nationality:");
                stringBuilder.append(players.players.get(i).nationality.name);
                stringBuilder.append("\n");
                stringBuilder.append("-----");
                stringBuilder.append("\n");
            }
            final_content.add(0,stringBuilder.toString());
//            System.out.println(stringBuilder);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
//        System.out.println(final_content.get(0));
        return final_content.get(0);
    }

result_analyse(String date)

关键:
1.获取matches_status的code属性判断是否有选手弃赛;2.使用两个List来存放分数,交替输出;3.输出分数的时候最后一个比分后不加“|”。

    public static String result_analyse(String date) {
        Gson gson = new Gson();
        String file = "src/data/results/" + date + ".json";
        String team_id = null;
        try {
            JsonReader reader = new JsonReader(new FileReader(file));
            JResult result = gson.fromJson(reader,JResult.class);
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < result.matches.size(); i++)
                if (result.matches.get(i).match_status.code.equals("W")) {
                    stringBuilder.append("W/O");
                    stringBuilder.append("\n");
                    stringBuilder.append("-----");
                    stringBuilder.append("\n");
                } else {
                    stringBuilder.append("time:");
                    stringBuilder.append(result.matches.get(i).actual_start_time);
                    stringBuilder.append("\n");
                    stringBuilder.append("winner:");
                    List<String> score_team1 = new ArrayList<>();//存放第一个队的分数
                    List<String> score_team2 = new ArrayList<>();//存放第二个队的分数
                    for (int j = 0; j < result.matches.get(i).teams.size(); j++) {
                        team_id = result.matches.get(i).teams.get(j).team_id;
                        //判断这个组是否胜利
                        if (result.matches.get(i).teams.get(j).status != null) {
                            List<String> players_name = get_name(team_id, result);
                            //若人数>1为双人赛,否则为单人赛
                            if (players_name.size() == 1)
                                stringBuilder.append(players_name.get(0));
                            else stringBuilder.append(players_name.get(0) + " & " + players_name.get(1));
                            stringBuilder.append("\n");
                            //输出比分
                            stringBuilder.append("score:");
                        }
                        //将第二组的score存入score_team2
                        //输出分数,数组长度等于轮数
                        //team始终只有两组,j的值为0或1,0表示第一组,1表示第二组
                        //将第一组的score存入score_team1
                        if (j == 0) for (int l = 0; l < result.matches.get(i).teams.get(j).score.size(); l++)
                            score_team1.add(result.matches.get(i).teams.get(j).score.get(l).game);
                        else for (int l = 0; l < result.matches.get(i).teams.get(j).score.size(); l++)
                            score_team2.add(result.matches.get(i).teams.get(j).score.get(l).game);
                    }
                    for (int r = 0; r < score_team1.size(); r++)
                        if (r == score_team1.size() - 1)//最后一个比分后没有分隔符“|”
                            stringBuilder.append(score_team1.get(r) + ":" + score_team2.get(r));
                        else stringBuilder.append(score_team1.get(r) + ":" + score_team2.get(r) + " | ");
                    stringBuilder.append("\n");
                    stringBuilder.append("-----");
                    stringBuilder.append("\n");
                }
            return String.valueOf(stringBuilder);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

output_txt_operate(List input_content,String output_name)

关键:
1.使用is_players和is_date[]来标记是否是第一次解析json,避免重复解析;2.关闭文件流。

public static void output_txt_operate(List<String> input_content,String output_name) throws IOException {
        File file = new File(output_name);
        if(!file.exists()) file.createNewFile();//如果不存在,就新建文件
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output_name),"UTF-8"));
        boolean is_players = false;//标记是否曾经解析players
        boolean[] is_date = new boolean[25];//标记是否解析date
        for (int i = 0; i < is_date.length; i++) {//初始化
            is_date[i] = false;
        }
        for (String content:input_content) {
            if (content.equals("players")) {
                if (!is_players){//player还未解析过//写入数组
                    players_analyse();
                }
                is_players = true;//从string写入文件
                bw.write(final_content.get(0).toString());
            } else if (content.equals("error")) bw.write("Error" + "\n" + "-----" + "\n");
            else if (content.equals("N/A")) bw.write("N/A" + "\n" + "-----" + "\n");
            else {
                int date = Integer.valueOf(content);//日期字符变为整型
                date = date % 109 + 1;
                if (!is_date[date]) {//第一次读取该日期内容
                    final_content.add(date,result_analyse(content));
                    is_date[date] = true;
                }
                bw.write(String.valueOf(final_content.get(date)));
            }
        }
        bw.close();
    }

六、性能改进

1.数据结构优化

因为代码中大量使用到StringBuffer,经搜索发现StringBuilder更加适合。原因是,StringBuffer在多线程下更安全但是速度稍慢,StringBuilder适用于单线程且速度快,因为本次作业只使用了单线程,所以将StringBuffer改为StringBuilder。

在这里插入图片描述

2.代码优化

将代码的细节进行优化,如:字符串变量和字符串常量equals的时候将字符串常量写在前面;增加代码可读性。

3.性能展示

优化后的程序运行一百万指令的性能快照如图,虽然时间提升了,但是空间占用很大,暂时还没想出更好的办法降低空间复杂度,希望下次作业能优化这个问题。

在这里插入图片描述


在这里插入图片描述

七、单元测试

对核心的Lib的函数进行测试,这里使用了Junit库来生成test函数。

编写测试函数

在这里插入图片描述


在这里插入图片描述



测试结果

返回结果符合预期

在这里插入图片描述




Lib的覆盖率也达到标准

在这里插入图片描述

八、异常处理

1.文件读取异常

try {
            lib.output_txt_operate(lib.input_txt_analyse(args[0]),args[1]);
        } catch (IOException e) {
            e.printStackTrace();
        }

2.input.txt打开异常

public static List<String> input_txt_analyse(String path) throws IOException {
        List<String>  input_content_list = new ArrayList();
        //判断文件是否为空
        File file = new File(path);
        if (!file.exists()) {System.exit(0);}
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(path),"UTF-8"));//按行读取
        String file_content = bufferedReader.readLine();//判断空行
        while (file_content!=null) {
            if(!file_content.equals(""))//如果是空行不进入内容判断
                input_content_list.add(content_analyse(file_content)); //把每个指令的判断加入input_content_list中//空行直接读取下一行
            file_content = bufferedReader.readLine();
        }
        bufferedReader.close();
//        System.out.println(input_content_list);
        return input_content_list; //返回input.txt里面的输入指令所有内容
    }

3.控制台传入参数个数异常

if (args.length != 2) {
            System.out.println("Please input two parameter");
            System.exit(-1);
        }

九、心得体会

这次作业给了两周的时间,前一周因为在准备缓考的考试就耽误了,结果第二周疯狂赶进度,ddl的逼近和打代码遇到的困难都给了我很大压力。已经不知道修改了多少bug,学习了多少新的知识了,连复盘的时间都没有,因为不能停下T T。
虽然压力大,但是收获也大,这次的作业让我更加有编码的信心,对需求的分析和实现的流程也更熟悉,接下来希望在下一次作业里能将这次不足的地方做得更好!

...全文
219 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
助教-张思锐 2023-03-10
  • 打赏
  • 举报
回复 1

时间紧张的情况下还能做的这么详细,上心了!

222000330林诗怡 学生 2023-03-15
  • 举报
回复
@助教-张思锐 谢谢助教

688

社区成员

发帖
与我相关
我的任务
社区描述
2023年福州大学软件工程实践课程W班的教学社区
软件工程团队开发软件构建 高校 福建省·福州市
社区管理员
  • FZU_SE_teacherW
  • 张书旖
  • 郭渊伟
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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