软件工程导论个人项目互评
评价人:软件2101谢先衍
评价对象:软件2101方毅
前言
个人项目是实现一个中小学数学卷子自动生成程序,用户为小学、初中和高中老师,老师可以登录自己的账号生成对应年级的题目并保存到对应的文件夹下,并且还可以切换年级以生成不同难度的题目。
具体要求如下:
1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;
2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;
3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);
4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;
5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行。
难度要求表:
小学 | 初中 | 高中 | |
---|---|---|---|
难度要求 | +,-,*./ | 平方,开根号 | sin,cos,tan |
备注 | 只能有+,-,*./和() | 题目中至少有一个平方或开根号的运算符 | 题目中至少有一个sin,cos或tan的运算符 |
方毅同学在个人项目中使用的是Java语言,下面对他的项目进行评价。
1.代码结构
工程结构
C:.
├───.idea
│ └───codeStyles
├───out
│ └───production
│ └───TRProject
│ └───com
│ ├───baseclass
│ ├───filetool
│ ├───menupart
│ └───problemtool
├───source
│ └───Problems
│ ├───张三1
│ ├───李四1
│ └───王五1
└───src
└───com
├───baseclass
├───filetool
├───menupart
└───problemtool
其中源代码的结构
.
├── Main.java
└── com
├── baseclass
│ ├── People.java
│ └── User.java
├── filetool
│ ├── CreateFileTool.java
│ ├── ReadFileTool.java
│ └── WriteFileTool.java
├── menupart
│ ├── InitInformation.java
│ ├── LoginMenu.java
│ └── WorkMenu.java
└── problemtool
├── GetProblem.java
├── ListComparison.java
├── RandomSeed.java
└── SaveProblem.java
具体设计
baseclass 👤
首先是baseclass下的的People类和User类,People类有name, password和status(该老师所属的年级编码)三个属性,User类继承People类,我个人感觉这里就没有必要用继承了,User类相对于People类并没有增加任何属性和方法,直接一个User类就行了,方毅同学应该是被个人项目要求里的一些地方误导了才这么设计的。
点击查看People类
package com.baseclass;
public abstract class People {
public String name;
public String password;
public String status;
public People(){}
public People(String name, String password, String status)
{
this.name=name;
this.password=password;
this.status=status;
}
}
点击查看User类
package com.baseclass;
public class User extends People{
public User(){}
public User(String name, String password, String status)
{
this.name=name;
this.password=password;
this.status=status;
}
}
filetool 📁
然后filetool包下面的三个类分别是用来创建文件、读文件和写文件的方法类。
- CreateFileTool在指定路径下创建文件,如果指定的路径存在就直接创建文件,否则先创建目录在创建文件。
- ReadFileTool读取指定文件到List容器中:
while ((line = br.readLine()) != null) {
linesList.add(line); // 将每一行添加到列表中
}
- WriteFileTool将List容器中的内容写入指定路径下的文件,并且可以控制是否在两行之间空一行:
// 逐行写入字符串列表中的内容
for (String line : stringList) {
bufferedWriter.write(line);
bufferedWriter.newLine(); // 写入换行符
if (isLine) bufferedWriter.newLine();
}
核心部分problemtool 🛠
这部分时与出题相关的类,是该项目比较核心的部分。
1.RandomSeed类
该部分包含两个方法,一个是fiveRandomNum方法,返回5个随机的1-100之间的数字。
另一个是getUniqueCode方法,根据年级和随机的五个数字为每道题生成唯一的UniqueCode,如下:
H-53-65-92-96-80
H-88-74-88-75-10
H-23-68-97-17-98
H-91-91-31-49-5
小学以"L-"开头初中"M-"高中"H-",后面加上五个随机数。
点击查看getUniqueCode方法
public static String getUniqueCode(String status, int[] num) {
//为每道随机的题目出随机且唯一的出题码
String s1 = "";
String s2 = num[0] + "-" + num[1] + "-" + num[2] + "-" + num[3] + "-" + num[4];
if (status.equals("0")) s1 = "L-" + s2;
if (status.equals("1")) s1 = "M-" + s2;
if (status.equals("2")) s1 = "H-" + s2;
return s1;
}
2.ListComparison类
里面只有一个check方法,这个方法开始就有问题了,check方法的第一个参数是当前生成的题目的UniqueCode,list2是之前的所有题目的,将它们分别存入HashSet,看Set和List长度是否相同来达到检查是否有重复的目的。根据在调用时的做法,这种查重方式是在所有题目全部生成之后再进行查重,一旦有重复的题目,直接不再生成而提醒用户有重复题目,这种做法显然是有很大问题的。而且初中生成的题目统统只有一个操作数,这种情况下按照概率基本上每次都必会重复了,完全无法正常生成题目。但是还有一些bug让方毅同学在测试的时候没有发现这一点。
点击查看check方法
public static boolean check(List<String> list1, List<String> list2) {
// 创建两个集合来存储各自的元素
Set<String> set1 = new HashSet<>(list1);
Set<String> set2 = new HashSet<>(list2);
// 检查是否有重复元素
if (set1.size() != list1.size() || set2.size() != list2.size()) {
return false;
}
// 检查两个集合之间是否有相同的元素
for (String s : set1) {
if (set2.contains(s)) {
return false;
}
}
return true;
}
3.GetProblem类
上面的RandomSeed类里面的fiveRandomNum方法返回五个随机的数字,方毅同学在小学和高中的出题函数中所有题目都是那五个随机数打乱顺序作为操作数,而初中的题目只选取其中一个作为操作数,因此最后的结果就是小学高中都是五个操作数的题目,初中都是一个数的题目,而按照上面那种查重方式,初中的题目很容易重复,而UniqueCode有五个随机数显然是不容易重复的,这个bug方毅同学没有发现。
点击查看GetProblem类
package com.problemtool;
import java.util.Arrays;
import java.util.Random;
public class GetProblem {
public static String low(int[] numbers) {
Random random = new Random();
// 打乱数组顺序以确保题目不同
for (int i = 0; i < numbers.length; i++) {
int j = random.nextInt(numbers.length);
int temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}
// 随机生成题目
StringBuilder question = new StringBuilder("(" + numbers[0]);
for (int i = 1; i < numbers.length; i++) {
char operator = "+-*/".charAt(random.nextInt(4));
// 随机决定是否加括号
boolean useParentheses = random.nextBoolean();
if (useParentheses) {
question.append(") ").append(operator).append(" (").append(numbers[i]);
} else {
question.append(" ").append(operator).append(" ").append(numbers[i]);
}
}
question.append(")=?");
return question.toString();
}
public static String mid(int[] numbers) {
Random random = new Random();
int resultIndex = random.nextInt(5);
int resultNumber = numbers[resultIndex];
// 随机选择平方或开根号运算符
String[] operators = {"²", "√"};
String operator = operators[random.nextInt(operators.length)];
// 生成问题
String question;
if (operator.equals("²")) {
// 如果运算符是平方,则选择一个可以平方的数字
int squareIndex = random.nextInt(numbers.length);
while (numbers[squareIndex] < 2) {
squareIndex = random.nextInt(numbers.length);
}
question = numbers[squareIndex] + "² = ?";
} else {
// 否则,生成开根号的题目
question = "√" + resultNumber + " = ?";
}
return question;
}
public static String high(int[] numbers) {
Random rand = new Random(Arrays.hashCode(numbers));// 角度值(以弧度表示)
int[] angles = {0, 15, 30, 45, 60, 75, 90};// 随机选择五个角度和一个函数
int[] angleIndices = new int[5];
String[] operators = {"+", "-", "*", "/"};//随机选择五个运算符(+,-,*,/)
String[] selectedOperators = new String[5];
String[] functions = {"sin", "cos", "tan"};// 随机选择一个函数
String[] selectedFunctions = new String[5];
for (int i = 0; i < 5; i++) {
angleIndices[i] = rand.nextInt(angles.length);
}
Arrays.sort(angleIndices); // 为了让角度按顺序排列
for (int i = 0; i < 5; i++) {
selectedOperators[i] = operators[rand.nextInt(operators.length)];
}
for (int i = 0; i < 5; i++) {
selectedFunctions[i] = functions[rand.nextInt(functions.length)];
}
// 生成题目
return String.format(
"%s(%d) %s %s(%d) %s %s(%d) %s %s(%d) %s %s(%d) = ?",
selectedFunctions[0],
angles[angleIndices[0]],
selectedOperators[0],
selectedFunctions[1],
angles[angleIndices[1]],
selectedOperators[1],
selectedFunctions[2],
angles[angleIndices[2]],
selectedOperators[2],
selectedFunctions[3],
angles[angleIndices[3]],
selectedOperators[3],
selectedFunctions[4],
angles[angleIndices[4]]);
}
}
4.SaveProblem类
调用GetProblem方法生成指定数量的题目并将其存入文件,然后UniqueCode也在原有文件后面新增新的内容。
点击查看SaveProblem类
package com.problemtool;
import com.baseclass.User;
import com.filetool.CreateFileTool;
import com.filetool.WriteFileTool;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class SaveProblem {
public static void save(User u, List<String> newCode, List<int[]> ranSeed) {
//传入的是用户,出题码,随机数
List<String> beforeProblem = new ArrayList<>();
List<String> afterProblem = new ArrayList<>();
//一个存出的题(不含序号),一个存存入文件中的题(含有序号)
LocalDateTime now = LocalDateTime.now();
String J;
String k1;
k1 = now.getYear() + "-" + now.getMonthValue() + "-" + now.getDayOfMonth() + "-" +
now.getHour() + "-" + now.getMinute() + "-" + now.getSecond()+".txt";
for (int i = 0; i < ranSeed.size(); i++) {
if (u.status.equals("0")) beforeProblem.add(GetProblem.low(ranSeed.get(i)));
if (u.status.equals("1")) beforeProblem.add(GetProblem.mid(ranSeed.get(i)));
if (u.status.equals("2")) beforeProblem.add(GetProblem.high(ranSeed.get(i)));
J = (i + 1) + ". " + beforeProblem.get(i);
afterProblem.add(J);
System.out.println(J);
}
CreateFileTool.createFile("source/Problems/" + u.name, k1);
WriteFileTool.writeFile("source/Problems/" + u.name, newCode, "UniqueCode.txt", false);
WriteFileTool.writeFile("source/Problems/" + u.name, afterProblem, k1, true);
}
}
menupart📑
menupart包里面是登录及操作时控制菜单的类,这部分代码有该出题系统一个重大bug🐛,在workmenu类中,在提示"准备生成" + nowStatus + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):"的循环中,由于List容器L和NewCode是在循环外部初始化的,每次循环都会往里面add新的元素,导致同一个用户第一次出题没有问题,第二次出题的数量就是第一次出题的数量加上第二次输入的数字,以此类推,后面题目会越来越多。方毅同学测试的时候没有进行多次生成题目也就没有发现这个bug⚠️。
点击查看workmenu类
package com.menupart;
import com.baseclass.User;
import com.problemtool.ListComparison;
import com.problemtool.RandomSeed;
import com.problemtool.SaveProblem;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class WorkMenu {
public static void menu(User u, List<String> uniqueCode) {
String nowStatus;
//此处为原先代码初始化所在处======================
/*List<int[]> L = new ArrayList<>();*/
/*List<String> NewCode = new ArrayList<>();*/
//==============================================
nowStatus = u.status.equals("0") ? "小学" : u.status.equals("1") ? "初中" : "高中";
Scanner in = new Scanner(System.in);
while (true) {
System.out.print("准备生成" + nowStatus + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):");
String input = in.next();
if (input.equals("-1")) break;
if (input.matches("\\d+")) {
int number = Integer.parseInt(input);
//修改后移动到了循环里面========================
List<int[]> L = new ArrayList<>();
List<String> NewCode = new ArrayList<>();
//============================================
if (number >= 10 && number <= 30) {
for (int i = 0; i < number; i++) {
int[] temp = RandomSeed.fiveRandomNum();
L.add(temp);
NewCode.add(RandomSeed.getUniqueCode(u.status, temp));
//调用随机种子出题
}
if (!ListComparison.check(NewCode, uniqueCode)) {
System.out.println("这次的题目出现了重复,请重新出题");
//自动查重,若和已有的题目有所重复,告诉用户重新出题,查重功能是必须有且限制于程序的
} else {
System.out.println("出题成功");
SaveProblem.save(u, NewCode, L);
}
} else {
System.out.println("输入的是数字但不在10到30之间,请重新输入。");
}
} else if (input.equals("切换为小学") || input.equals("切换为初中") || input.equals("切换为高中")) {
System.out.println("切换成功!");
u.status = input.contains("小学") ? "0" : input.contains("初中") ? "1" : "2";
nowStatus = u.status.equals("0") ? "小学" : u.status.equals("1") ? "初中" : "高中";
} else {
System.out.println("请输入小学、初中和高中三个选项中的一个");
}
}
}
}
其他部分没什么很大的问题。
main函数
调用menupart里面的方法登录并使用。
2.功能测试
这是在修改了workmenu的bug之后再进行的功能测试。
登录
如果输入格式或账号密码不正确会有提示,输入正确则进入系统。
出题
输入数字后则生成指定数量的题目,文件名以及题目文件格式均没有问题。
题目数字不在规定范围会有提示。
切换
切换后正常生成对应难度的题目。
随便乱输入会有提醒。
退出
正常退出。
3.总结
总的来说,方毅同学的代码有一下优缺点:
优点
- 代码清晰整洁,符合Google style规范,关键处都有注释。
- 每个方法以及类功能单一,没有冗余代码,符合设计原则
- 为每个题目生成一个独一无二的UniqueCode查重
- 对用户可能输入的各种情况都作了考虑
缺点
- 未发现每次生成题目数量叠加的bug
- 生成的题目操作数较单一,都是相同个数的操作数
- 每个题目UniqueCode的使用也使得生成题目的种类数受到限制
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)