个人项目互评
评价人员:刘贤
编码人员;郭丽琳
编程语言:Java
一、题目分析
用户:小学、初中和高中数学老师。
功能:
1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;
2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;
3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);
4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;
5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;
二、代码分析
User类
姓名、密码和默认年级全部设置为private类型,起到封装作用,避免被外部类直接访问并随意修改,避免出现安全问题。同时将生成文件操作单独写成一个方法,方便进行各种调用,提升了代码的可读性。

public class User { private String account; private String password; private String kind; /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public User(String account, String password, String kind) { this.account = account; this.password = password; this.kind = kind; } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public String getAccount() { return account; } public String getPassword() { return password; } public String getKind() { return kind; } // 用于切换类型 public void setKind(String kind) { this.kind = kind; } // 用于修改密码 public void setPassword(String password) { this.password = password; } // 生成专用文件夹 和 txt文件 /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public File makeFile() { String mkPath = this.getAccount(); File file = new File(mkPath); Date date = new Date(); // 时间处理 用于txt文件的名称 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); String title = simpleDateFormat.format(date) + ".txt"; if (!file.exists()) { // 单例模式 如果没有对应文件夹则生成 file.mkdirs(); } File paper = new File(mkPath, title); return createFile(paper); } private File createFile(File file) { try { if (file.createNewFile()) { return file; } else { throw new IOException("文件创建失败"); } } catch (IOException e) { e.printStackTrace(); return null; } } // 开始制作试卷 /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public void makePaper(int digit) { Paper paperMaker = new Paper(); File newFile = makeFile(); if (this.kind.equals("小学")) { paperMaker.primaryMaker(digit, newFile); } else if (this.kind.equals("初中")) { paperMaker.juniorMaker(digit, newFile); } else if (this.kind.equals("高中")) { paperMaker.seniorMaker(digit, newFile); } } }
Write类
每生成一道题,就将获取到的算式按照字符串的格式存储到文件中,并且加入try catch异常处理,可以避免因为代码中出现异常而导致程序崩溃,提升了代码的健壮性。

public class Writer { // 写入文件 /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public void writeIn(File file, Integer n, String topic) { try { // 使用 NIO 写入文件 Path filePath = file.toPath(); String serial = String.valueOf(n) + "."; String content = serial + " " + topic + "\n\n"; // 使用 StandardOpenOption.APPEND 表示追加写入文件 Files.write(filePath, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND); // System.out.println("写入成功!"); } catch (IOException ioException) { ioException.printStackTrace(); } } }
Login类
如果在输入的语句中包含“切换为xx”三个字,那么就会进一步使用setUserKind来判断是否"xx"为小学、初中、高中三者任意一个,如果是的话,那么重置年级为新的年级。在代码中,队友创新的增加了修改密码和注册的功能,但是并不推荐将注册方法写到登录的类中。

public class Login { private HashMap<String, User> hashMap = new HashMap<>(); /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public Login() { hashMap.put("张三1", new User("张三1", "123", "小学")); hashMap.put("张三2", new User("张三2", "123", "小学")); hashMap.put("张三3", new User("张三3", "123", "小学")); hashMap.put("李四1", new User("李四1", "123", "初中")); hashMap.put("李四2", new User("李四2", "123", "初中")); hashMap.put("李四3", new User("李四3", "123", "初中")); hashMap.put("王五1", new User("王五1", "123", "高中")); hashMap.put("王五2", new User("王五2", "123", "高中")); hashMap.put("王五3", new User("王五3", "123", "高中")); } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public boolean setNot(String account, String kind) { // 切换为小学、初中、高中,否则返回true if (kind.equals("小学")) { hashMap.get(account).setKind(kind); return false; } else if (kind.equals("初中")) { hashMap.get(account).setKind(kind); return false; } else if (kind.equals("高中")) { hashMap.get(account).setKind(kind); return false; } else { return true; } } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public void setUserKind(String account, String kind) { Scanner scanner = new Scanner(System.in); while (true) { Boolean temp = setNot(account, kind); if (!temp) { break; } else { System.out.println("请输入小学、初中和高中三个选项中的一个"); kind = scanner.next(); } } } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ //修改密码 public void updatePassword(String account) { System.out.println("请输入新密码"); Scanner scanner = new Scanner(System.in); String newPassword = ""; while (true) { newPassword = scanner.nextLine(); if (this.hashMap.get(account).getPassword().equals(newPassword)) { System.out.println("密码不能与旧密码相同,请重新输入"); } else { this.hashMap.get(account).setPassword(newPassword); System.out.println("密码修改成功!"); break; } } } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ // 用户登录 public User userLogin(String account, String password) { User user = hashMap.get(account); if (user != null && user.getPassword().equals(password)) { System.out.println("登录成功,当前选择为" + user.getKind() + "出题"); return user; } else { //System.out.println("请输入正确的用户名和密码"); return null; } } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ // 登录后 用户选择想做的操作并传参 public boolean choose(String account, String password) { User user = userLogin(account, password); if (user == null) { // 登录失败 System.out.println("请输入正确的用户名、密码"); return false; } Scanner scanner = new Scanner(System.in); while (true) { System.out.println("准备生成" + user.getKind() + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录;输入-2将退出程序;输入0可以修改密码):"); String input = scanner.nextLine(); if (input.contains("切换为")) { String newUserKind = input.substring(3); this.setUserKind(account, newUserKind); } else { try { int digit = Integer.parseInt(input); if (digit == -1) { // 输入-1退出登录 break; } else if (digit == -2) { // 输入-2退出程序 return true; } else if (digit >= 10 && digit <= 3000) { user.makePaper(digit); } else { if (digit == 0) { this.updatePassword(account); continue; } System.out.println("输入的题目数量不在10-30之间!!!"); } } catch (NumberFormatException e) { System.out.println("请输入10-30之间的自然数 或 输入-1退出登录 或 输入-2退出程序 或 切换为XX"); } } } scanner.close(); return false; } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public void regist() { Scanner scanner = new Scanner(System.in); System.out.println("开始注册,请输入用户名"); String account; // 判断是否存在用户 while (true) { account = scanner.nextLine(); if (hashMap.get(account) != null) { System.out.println("已存在该用户名,请重新输入用户名"); } else { break; } } System.out.println("请输入密码"); String password = scanner.nextLine(); System.out.println("请输入类型(小学,初中,高中任选一个)"); String kind = scanner.nextLine(); User newUser = new User(account, password, kind); hashMap.put(account, newUser); System.out.println("注册成功,请重新登录"); } }
Paper类
Paper类里面实现算式查重功能并且实现了生成小初高试卷的功能,在生成小初高试卷的方法中会去调用生成算式的方法,并且同时对每一个算式进行查重。但是这部分生成试卷的代码过于冗余复杂,可以采用更简单的逻辑,直接定义三个类来分别生成小初高的一道算式,然后利用另外一个生成试卷的类来循环生成试题并且进行查重,将符合要求的算式直接添加到文件当中即可。

public class Paper { private void primaryQuestion(PrimaryQuestionMaker questionMaker, File file, int questionNumber) { Random random = new Random(); StringBuffer stringBuffer = new StringBuffer(); // 生成一道小学难度的题目 questionMaker.makeQuestion(random, stringBuffer); // 存储题目 String topic = stringBuffer.toString(); // 题目查重 if (this.check(file, topic)) { // 重复生成递归生成 primaryQuestion(questionMaker, file, questionNumber); return; } // 写入试卷 Writer writer = new Writer(); writer.writeIn(file, questionNumber, topic); } private void juniorQuestion(JuniorQuestionMaker questionMaker, File file, int questionNumber) { Random random = new Random(); StringBuffer stringBuffer = new StringBuffer(); // 生成一道初中题目 questionMaker.makeQuestion(random, stringBuffer); // 存储题目 String topic = stringBuffer.toString(); // 题目查重 if (this.check(file, topic)) { // 重复生成 juniorQuestion(questionMaker, file, questionNumber); return; } // 写入试卷 Writer writer = new Writer(); writer.writeIn(file, questionNumber, topic); } private void seniorQuestion(SeniorQuestionMaker questionMaker, File file, int questionNumber) { Random random = new Random(); StringBuffer stringBuffer = new StringBuffer(); // 生成一道高中题目 questionMaker.makeQuestion(random, stringBuffer); // 存储题目 String topic = stringBuffer.toString(); // 题目查重 if (this.check(file, topic)) { // 重复生成 seniorQuestion(questionMaker, file, questionNumber); return; } // 写入试卷 Writer writer = new Writer(); writer.writeIn(file, questionNumber, topic); } // 生成小学难度题目 生成数量是number /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public void primaryMaker(int number, File file) { PrimaryQuestionMaker primaryQuestionMaker = new PrimaryQuestionMaker(); // 生成多少道题目就执行多少次循环 for (int i = 0; i < number; i++) { primaryQuestion(primaryQuestionMaker, file, i + 1); } } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public void juniorMaker(int number, File file) { JuniorQuestionMaker juniorQuestionMaker = new JuniorQuestionMaker(); // 生成多少道题目就执行多少次循环 for (int i = 0; i < number; i++) { juniorQuestion(juniorQuestionMaker, file, i + 1); } } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public void seniorMaker(int number, File file) { SeniorQuestionMaker seniorQuestionMaker = new SeniorQuestionMaker(); // 生成多少道题目就执行多少次循环 for (int i = 0; i < number; i++) { seniorQuestion(seniorQuestionMaker, file, i + 1); } } /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public boolean check(File file, String topic) { // 创建一个集合来存储已经出现的题目 Set<String> usedTopics = new HashSet<>(); // 读取指定文件并检查重复题目 try { FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader); String usedTopic; while ((usedTopic = bufferedReader.readLine()) != null) { // 以空格为分隔符,获取题目部分 String[] topicPortion = usedTopic.split(" "); if (topicPortion.length > 1) { String currentTopic = topicPortion[1]; if (currentTopic.equals(topic)) { System.out.println("重复"); // 发现重复题目,直接返回true return true; } // 将题目添加到集合中 usedTopics.add(currentTopic); } } } catch (IOException e) { e.printStackTrace(); } // 没有发现重复题目 return false; } }
生成小学算式类

public class PrimaryQuestionMaker implements QuestionMaker { private String[] token = {"+", "-", "*", "/"}; @Override public void makeQuestion(Random random, StringBuffer stringBuffer) { String question = new String(); while (true) { question = ""; // 记录数字数量 int length = 0; // 记录括号数量 int backet = random.nextInt(5); int left = 0; int gap = 0; while (true) { if (random.nextInt(2) == 0 && backet > 0) { question += "("; backet--; left++; gap = 0; } int num = random.nextInt(100) + 1; question += String.valueOf(num); gap++; if (random.nextInt(2) == 0 && left > 0 && gap >= 2) { question += ")"; left--; } if (length >= 2 && backet <= 0 && left <= 0) { break; } question += this.token[random.nextInt(4)]; length++; } if (length < 5 && gap < 4) { break; } } question += "="; stringBuffer.append(question); } }
生成初中算式类

public class JuniorQuestionMaker implements QuestionMaker { private String[] token = {"+", "-", "*", "/", "^2", "√"}; @Override public void makeQuestion(Random random, StringBuffer stringBuffer) { String question = new String(); while (true) { question = ""; int length = 0; // 初始化平方根号个数大于1的随机数 int sym = random.nextInt(2) + 1; // 表示括号个数,随机化 int bracket = random.nextInt(5); // 表示左括号多余的个数 int left = 0; // 表示左右括号之间的跨度 int gap = 0; while (true) { // 随机加"(" if (random.nextInt(2) == 0 && bracket > 0) { question += "("; bracket--; left++; gap = 0; } gap++; int num = random.nextInt(100) + 1; if (random.nextInt(2) == 0) { if (random.nextInt(2) == 0) { // 1/2的概率加入平方 question += (String.valueOf(num) + token[4]); } else { // 1/2的概率加入根号 question += (token[5] + String.valueOf(num)); } sym--; } else { // 1/2的概率还是加入数字 question += String.valueOf(num); } // 随机加”)“ if (random.nextInt(2) == 0 && left > 0 && gap >= 2) { question += ")"; left--; } // 括号中间至少含有2个操作数 if (length >= 2 && sym <= 0 && bracket <= 0 && left <= 0) { break; } // 随机运算符号 question += token[random.nextInt(4)]; length++; } // 操作数超过5个,跳出循环 if (length < 5 && gap < 5) { break; } } question += "="; stringBuffer.append(question); } }
生成高中算式类

public class SeniorQuestionMaker implements QuestionMaker { private String[] token = {"+", "-", "*", "/", "^2", "√", "sin", "cos", "tan"}; @Override public void makeQuestion(Random random, StringBuffer stringBuffer) { String question = new String(); while (true) { question = ""; int length = 0; int sym = 1; int sym2 = random.nextInt(3) + 1; //初始化三角函数个数大于1的整数 int bracket = random.nextInt(5); int left = 0; int gap = 0; while (true) { if (random.nextInt(2) == 0 && bracket > 0) { question += "("; bracket--; left++; gap = 0; } gap++; int num = random.nextInt(100) + 1; if (random.nextInt(3) == 0) { //1/3的概率加入平方根号 if (random.nextInt(2) == 0) { question += (String.valueOf(num) + token[4]); } else { question += (token[5] + String.valueOf(num)); } sym--; } else { //1/2的概率加入三角函数 if (random.nextInt(2) == 0) { int symloc = random.nextInt(3) + 6; question += (token[symloc] + String.valueOf(num)); sym2--; } else { //1/2的概率加入普通符号 question += String.valueOf(num); } } if (random.nextInt(2) == 0 && left > 0 && gap >= 2) { question += ")"; left--; } if (length >= 2 && sym <= 0 && sym2 <= 0 && bracket <= 0 && left <= 0) { break; } question += token[random.nextInt(4)]; length++; } if (length < 5 && gap < 5) { break; } } question += "="; stringBuffer.append(question); } }
QuestionMaker接口
分别用小初高Maker来实现这个接口,屏蔽实现,方便维护。实现可以随便改,入参和返回值和约定一致就可以,大项目对接时比较方便扩展和多继承。
public interface QuestionMaker { public void makeQuestion(Random random, StringBuffer stringBuffer); }
Main
主界面,包括登录和注册两种功能,存在问题是,只对选择注册时进行了判断,但是登录功能是通过while(true)循环进入的,因此输入其他不在可选范围内的数字时也会直接天转到登录界面,解决方法是可以做一个switch语句的选择,对于非选项内容的输入就输出一个无效输入,并要求重新进行选择输入。

public class Main { /** *Multiple lines of Javadoc text are written here, *wrapped normally... */ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); Login login = new Login(); System.out.println("请输入您的操作:1-登录,2-注册"); int choice = scanner.nextInt(); // 吸收换行符 scanner.nextLine(); if (choice == 2) { login.regist(); } while (true) { System.out.println("请输入用户名、密码"); String username; String password; while (true) { String[] input = scanner.nextLine().split("\\s+"); if (input.length != 2) { System.out.println("请按照正确格式输入用户名,密码"); } else { username = input[0]; password = input[1]; break; } } boolean exitProgram = login.choose(username, password); if (exitProgram) { System.out.println("程序已退出。"); break; } else { System.out.println("重新登录..."); } } scanner.close(); } }
三、功能及完成情况测试
1、登录
1.1测试正确输入
1.2测试错误输入,但格式正确:两次提醒设计的不规范
修改意见:将Login类中一条输出语句“请输入正确的用户名和密码”删除。
1.4测试输入非选项内的数字:出现BUG,输入其他数字也能跳转到登录选项,分析代码发现是因为采用while(true)循环进入的登录选项,因此无论如何都会进入该选项
修改意见:将登录、注册和其他无效的选择放到一个while循环嵌套switch语句的结构体中,保证输入其他选项失败,触发重新输入操作。
2、出题
2.1测试小学试题,分别测试输入范围正确和不正确两种情况
2.2测试生成小初高试题:生成的试题符合逻辑且满足要求
2.3测试切换为xx:切换功能正确,且可以重新切换回默认难度
2.4测试一次登录多次出题
3、查重
3.1查重无法测试,直接分析代码,发现代码逻辑正确,相关的函数调用合理。
public boolean check(File file, String topic) { // 创建一个集合来存储已经出现的题目 Set<String> usedTopics = new HashSet<>(); // 读取指定文件并检查重复题目 try { FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader); String usedTopic; while ((usedTopic = bufferedReader.readLine()) != null) { // 以空格为分隔符,获取题目部分 String[] topicPortion = usedTopic.split(" "); if (topicPortion.length > 1) { String currentTopic = topicPortion[1]; if (currentTopic.equals(topic)) { System.out.println("重复"); // 发现重复题目,直接返回true return true; } // 将题目添加到集合中 usedTopics.add(currentTopic); } } } catch (IOException e) { e.printStackTrace(); } // 没有发现重复题目 return false; }
4、切换
4.1测试退回主页:退回主页时出现BUG,分析原因是因为提前关闭了输入流,导致重新登陆时无法接受字符串的输入。
修改意见:将登录方法choose下的scanner.close()删除掉即可。
4.2测试切换难度不符合要求时的处理,已经在2.3中做出过相应测试,不再赘述。
5、保存
保存为绝对路径,文件生成名称和要求
6、检查代码规范
使用checkstyle工具加自己检查发现代码符合google编码规范
7、设计合理检查
类数量不等于1,且有一个接口,无方法代码行数大于40行。
8、对于其他外加功能的检查
8.1测试注册,当按照附件格式设置用户名和密码时,注册结果正确
8.2当用户名中间加入空格时,出现BUG,分析代码是因为重新登录时会将输入的用户名和密码按照空格切割开,因此用户名总是被切割成两部分,后一部分被当成输入的密码,而存储用户新的map中存储的用户名包含空格,密码也非空格之后的用户名,因此,会出现用户名和密码永远也输入不正确的情况
修改意见:输入的用户名和密码不要同时输入,一输入就将用户名赋值给一个字符串,接着输入密码,同样立马赋值给一个字符串,这样就不用调用按照空格切割的函数,自然不会产生上述问题。
8.3用户注册查重:查重功能正确
8.4输入选项外的年级:出现错误,原因是没有增加对于输入是否符合要求的判断
8.5修改密码:功能正确
8.6退出程序
代码结构
四、总结评价
代码优点:
1、通过各种方法相互调用来实现功能,简化了程序,提升了代码的复用性。具体体现在生成试题和生成文件的几个操作上。
2、生成小初高算式过程简单易懂,逻辑清晰明了,虽然使用纯模拟的方法,但是代码书写流畅,注释得精辟。
3、代码捕获了可能抛出的IOException异常并进行了处理,通过打印堆栈跟踪信息,有助于诊断问题。虽然没有采取更复杂的异常处理策略,但这是一个良好的起点
代码缺点:
1、退出到主界面、登录输入范围外选项、注册功能上还存在一些小的问题,而造成这些问题的原因多半是因为测试不到位导致的,队友出错的地方也并非逻辑上不可修复的问题,而都是一些简单的关闭输入流、switch选择、判断输入是否合法的问题。
2、一些提示信息设计不符合逻辑,会给使用人员带来困扰。
3、一些不必要的地方也使用方法调用使得逻辑复杂,对于代码的可读性带来了很大的影响。
总得来说,任务完成度较高,出现的问题也较为低级,属于粗心导致,添加的一些功能也很有自己的想法,从她的代码中我也学到了很多,整体上讲是一个很好的项目。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】