685
社区成员
这个作业属于哪个课程 | 2023年福大-软件工程实践-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
这个作业的目标 | 1、完成对澳大利亚网球公开赛相关数据的收集 2、实现一个能够对赛事数据进行统计的控制台程序 3、进行性能分析和改进 4、使用单元测试对项目进行测试 5、撰写博客 |
其他参考文献 | 具体参考教程在相应内容里注明 |
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 25 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 15 |
Development | 开发 | 800 | 720 |
• Analysis | • 需求分析 (包括学习新技术) | 150 | 160 |
• Design Spec | • 生成设计文档 | 40 | 30 |
• Design Review | • 设计复审 | 20 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 40 |
• Design | • 具体设计 | 30 | 40 |
• Coding | • 具体编码 | 300 | 400 |
• Code Review | • 代码复审 | 30 | 45 |
• Test | • 测试(自我测试,修改代码,提交修改) | 200 | 350 |
Reporting | 报告 | 60 | 50 |
• Test Repor | • 测试报告 | 30 | 35 |
• Size Measurement | • 计算工作量 | 20 | 25 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 1775 | 1980 |
注:该爬取行为只适用于课程教学!
思路:得知数据不需要在程序中爬取(联网),只需要在官网的api中获取后,在网上找到了如何通过浏览器抓包获取API的教程,找到对应数据文件,再通过转换工具将数据格式化。
参考教程:使用浏览器抓包获取API
具体步骤:
(1)打开我们要爬取数据的网站澳大利亚网球公开赛官网,进入赛程的界面。
(2)同时按Fn和F12
,打开开发人员工具
(3)选择开发人员工具的网络一栏并刷新页面,找到一个名为result
的文件就是我们要找的数据(这里以获取Q1数据为例)
(4)双击该文件,即可获得我们需要的源数据
附:Q1的API
(5)复制API中的数据,通过在线Json工具完成数据的格式化
思路:采用Gson解析Json数据,新建两个类分别对应选手信息和赛程信息,每个类只存需要的属性,利用Gson进行反序列化,最终通过两个类中的属性获取数据。
参考教程:(1)使用Gson解析复杂的json数据 (2)Java使用GSON对JSON进行解析
具体步骤
(1)首先下载Gson的jar包,将jar包导入IDEA
参考教程:IDEA引入jar包方式
(2)定义两个序列化的类Players和Schedule(采用内部类的形式),存放需要的属性
(3)在Lib类中编写解析数据的方法:
- parsePlayers:反序列化后返回的是选手信息的List,通过属性获取数据
- parseSchedule:反序列化后返回的是Schedule类,通过属性获取数据
思路:
读取文件: 一行一行读取文件中的内容,如果为空行则继续读下一条指令,每行不为空的作为参数传递到一个处理指令的方法,处理完成后添加到指令列表中
写入文件:从指令列表中取指令,对指令的形式进行判断,最终输出到output.txt文件
具体步骤:
(1)引入IDEA的Junit插件进行单元测试分析
(2)编写测试函数和测试用例进行测试
思路: 先查看代码,看看哪里有可以优化的地方,优化完成后,对未优化和优化完成后的程序进行对比,查看优化效果。
具体步骤:
(1)点击菜单栏文件-->项目结构
(2)选择工件一栏,点+按钮,选择JAR-->来自具有依赖项的模块.....
(3)主类选择程序入口类,点击确定
(4)回到主界面,点击菜单栏构建-->构建工件...,弹出对话框,选中刚刚生成的jar,点击构建
(5)将jar包放到与src同级目录下,即可在控制台运行
Players类:用来存放输出选手信息所需的属性
Schedule类:用来存放输出赛程信息所需的属性
getInput:对input.txt里的内容进行读取,每行非空的指令作为getInstruction方法的参数传入,通过getInstruction方法进行处理,加入字符串列表
getInstruction:对input.txt里的指令进行处理,返回处理后的字符串
parsePlayers:解析选手信息
parseSchedule:解析赛程信息
getOutput:将解析后的数据写入文件
关键点1:对json文件的解析要正确,采用第三方库Gson的方法
关键点2:input.txt文件的读取要正确,编写了两个函数getInput和getInstruction进行指令的读取和处理
采用了第三方库Gson对json文件进行解析,利用Gson进行反序列化,最终通过两个类中的属性获取数据
//选手类
public class Players {
public List<Player> players; //选手列表
public class Player {
public String full_name; //全名
public String gender; //性别
public Nationality nationality;
public class Nationality {
public String name; //国籍
}
}
}
//赛程类
public class Schedule {
public List<Matches> matches; //赛程列表
public List<Teams> teams; //球员所属队的列表
public List<Players> players; //选手列表
public class Matches {
public String actual_start_time; //比赛实际开始时间
public List<Teams> teams; //对局列表
public Match_status match_status;
public class Teams {
public String team_id; //标识一个team的id,为了与另外一个Teams列表关联
public List<Score> score; //比分列表
public String status; //标识是否为胜利者
public class Score {
public String game; //比分
}
}
public class Match_status {
public String code; //“C”为正常完成比赛,“W”为弃赛,“R”为退役
}
}
public class Teams {
public String uuid; //标识一个team的id
public List<String> players; //球员uuid的列表
}
public class Players {
public String uuid;
public String short_name; //姓名缩写
}
}
//对input.txt里的指令进行处理,返回处理后的字符串
public static String getInstruction(String instruction) {
String regex="\\s+"; //删去头尾空格
String i[] = instruction.trim().split(regex);
if (i[0].equals("players")) {
if (i.length == 1)
return "players";
else
return "Error";
}
else if (i[0].equals("result")) {
if (i.length == 2) {
String d = i[1];
if (d.equals("Q1"))
return "0109";
if (d.equals("Q2"))
return "0110";
if (d.equals("Q3"))
return "0111";
if (d.equals("Q4"))
return "0112";
if(isDate(i[1]))
return d;
return "N/A";
}
else
return "N/A";
}
else
return "Error";
}
未优化之前:
//将解析后的数据写入文件
public static void getOutput(List<String> input,String path) throws IOException {
File file = new File(path);
if (!file.exists())
file.createNewFile(); //如果文件不存在,则新建文件
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path) , "UTF-8"));
for (String i:input) {
if(i.equals("Error")) {
writer.write("Error" + "\n" + "-----" + "\n");
}
else if (i.equals("N/A")) {
writer.write("N/A" + "\n" + "-----" + "\n");
}
else if (i.equals("players")) {
writer.write(parsePlayers());
}
else {
writer.write(parseSchedule(i));
}
}
writer.close();
}
优化之后:
//将解析后的数据写入文件
public static void getOutput(List<String> input,String path) throws IOException {
File file = new File(path);
if (!file.exists())
file.createNewFile(); //如果文件不存在,则新建文件
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path) , "UTF-8"));
boolean pflag = false; //标记之前是否输出过players
boolean[] sflag = new boolean[25]; //标记之前是否输出过每个日期的赛程
for (int i = 0; i < 25; i++) {
sflag[i] = false;
}
for (String i:input) {
if(i.equals("Error")) {
writer.write("Error" + "\n" + "-----" + "\n");
}
else if (i.equals("N/A")) {
writer.write("N/A" + "\n" + "-----" + "\n");
}
else if (i.equals("players")) {
if (!pflag) { //如果从未输出过players
String s = parsePlayers();
memory.add(0 , s);
pflag = true;
}
writer.write(memory.get(0)); //之前输出过players,则直接从列表中取数据
}
else {
int date = Integer.valueOf(i);
date = date % 109 + 1;
if (!sflag[date]) { //如果从未输出过players
String s = parseSchedule(i);
memory.add(date , s);
sflag[date] = true;
}
writer.write(memory.get(date));
}
}
writer.close();
}
思路:每次要输出选手信息和赛程信息时,都要到相应json文件中取数据,想到可以把已经输出的数据放到一个列表中,并用boolean变量来判断是否之前读取过,这样一来就不用频繁去读取json文件,节省了时间
利用JProfiler对比程序运行结果(运行20万行正确指令的情况下)
未优化前:
优化后:
getInstruction的测试函数
public void testGetInstruction() {
Lib lib = new Lib();
lib.getInstruction("result sss");
lib.getInstruction("result0116");
lib.getInstruction("player");
lib.getInstruction("test");
lib.getInstruction("result Q1");
lib.getInstruction("result Q5");
lib.getInstruction("result 1312");
lib.getInstruction("result 0129");
lib.getInstruction("result");
lib.getInstruction("players");
}
getOutput的测试函数
public void testGetOutput() throws IOException {
Lib lib1 = new Lib();
Lib lib2 = new Lib();
lib2.getOutput(lib1.getInput("Test/abc.txt") , "Test/abcd.txt");
}
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("命令行应输入两个参数");
System.exit(-1);
}
}
File file = new File(path);
if (!file.exists()) {
System.out.println("文件不存在!");
System.exit(0);
}
(1)在这次作业中,给我最大的一个感悟就是要合理安排时间。如果没有及时开始实践,在ddl之前会非常手忙脚乱。应该要留足够的时间来修改bug、测试、性能优化,同时每完成一些功能后及时提交,还可以一边实现功能一边写博客,这样可以及时将当时的思路记录下来
(2)在开始写作业之前认真阅读作业要求十分重要!一开始的时候我只是粗略阅读一下作业要求就开始,这里写一写那里写一下,最后发现思维非常混乱,又重新读了一遍作业要求的博客后思路更加清晰,明确地知道要完成什么功能了
(3)在这次作业中,让我有了对程序进行性能优化的意识。以前写完一个程序能运行和正确输出后就直接提交了,不会再看那些地方可以优化,这次优化后前后程序的对比让我意识到性能优化的重要性,如果测试的数据很多话,优化的重要性就更能体现出来了
(4)最后,多多与其他同学交流也很重要。对于写程序过程中出现的问题,首先要自己动手调试,不能一遇到问题就找别人解决,如果遇到一直解决不了的问题可以与同学讨论,可能可以帮助自己打开思路,不然会一直卡在一个点上,无法推进