软件工程实践第二次作业——个人实战
| 这个作业属于哪个课程 | 2023年福大-软件工程实践-W班 |
|---|---|
| 这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
| 这个作业的目标 | 1.完成代码编写并进行测试。2.撰写博客。 |
| 其他参考文献 | Java性能优化 ; Java Gson教程 ;Java文本逐行读取及效率对比 |
目录
- 1. Gitcode项目地址
- 2. PSP表格
- 3. 解题思路描述
- 3.1爬取数据
- 3.2解析json文件
- 3.3读写txt文件
- 4.设计和实现过程
- 4.1项目包含的类
- 4.2类与主要函数
- 4.3主要函数关系
- 5. 关键代码展示
- 6.性能改进
- 7.单元测试
- 8.异常处理
- 9.心得体会
1. Gitcode项目地址
2. PSP表格
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 50 |
| • Estimate | • 估计这个任务需要多少时间 | 30 | 50 |
| Development | 开发 | 620 | 1000 |
| • Analysis | • 需求分析 (包括学习新技术) | 80 | 120 |
| • Design Spec | • 生成设计文档 | 40 | 60 |
| • Design Review | • 设计复审 | 20 | 30 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
| • Design | • 具体设计 | 60 | 120 |
| • Coding | • 具体编码 | 300 | 500 |
| • Code Review | • 代码复审 | 40 | 60 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 60 | 80 |
| Reporting | 报告 | 60 | 90 |
| • Test Repor | • 测试报告 | 20 | 30 |
| • Size Measurement | • 计算工作量 | 15 | 30 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 25 | 30 |
| 合计 | 710 | 1140 |
3. 解题思路描述
3.1爬取数据
一开始并不知道怎么爬取数据,后来经过百度了解到可以利用浏览器的开发者工具进行数据爬取。
首先,进入澳大利亚网球公开赛官网,找到schedule栏,选择要爬取的赛事日期,按F12打开浏览器开发者工具,选择“网络”,等待响应,点击schedule文件名,右边会出现json格式的数据。

然后双击schedule文件名,会出现新页面,点击“原始数据”,选择“美化输出”,就可以把数据按美化输出后的样式保存成json文件。

