结对编程代码互评

结对编程代码互评

本次代码评价基于 lyc 同学的中小学数学卷子自动生成程序(个人项目)进行测试、分析和评价。我和lyc同学一起参加过一些比赛,对彼此的代码风格和处理问题的逻辑都比较熟悉,希望这次能从他的代码中得到新的收获。

代码功能测试

  1. 基础功能分析测试

按照题目给出的用户和常规操作进行测试。

img

对应的卷子生成在程序所设置的绝对路径下,命名符合题目要求。

img

生成试卷进一步增加了难度标题,便于用户分辨,优化了用户体验。生成题目的格式符合规范,各个运算符号添加位置合理,符合题目要求。

img

  1. 随机输入测试

这部分测试不按照题目给出的格式进行,而是随机输入不符合要求的字符测试程序。可以看到,程序在遇到无法识别的字符串时会提示输入错误,并自动返回输入字符串前的状态,由此保障了程序的安全性和高可用性。

img

综上,可以看出 lyc 同学代码实现的功能和效果还是非常好的。

代码分析

这份代码有四个类:

img

其中Paper和User是实体类,分别定义了试卷和用户的属性和方法,PaperGenerationSystem是试卷产生系统的工具类,Main是程序的入口。

下面具体分析每个类的实现和作用

User

User类有三个属性:分别是类型,用户名和密码

属性名 类型 含义
type String 用户此时的生成试卷的类型
username String 用户名
password String 密码

构造方法如下

1
2
3
4
5
public User(String type, String username, String password) {
this.type = type;
this.username = username;
this.password = password;
}

其他方法:

方法名 返回值 作用
getType String 获取用户类型
setType void 设置用户类型
getUsername String 获取用户名
setUsername void 设置用户名
getPassword String 获取用户密码
setPassword void 设置用户密码

总体来说User类设计逻辑清晰,方法得体。

Paper

Paper类有三个属性:分别是三种难度的运算符号

属性名 类型 含义
priSign String[] 小学试卷操作符号
midSign String[] 初中试卷操作符号
highSign String[] 高中试卷操作符号
1
2
3
4
5
private final String[] priSign = {"+", "-", "*", "/"}; // 小学试卷操作符号

private final String[] midSign = {"²", "√"}; // 初中试卷操作符号

private final String[] highSign = {"sin", "cos", "tan"}; // 高中试卷操作符号

Paper类只有一个方法,这也是这份代码做的不太好的地方:一个方法过长,即使注释写的比较清楚,也显得繁杂,可读性较低。

方法名 返回值 作用
makeQuestion String 生成数学题的方法

生成算式的逻辑是按照算式元素从左到右的顺序,除了必须的操作数和加减乘除等于符号,根据试卷类型和随机数来决定是否插入每个符号元素。这种方式生成逻辑清晰,便于理解,但是生成过程繁杂,算法性能并不是很好。

PaperGenerationSystem

PaperGenerationSystem类仅有一个属性,是用户信息的存储链表

属性名 类型 含义
userMessage List 存储用户信息的链表

PaperGenerationSystem的方法如下:

方法名 *返回值 作用
userInit void 初始化用户信息的链表信息
generate void 程序的主系统
PaperGeneration void 试卷生成写入函数
check boolean 查重函数
isNumeric boolean 判断是否为数字
  • userInit

使用链表存储小规模的用户信息,避免了数据库的使用,简化了程序。但是在判断登录时,需要遍历链表来比对用户名和密码,效率较低,可以换成hashmap提高查找效率,同时避免用户重复,进而提升程序的工作效率。

1
2
3
4
5
6
7
8
9
10
11
public void userInit() {
userMessage.add(new User("小学", "张三1", "123"));
userMessage.add(new User("小学", "张三2", "123"));
userMessage.add(new User("小学", "张三3", "123"));
userMessage.add(new User("初中", "李四1", "123"));
userMessage.add(new User("初中", "李四2", "123"));
userMessage.add(new User("初中", "李四3", "123"));
userMessage.add(new User("高中", "王五1", "123"));
userMessage.add(new User("高中", "王五2", "123"));
userMessage.add(new User("高中", "王五3", "123"));
}
  • generate

是程序的主系统,实现了 用户交互部分 的功能,由于代码过长,只展示部分代码。这个方法同样有代码过长的问题,可以进一步封装来提高代码的可读性。

    • 用户登录
1
2
3
4
5
6
7
8
9
10
11
System.out.println("-----欢迎来到中小学数学卷子自动生成系统!-----");
System.out.println("## 请输入用户名和密码(两者之间用空格隔开)");

