【HNU 个人项目互评】结对编程互评-Java

【结对编程互评-Java】中小学数学卷子自动生成程序


项目名称:中小学数学卷子自动生成程序
编程语言:Java
代码作者:张钉
评价人:张诗泉


目录


1. 项目要求

1.1 目标用户:

小学、初中和高中数学老师。

1.2 实现功能:

1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;
2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;
3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);
4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;
5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;

附表-1:账户、密码

账户类型 账户 密码
小学 张三1 123
小学 张三2 123
小学 张三3 123
初中 李四1 123
初中 李四2 123
初中 李四3 123
高中 王五1 123
高中 王五2 123
高中 王五3 123

附表-2:小学、初中、高中题目难度要求

阶段 小学 初中 高中
难度要求 +,-,*./ 平方,开根号 sin,cos,tan
备注 只能有+,-,*./和() 题目中至少有一个平方或开根号的运算1=符 题目中至少有一个sin,cos或tan的运算符

2. 代码分析

main方法:

public class AutoTestPaper{
	public static void main(String args[]) {
	    Scanner input = new Scanner(System.in);
            UserOperate loginUser = new UserOperate();
            while(true) {
        	System.out.println("请输入用户名、密码");
        	loginUser.userLogin(input.next(), input.next().trim());
	    }
    }
}

分析:调用scanner对呼出的登陆界面传参,简洁明了。

PaperMaker类:

interface ProduceRandom{
	public abstract boolean fun();
}

class PaperMake implements ProduceRandom {
	List<String> primaryMathSign = new ArrayList<>();
	// 存储高中特殊运算符
	List<String> juniorMathSign = new ArrayList<>();
	// 存储初中特殊运算符
	List<String> highMathSign = new ArrayList<>();
	// 存储基础运算符
	
	public PaperMake() {
		highMathSign.add("sin");
		highMathSign.add("cos");
		highMathSign.add("tan");
		// 高中难度题目特殊运算符
		juniorMathSign.add("√");
		juniorMathSign.add("^2");
		// 初中难度题目特殊运算符
		primaryMathSign.add("+");
		primaryMathSign.add("-");
		primaryMathSign.add("*");
		primaryMathSign.add("/");
		// 题目基础运算符
	}
	
	@Override
	public boolean fun() {
		boolean sign=false;
		Random random=new Random();
		int cnt=random.nextInt(2);
		if(cnt == 0) {
			sign = false;
		}
		else {
			sign = true;
		}
		return sign;
	}
	//重写fun方法用于括号,特殊符号随机生成
	
	public void primaryPaper(User user, int amount) {
		for(int i = 0; i < amount; i++) {
			StringBuffer tempTitle = new StringBuffer();
			Random random = new Random();
			int signAmount = random.nextInt(4) + 2;
			// 操作数的数量,范围为2-5
			int bracket = 0;
			// 括号的数量
			
			while(signAmount-- > 0) {
				if(signAmount > 1 && this.fun()) {
					tempTitle.append("(");
					bracket++;
				}
				// 判断是否加左括号
				
				int number = random.nextInt(100) + 1;
				tempTitle.append(number);
				// 加入操作数,范围为1-100
				
				// 判断是否加右括号
				if(this.fun() && bracket > 0) {
					tempTitle.append(")");
					bracket--;
				}
				
				if(signAmount == 0) {
					while(bracket-- > 0) {
						tempTitle.append(")");
					}
				}
				// 如果出题结束,补齐右括号
				
				if(signAmount > 0) {
					tempTitle.append(primaryMathSign.get(random.nextInt(4)));
				}
				// 加入基础运算符
			}
			tempTitle.append(" = ");
			// 试题完成,加入等号
			
			IOTxt txtGenerate = new IOTxt();
			File newFile = txtGenerate.makeFile(user);
			// 创建试卷文件
			String titlePaper = tempTitle.toString();
			// 类型转换为String
			if(!txtGenerate.checkTitle(newFile, titlePaper)) {
				txtGenerate.writeIn(newFile, i + 1, titlePaper);
			} else {
				i--;
			}
			// 试卷查重并写入,当重复时不写入txt不增加题目数量并重新出题
	    }
		System.out.println("当前试卷已输出完毕");
	}
	