3.2解析json文件
这次作业编码重点就是读json文件,将所需的数据输出。因为之前在学习Android时用过gson,所以我打算继续用第三方库gson来解析json文件。大概思路就是建player类,schedule类,然后用gson反序列化得到相应的对象,最后输出。但是没想到的是实际编码过程中发现json文件里数据是比较复杂的,就干脆用JsonObject和JsonArray一层一层获取所需的数据。
3.3读写txt文件
一是读输入的指令文件input.txt,打算按行读取每条指令,用String类的split函数对每条指令进行处理,然后判断指令的正确性,最后执行指令。
二是写结果文件output.txt,考虑到每条指令执行都有各自的结果,就先将结果存在一个字符串数组,最后一起写进output文件。
4.设计和实现过程
4.1项目包含的类
//主程序,包含main方法
public class AOSearch {...}
//Lib类,包含自定义函数
public class Lib {...}
//Player类,存放运动员相关信息
public class Player {...}
//Scores类,存放每个队的比赛成绩
public class Scores {...}
//Team类,存放每个队的信息
public class Team {...}
4.2类与主要函数
- Lib类
//Lib类,包含自定义函数
public class Lib {
//读指令文件input.txt
public static boolean readInputTxt(String file) throws IOException {...}
//判断每条指令的正确性
public static String isRightInstruction(String s) {...}
//判断result指令后日期是否正确
public static boolean isRightDate(String s) {...}
//执行指令
public void runInstruction() throws IOException {...}
//读取json文件
public String readJson(String s) throws IOException {...}
//获取运动员信息
public void getPlayers(String s) {...}
//获取赛事信息
public void getSchedule(String s) {...}
//获取胜利队伍的成员信息
public void getWinPlayers(JsonObject jsonObject,ArrayList<String> winteam,HashMap<String, ArrayList> Sites) {...}
//按格式输出时间及对应的比赛队伍成绩
public void showScheduleForm(ArrayList<Team> tm,ArrayList<String> time,ArrayList<String> winteam,HashMap<String, ArrayList> Sites) {...}
//获取要写入文件的结果
public ArrayList<String> getResults() {...}
//将结果写入文件
public void writeToFile(String file) throws IOException {...}
}
- Player类
public class Player {
private String full_name; //全名
private String gender; //性别
private String nationality; //国籍
public Player(String s1,String s2,String s3){
full_name=s1;
gender=s2;
nationality=s3;
}
@Override
public String toString() {...}
...//及set、get方法
- Scores类
public class Scores {
private String set,game; //场次、得分
private boolean winner; //是否胜利
public Scores(String set,String game,boolean winner){
this.set=set;
this.game=game;
this.winner=winner;
}
...//及set、get方法
- Team类
public class Team {
private String status=null; //状态
private String team_id; //队名id
private ArrayList<Scores> scoresArrayList; //成绩
public Team(){
scoresArrayList=new ArrayList<Scores>();
}
...//及set、get方法
4.3主要函数关系
- (1)AOSearch.main函数
调用lib.readInputTxt() 函数读取输入文件里的指令
调用lib.runInstruction() 函数执行指令
调用lib.writeToFile() 函数将结果写入输出文件 - (2)Lib.isRightInstruction函数
调用lib.isRightDate() 函数判断赛事日期是否正确 - (3)Lib.runInstruction函数
调用lib.isRightInstruction() 函数判断指令正误
调用lib.readJson() 函数读取json文件
调用lib.getPlayers() 函数获取运动员信息
调用lib.getSchedule() 函数获取赛事信息 - (4)Lib.getSchedule函数
调用lib.getWinPlayers() 函数获取胜利队的成员名
调用lib.showScheduleForm() 函数按格式输出赛事信息
5. 关键代码展示
1、我觉得本次作业关键点是对于赛事结果的获取,里面的细节点有这些:
- 获胜队成员的名字缩写查找:status:winner-->获胜队伍id-->成员id-->成员名字缩写
- 两个队伍成绩的获得,并且按场次顺序输出各自成绩
- 得到数据后,按要求格式整理数据
//获取胜利队成员名字
public void getWinPlayers(JsonObject jsonObject,ArrayList<String> winteam,HashMap<String, ArrayList> Sites) {
//取获胜队的成员
JsonArray teamArray = jsonObject.getAsJsonArray("teams");
for (String s : winteam) {
for (JsonElement ta : teamArray) {
JsonObject jsonObject1 = ta.getAsJsonObject();
String uuid = jsonObject1.get("uuid").getAsString();
//判断是否是获胜队
if (uuid.equals(s)) {
//取出成员编号
JsonArray memberArray = jsonObject1.get("players").getAsJsonArray();
ArrayList<String> members = new ArrayList<String>();
for (JsonElement ma : memberArray) {
members.add(ma.getAsString());
}
Sites.put(uuid, members);
}
}
}
//在player数组找成员名字
JsonArray playerArray = jsonObject.getAsJsonArray("players");
ArrayList tmp;
//player
for (JsonElement pa : playerArray) {
JsonObject jsonObject1 = pa.getAsJsonObject();
String playeruuid = jsonObject1.get("uuid").getAsString();
for (String i : Sites.keySet()) {
tmp = Sites.get(i);
for (int j = 0; j < tmp.size(); j++) {
//找到名字就替换成员编号
if (tmp.get(j).equals(playeruuid)) {
tmp.set(j, jsonObject1.get("short_name").getAsString());
}
}
Sites.replace(i, tmp);
}
}
}
以上获取成员名字缩写思路就是:将获胜队伍id与该队伍成员id用HashMap的方式进行匹配,然后再取查询成员id对应得名字缩写,替换id,目的是为了队伍id与队伍成员名字缩写相匹配。
public void showScheduleForm(ArrayList<Team> tm,ArrayList<String> time,ArrayList<String> winteam,HashMap<String, ArrayList> Sites) {
int p = 0;
//先按时间顺序
StringBuilder sb;
for (int i = 0; i < time.size(); i++) {
sb = new StringBuilder();
if (time.get(i).equals("no")) {
sb.append("W/O\n-----\n");
} else {
sb.append("time:").append(time.get(i)).append("\nwinner:");
//找胜利队成员
int b = 0;
if (Sites.get(winteam.get(i)) != null) {
for (int m = 0; m < Sites.get(winteam.get(i)).size(); m++) {
sb.append(Sites.get(winteam.get(i)).get(m));
if (b != Sites.get(winteam.get(i)).size() - 1) {
sb.append(" & ");
b++;
}
}
sb.append("\nscore:");
int m = 0;
//按2个队伍一组,输出2队之间成绩
for (int j = 0; j < tm.get(p).getScore().size(); j++) {
sb.append(tm.get(p).getScore().get(j)).append(":").append(tm.get(p + 1).getScore().get(j));
if (m != tm.get(p).getScore().size() - 1) {
sb.append(" | ");
m++;
}
}
sb.append("\n-----\n");
p = p + 2;
}
results.add(sb.toString());
}
}
}
以上按格式输出赛事的思路就是:首先,已经提前把时间和获胜队伍id分别存储在字符串数组里,这样就可以输出格式前两行的time和winner,对于特殊情况“弃赛”就直接输出“W/O”;接下来,先前读取赛事时把所有队伍id也存储在字符串数组里,就是按两个队伍为一组,将成绩根据场次顺序输出来。这样一个时间段的比赛情况就按格式整理好了。
6.性能改进
1、在循环输出赛事结果时,字符串连接时使用StringBuilder/StringBuffer的append方法代替字符串的“+”号连接,提高效率。
2、在循环遍历JsonArray时不要不断创建JsonObject对象引用,节省内存空间,如:

7.单元测试
1、部分测试代码
- 判断指令是否正确
@Test
void isRightInstructionTest() { //判断指令是否正确
assertEquals("players",Lib.isRightInstruction("players"));
assertEquals("Error",Lib.isRightInstruction("Player"));
assertEquals("N/A",Lib.isRightInstruction("result"));
assertEquals("Error",Lib.isRightInstruction("result0116"));
assertEquals("N/A",Lib.isRightInstruction("result 0133"));
assertEquals("N/A",Lib.isRightInstruction("result sss"));
assertEquals("N/A",Lib.isRightInstruction("result 117"));
assertEquals("0116",Lib.isRightInstruction(" result 0116"));
assertEquals("Q2",Lib.isRightInstruction("result Q2"));
assertEquals("Error",Lib.isRightInstruction("re"));
assertEquals("N/A",Lib.isRightInstruction(" result players"));
}
- 测试获取Player的结果是否为空
@Test
void getPlayerTest() throws IOException {
Lib lib1=new Lib();
lib1.getPlayers(lib1.readJson("players"));
assertNotNull(lib1.getResults());
}
2、单元测试覆盖率

8.异常处理
1、命令行参数个数的错误

2、文件不存在

3、文件读写的异常


9.心得体会
- 在编码过程中要思路清晰,因为用旧数据格式解析复杂json数据时我是层层循环遍历获取,容易出错,然后就会卡壳,又要重新理一遍思路。
- 第一次用gitcode管理代码还不太习惯,但用的时候发现了它的好处:当代码改动多次后,效果不如之前还可以迅速找回原来的代码。
- 在做这次作业过程中,学习了爬取数据,单元测试和性能优化等,这些东西其实之前都没有用过,只有到实际运用时才会去学这些,也就是边做边学,希望自己今后能提前去学一些未来可能会用上的知识或技术,不要等到要用的时候才狂补,耗时耗精力,这就是“磨刀不误砍柴工”。


