结对编程代码互评
本次代码评价基于 lyc 同学的中小学数学卷子自动生成程序(个人项目)进行测试、分析和评价。我和lyc同学一起参加过一些比赛,对彼此的代码风格和处理问题的逻辑都比较熟悉,希望这次能从他的代码中得到新的收获。
代码功能测试
基础功能分析测试
按照题目给出的用户和常规操作进行测试。
对应的卷子生成在程序所设置的绝对路径下,命名符合题目要求。
生成试卷进一步增加了难度标题,便于用户分辨,优化了用户体验。生成题目的格式符合规范,各个运算符号添加位置合理,符合题目要求。
随机输入测试
这部分测试不按照题目给出的格式进行,而是随机输入不符合要求的字符测试程序。可以看到,程序在遇到无法识别的字符串时会提示输入错误,并自动返回输入字符串前的状态,由此保障了程序的安全性和高可用性。
综上,可以看出 lyc 同学代码实现的功能和效果还是非常好的。
代码分析
这份代码有四个类:
其中Paper和User是实体类,分别定义了试卷和用户的属性和方法,PaperGenerationSystem是试卷产生系统的工具类,Main是程序的入口。
下面具体分析每个类的实现和作用
User
User类有三个属性:分别是类型,用户名和密码
属性名 | 类型 | 含义 |
type | String | 用户此时的生成试卷的类型 |
username | String | 用户名 |
password | String | 密码 |
构造方法如下
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[] | 高中试卷操作符号 |
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提高查找效率,同时避免用户重复,进而提升程序的工作效率。
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
是程序的主系统,实现了 用户交互部分 的功能,由于代码过长,只展示部分代码。这个方法同样有代码过长的问题,可以进一步封装来提高代码的可读性。
用户登录
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;
}
难度切换部分
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;
}
题目生成部分:根据输入是否有数字和数字的范围,来判断是否要生成题目
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
此方法是 试卷生成写入函数,用于
新建用户对应的文件夹和txt文件
将生成的题目经过查重,加序号处理后写入文件
在程序中,文件使用的是绝对路径,因此会在未经过用户同意的情况下,在用户的电脑的对应路径下产生相应的文件夹,可能会造成用户找不到文件的问题,降低用户的使用体验。
由于代码过长,只展示部分代码。这个方法同样有代码过长的问题,可以进一步封装来提高代码的可读性。
获取系统时间
Date time = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
String title = df.format(time);
生成文件,并将查重后的试题写入文件
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文件,逐个对比题目是否重复。这部分有一个小缺陷
// 只有一张卷子不查重
if (files.length == 1) {
return true;
}
我认为在同一张卷子的生成过程中,题目仍需与上面已经生产的题目进行查重处理。
isNumeric
此方法的作用是判断输入的字符串是否是数字,如果是数字则可能可以进行题目生成处理。
public boolean isNumeric(String str) {
Pattern pattern = Pattern.compile("[0-9]*");
Matcher isNum = pattern.matcher(str);
if (!isNum.matches()) {
return false;
}
return true;
}
代码评价
优点:
这份代码的功能实现十分完备,各个判断和边界考虑的清晰周到。
类的设计逻辑清晰,易于他人理解。
程序的文字提示十分完善,便于用户操作。
属性的安全性较好,在适当的地方采用了private和final关键字进行处理。
缺点:
方法的封装不够完善,程序整体的可读性偏差。
存储用户信息用的是链表,查找效率较低,可以采用map或者数据库
文件生成的路径是绝对路径,单从终端无法得知文件生成在哪里。
算法相对较为简单,效率较低,期待可以进行优化。
总体上来说,lyc同学编写的程序还是很值得我去学习的,尤其是在用户体验方面的考虑十分周到,向yc大佬低头!