	public void juniorHighPaper(User user, int amount) {
		for(int i = 0; i < amount; i++) {
			StringBuffer tempTitle = new StringBuffer();
			Random random = new Random();
			int signAmount = random.nextInt(4) + 2;
			// 操作数的数量,范围为2-5
			int bracket = 0;
			// 括号的数量
			
			while(signAmount-->0) {
				if(this.fun() || signAmount == 0) {
					tempTitle.append(juniorMathSign.get(0));
				}
				// 判断是否加根号
				
				if(signAmount > 1 && this.fun()) {
					tempTitle.append("(");
					bracket++;
				}
				// 判断是否加左括号
				
				int number = random.nextInt(100) + 1;
				tempTitle.append(number);
				// 加入操作数,范围为1-100
				
				if(this.fun() && bracket > 0) {
					tempTitle.append(")");
					bracket--;
					//判断是否加右括号
				}
				
				if(signAmount == 0) {
					while(bracket-- > 0) {
						tempTitle.append(")");
					}
				}
				// 如果出题结束,补齐右括号
				
				if(this.fun()) {
					tempTitle.append(juniorMathSign.get(1));
				}
				// 判断是否加平方
				
				if(signAmount > 0) {
					tempTitle.append(primaryMathSign.get(random.nextInt(4)));
				}
				// 加入基础运算符	
			}
			tempTitle.append(" = ");
			// 试题完成,加入等号
			
			IOTxt txtGenerate = new IOTxt();
			File newFile = txtGenerate.makeFile(user);
			// 创建试卷文件
			String titlePaper = tempTitle.toString();
			// 类型转换为String
			if(!txtGenerate.checkTitle(newFile, titlePaper)) {
				txtGenerate.writeIn(newFile, i + 1, titlePaper);
			} else {
				i--;
			}
			// 试卷查重并写入,当重复时不写入txt不增加题目数量并重新出题
		}
		System.out.println("当前试卷已输出完毕");
	}
	
	public void highSchoolPaper(User user, int amount) {
		for(int i = 0; i < amount; i++) {
			StringBuffer tempTitle = new StringBuffer();
			Random random = new Random();
			int signAmount = random.nextInt(4) + 2;
			// 操作数的数量,范围为2-5
			int bracket = 0;
			// 括号的数量
			
			while(signAmount-- > 0) {
				if(this.fun() || signAmount == 0) {
					tempTitle.append(highMathSign.get(random.nextInt(3)));
				}
				// 判断是否加三角函数
				
				if(this.fun()) {
					tempTitle.append(juniorMathSign.get(0));
				}
				// 判断是否加根号
				
				if(signAmount > 1 && this.fun()) {
					tempTitle.append("(");
					bracket++;
				}
				// 判断是否加左括号
				
				int number = random.nextInt(100) + 1;
				tempTitle.append(number);
				// 加入操作数,范围为1-100
				
				if(this.fun() && bracket > 0) {
					tempTitle.append(")");
					bracket--;
				}
				// 判断是否加右括号
				
				if(signAmount == 0) {
					while(bracket-- > 0) {
						tempTitle.append(")");
					}
				}
				// 如果出题结束,补齐右括号
				
				if(this.fun()) {
					tempTitle.append(juniorMathSign.get(1));
				}
				// 判断是否加平方
				
				if(signAmount>0) {
					tempTitle.append(primaryMathSign.get(random.nextInt(4)));
				}
				// 添加基础运算符
			}
			tempTitle.append(" = ");
			// 试题完成,加入等号
			IOTxt txtGenerate = new IOTxt();
			File newFile = txtGenerate.makeFile(user);
			// 创建试卷文件
			String titlePaper = tempTitle.toString();
			// 类型转换为String
			if(!txtGenerate.checkTitle(newFile, titlePaper)) {
				txtGenerate.writeIn(newFile, i + 1, titlePaper);
			} else {
				i--;
			}
			// 试卷查重并写入,当重复时不写入txt不增加题目数量并重新出题
		}
		System.out.println("当前试卷已输出完毕");
	}
}