while (true) {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();

if (!str.contains(" ")) {
System.out.println("## 输入的账号密码格式不对,请重新输入");
continue;
}
    • 难度切换部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
System.out.println(
"## 当前选择为"
+ userMessage.get(index).getType()
+ "出题,请输入生成题目数量(题目数量范围为10~30,输入-1将退出当前用户,重新登录),如需切换出题类型,请输入切换为xx,xx为小学、初中和高中三个选项中的一个");
String command = sc.nextLine();
if (command.equals("")) {
System.out.println("请输入内容!");
continue;
}
if (command.equals("切换为小学")) {
userMessage.get(index).setType("小学");
continue;
} else if (command.equals("切换为初中")) {
userMessage.get(index).setType("初中");
continue;
} else if (command.equals("切换为高中")) {
userMessage.get(index).setType("高中");
continue;
}
    • 题目生成部分:根据输入是否有数字和数字的范围,来判断是否要生成题目
1
2
3
4
5
6
7
8
9
10
11
if (isNumeric(command)) {
if (Integer.valueOf(command) <= 30 && Integer.valueOf(command) >= 10) {
PaperGeneration(
Integer.valueOf(command),
userMessage.get(index).getType(),
userMessage.get(index).getUsername());
System.out.println("## 试卷生成成功!请在对应包下查看试卷!");
} else {
System.out.println("## 不满足题目数量,请重新输入");
}
}
  • PaperGeneration

此方法是 试卷生成写入函数,用于

    1. 新建用户对应的文件夹和txt文件
    2. 将生成的题目经过查重,加序号处理后写入文件

在程序中,文件使用的是绝对路径,因此会在未经过用户同意的情况下,在用户的电脑的对应路径下产生相应的文件夹,可能会造成用户找不到文件的问题,降低用户的使用体验。

由于代码过长,只展示部分代码。这个方法同样有代码过长的问题,可以进一步封装来提高代码的可读性。

    • 获取系统时间
1
2
3
Date time = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
String title = df.format(time);
    • 生成文件,并将查重后的试题写入文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    String dirPath = "D:\\IDEA code\\PaperSystem\\system\\src\\com\\papers\\" + username;
File file = new File(dirPath);

try {
if (!file.exists()) file.mkdirs();
String paperDir = dirPath + "\\" + title + ".txt";
// 写入文件内容,即试题
File newPaper = new File(paperDir);
writer = new FileWriter(newPaper);
// 标明是哪种卷子
writer.write(type + "期末考试试卷\r\n\r\n");

int quesIndex = 1; // 题目序号

// 根据输入的数量开始出题并写入文件
while (sum > 0) {
String content = paper.makeQuestion(type);
// 试题查重
if (check(newPaper, content)) {
if (sum == 1) {
writer.write(quesIndex + ". " + content);
break;
}
writer.write(quesIndex + ". " + content + "\r\n" + "\r\n");
sum--;
quesIndex++;
}
if (!check(newPaper, content)) {
System.out.println(content);
}
}
// 刷新并关闭FileWriter
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
  • check

此方法负责对题目进行查重,主要思路是遍历用户文件夹下的txt文件,逐个对比题目是否重复。这部分有一个小缺陷

1
2
3
4
// 只有一张卷子不查重
if (files.length == 1) {
return true;
}

我认为在同一张卷子的生成过程中,题目仍需与上面已经生产的题目进行查重处理。

  • isNumeric

此方法的作用是判断输入的字符串是否是数字,如果是数字则可能可以进行题目生成处理。

1
2
3
4
5
6
7
8
public boolean isNumeric(String str) {
Pattern pattern = Pattern.compile("[0-9]*");
Matcher isNum = pattern.matcher(str);
if (!isNum.matches()) {
return false;
}
return true;
}

代码评价

  • 优点:

    1. 这份代码的功能实现十分完备,各个判断和边界考虑的清晰周到。
    2. 类的设计逻辑清晰,易于他人理解。
    3. 程序的文字提示十分完善,便于用户操作。
    4. 属性的安全性较好,在适当的地方采用了private和final关键字进行处理。
  • 缺点:

    1. 方法的封装不够完善,程序整体的可读性偏差。
    2. 存储用户信息用的是链表,查找效率较低,可以采用map或者数据库
    3. 文件生成的路径是绝对路径,单从终端无法得知文件生成在哪里。
    4. 算法相对较为简单,效率较低,期待可以进行优化。

总体上来说,lyc同学编写的程序还是很值得我去学习的,尤其是在用户体验方面的考虑十分周到,向yc大佬低头!