分析:出题类实现随机数接口类。用列表来存储不同等级的运算符,可拓展性高。给不同等级做了不同的出题方法,同样增加了代码的灵活性。

IOTxt类:

class IOTxt {
	 
    // 生成用户文件夹和txt文件
    public File makeFile(User user) {
        String txtPath = "" + user.getName();
        // 相对路径,生成文件夹与src同级
        File file = new File(txtPath);
        Date date = new Date();
        // 处理时间用于txt文件的名称
        SimpleDateFormat dealwithDate = new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss");
        String title = dealwithDate.format(date) + ".txt";
        if (!file.exists()) {
            // 没有对应用户文件夹则生成
            file.mkdirs();
        }
        File paper = new File(txtPath, title);
        try {
            paper.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return paper;
    }
    
    // 写入txt文件
    public void writeIn(File file, Integer num, String title) {
        try {
            // 传入文件,设置为续写文件
            FileWriter fileWriter = new FileWriter(file, true);
            // 题目的序号
            fileWriter.append("第"+num+"道题" + ". " + title);
            // 题目间空一行
            fileWriter.append("\r\n");
            fileWriter.append("\r\n");
            // 刷新后关闭writer
            fileWriter.flush();
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	
    // 题目查重
	public boolean checkTitle(File file, String title) {
		 // 定义变量 是否重复
        boolean sign = false;
        // txt文件的上一级目录路径
        String parentPath = file.getParent();
        File parent = new File(parentPath);
        // 遍历上一级目录,得到所有试卷
        File[] allFiles = parent.listFiles();
        // 逐个文件进行查重对比
        for (int i = 0; i < allFiles.length; i++) {
            try {
                FileReader fileReader = new FileReader(allFiles[i]);
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                while (true) {
                    // 逐行读取数据
                    String usedTitle = bufferedReader.readLine();
                    // 跳过空行
                    if (usedTitle == null) {
                        break;
                    }
                    // 以空格为分隔符," "后面是题目
                    String[] realTitle = usedTitle.split(" ");
                    if (realTitle.length > 1) {
                        if (realTitle[1].equals(title)) {
                            System.out.println("题目重复");
                            sign = true;
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sign;
	}
	
}

分析:将文件保存在相对路径下,用户文件夹在对应的用户有题目时才会生成。有一处命名不规范,变量"dealwhithDate"没有使用小驼峰命名法。

User类:

class  User {
	private String name;
	private String password;
	private String grade;

	public User(String name, String password, String grade) {
		this.name = name;
		this.password = password;
		this.grade = grade;
	}

	public String getName() {
		return this.name;
	}

	public String getPassword() {
		return this.password;
	}

	public String getGrade() {
		return this.grade;
	}

	public void setGrade(String grade) {
		this.grade = grade;
	}

	public void makePaper(User user, int num) {
        PaperMake paperMake = new PaperMake();
        if (this.grade.equals("小学")) {
            paperMake.primaryPaper(user, num);
        } else if (this.grade.equals("初中")) {
            paperMake.juniorHighPaper(user, num);
        } else if (this.grade.equals("高中")) {
            paperMake.highSchoolPaper(user, num);
        }
    }

}

分析:对用户类的三个属性都进行了封装,并且加入了根据当前用户类型生成题目的方法,符合要求。

UserOperate类:

class UserOperate {
    Map<String, User> userMap = new HashMap<>();
	// 存储账号
	public UserOperate() {
		userMap.put("张三1", new User("张三1", "123", "小学"));
		userMap.put("张三2", new User("张三2", "123", "小学"));
		userMap.put("张三3", new User("张三3", "123", "小学"));
		userMap.put("李四1", new User("李四1", "123", "初中"));
		userMap.put("李四2", new User("李四2", "123", "初中"));
		userMap.put("李四3", new User("李四3", "123", "初中"));
		userMap.put("王五1", new User("王五1", "123", "高中"));
		userMap.put("王五2", new User("王五2", "123", "高中"));
		userMap.put("王五3", new User("王五3", "123", "高中"));
	}
	
	public void userLogin(String userName, String userPassword) { 
		Scanner input = new Scanner(System.in);
		User tempUser = userMap.get(userName);
		if(tempUser.getPassword().equals(userPassword)) {
			boolean output = true;
			// 用于判断当输入不合法时不会要求输出题目数量
				while(true) {
					System.out.println("当前选择为" + tempUser.getGrade() + "出题");
					if(output == true) {
						System.out.println("准备生成" + tempUser.getGrade() + "数学题目,请输入生成题目数量(10-30)");
					}
					String str = input.next();
					// 判断输入为"切换为.."或者题目数量
					if(str.contains("切换为")) {
							String givenGrade = str.substring(3);
							if(givenGrade.equals("高中") || givenGrade.equals("初中") || givenGrade.equals("小学")) {
								tempUser.setGrade(givenGrade);
								output = true;
							}
							else {
								output = false;
								System.out.println("请输入小学、初中和高中三个选项中的一个");
							}
					}
					else {
						int number = Integer.parseInt(str);
						// String到int类型转换,得到题目数量
						if(number == -1) {
							System.out.println("已退出登录,请重新登录");
							break;
						}
						// 为-1时退出登录
						else {
							if(number >= 10 && number <= 30) {
								tempUser.makePaper(tempUser, number);
							}
							else {
								System.out.println("输入范围有误,请重新输入:");
							}
						}
					}
				}
		}
		else {
			System.out.println("请输入正确的用户名、密码");
		}
	}
}

分析:
用户的初始化放在了用户操作类中,调用userLogin()登录后直接进入了用户界面。可惜的是,在对输入的处理上用了大量的if-else,导致了对非法输入的处理缺失:
比如,此处用if语句对密码进行了校验,却没有对用户名进行校验,导致输入错误的用户名时,会因为缺少处理而报错;
再比如,切换等级时,缺少对不规范输入的处理,在这种情况下也会报错。
可以考虑使用switch中的default处理各种异常输入,来解决这个问题。

分析总结:

代码结构上,代码结构合理,使用到了接口类ProduceRadom。
代码规范上,除部分地方存在例如"while(true)"/"while (true)"这样小括号前是否有括号不统一与一处变量名不规范外,代码格式与变量命名基本符合规范。
代码功能上,除了在登录和切换时缺少异常输入的处理外,能够正常实现项目要求。


3. 功能测试

3.1 登录功能测试

可以正常登录与切出账号:

能够对密码进行校验,但缺少对用户名的校验:

3.2 出题功能测试:

可以正常进行切换与出题,有对出题数量进行约束:


有对切换难度不符合要求时做处理,但缺少对不规范输入的处理:

3.3 保存与查重功能测试:

能够正确生成用户文件夹与题目文件,经查验,题目进行了查重、无重复题目出现:


4. 优缺点分析及总结

优点:

  1. 代码逻辑清晰、注释明确、格式规范,可读性很高。
  2. 代码结构合理,使用了接口类与其实现,可拓展性很好。
  3. 文件读写操作设计合理,可适应性高。
  4. 用户界面提示清晰,功能完整。

缺点:

  1. 登录时未校验用户名。
  2. 切换难度时未处理异常输入。
  3. 一处变量命名不规范。

总结:

代码整体逻辑清晰、结构合理、格式规范,具有良好的可读性、可拓展性和可适应性,功能实现完整。但在部分细节方面尚缺考虑,在优化之后未来可期。

posted @ 2023-09-19 20:45  原神大师  阅读(574)  评论(0)    收藏  举报