目录
一. 简介
二. 项目要求
三. 代码分析
四. 功能测试
五. 优缺点总结
一. 简介
本篇博客为对廖心怡同学的个人编程项目 “中小学数学卷子自动生成程序” 的分析与总结,在阅读代码的过程中学习到了许多优点,也发现了一些代码书写、代码结构等方面的不足,希望可以对该同学接下来的学习有所帮助。
二. 项目要求
面向用户:
- 小学、初中和高中数学老师
用户登录:
- 命令行输入账号和密码,中间用空格隔开;
- 输入错误或用户不存在,给出提示 “请输入正确的用户名、密码” ;
- 输入正确即输入用户存在显示“当前选择为XX出题”(XX为小学、初中和高中三个选项中的一个)。
生成试卷:
- 登录后,系统提示 “准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”;
- 用户输入所需出的卷子的题目数量,每道题目的操作数在1-5个之间,操作数取值范围为1-100;
- 题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录);
- 同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复。
切换类型:
- 登录状态下,如果用户需要切换类型选项,命令行输入 “切换为XX”;
- 输入不符合要求时,程序控制台提示 “请输入小学、初中和高中三个选项中的一个”;
- 输入正确后,显示系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,系统新设置的类型进行出题。
文件要求:
- 生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹;
- 每道题目有题号,每题之间空一行。
三. 代码分析
整体架构:
该项目共有八个java文件,包含七个类一个接口:题目生成类StyleChange、小学试卷类Primary、初中试卷类Junior、高中试卷类Senior、试卷生成接口Function、用户类User、登录界面类Login、主函数所在类Main,对类进行提取和功能封装,做到了模块化。
核心代码:
StyleChange.java
该类包含六个函数,方法有切换类型函数changeStyle、生成题目函数doPapersOut,以及三个不同类型的生成题目函数。
三个不同类型的生成题目函数结构基本相同,分析随机生成题目函数doSeniorPapers:首先生成一个操作符,如果是“(”则加入题目,再获取三角函数操作数,有1/2概率加入题目,然后再添加一个操作数;之后随机生成除+ - * /外的特殊操作符数量,for循环随机生成特殊操作符,如果是“(”或“)”则根据情况添加,其余的特殊操作符均有1/2的概率加入,再添加操作数,每次for循环加入+ - * /中的一个,这样for循环结束便生成了一个题目。思路清晰,易于理解,生成的题目也能较好的满足要求。
存在的问题是,没有考虑一个操作数带括号和整个题目带括号的情况。
1 public String doSeniorPapers() { 2 Random random = new Random(); 3 StringBuilder problem = new StringBuilder(); 4 String operator = operatorList.get(random.nextInt(6)); 5 int leftbraket = 0; //左括号个数 6 int sympleNum = 0; 7 if (operator.equals("(")) { 8 problem.append(operator); 9 leftbraket++; 10 } 11 if (random.nextBoolean()) { 12 problem.append(operatorList.get(random.nextInt(3)+8)); 13 sympleNum++; 14 } 15 problem.append(random.nextInt(100)+1); 16 operator = operatorList.get(random.nextInt(11)); 17 if (operator.equals(")") && leftbraket!=0) { 18 problem.append(operator); 19 leftbraket--; 20 } 21 int operatorNum = random.nextInt(4) + 1; 22 for(int i=0; i<operatorNum; i++) { 23 problem.append(operatorList.get(random.nextInt(4))); 24 operator = operatorList.get(random.nextInt(11)); 25 if (operator.equals("(")) { 26 problem.append(operator); 27 leftbraket++; 28 } 29 if (random.nextBoolean()) { 30 problem.append(operatorList.get(random.nextInt(3)+8)); 31 sympleNum++; 32 } 33 if (i==operatorNum-1 && sympleNum==0) { 34 problem.append(operatorList.get(random.nextInt(3)+8)); 35 } 36 problem.append(random.nextInt(100)+1); 37 operator = operatorList.get(random.nextInt(11)); 38 if (operator.equals(")") && leftbraket!=0) { 39 problem.append(operator); 40 leftbraket--; 41 } 42 } 43 for (int i = 0; i<leftbraket; i++) { 44 problem.append(")"); 45 } 46 return problem.toString(); 47 }doSeniorPapers
Primary.java、Junior.java、Senior.java
三各类包含的方法相同,试卷生成界面outPapers、写入文件函数writeFile、查重函数checkRepeat。
outPapers函数根据用户类型和输入的题目数量生成试卷。但是该函数同时具有类型切换功能,存在不合理性。
1 public void outPapers(User user, int questionNum, String grade) { 2 Scanner sr = new Scanner(System.in); 3 if (questionNum >= 10 && questionNum <= 30) { 4 StyleChange output = new StyleChange(); 5 writeFile(user, questionNum, grade, output); 6 System.out.println("题目已生成,请查看!"); 7 System.out.println("是否切换用户类型?"); 8 if (sr.next().equals("是")) { 9 output.changeStyle(user); 10 } else { 11 System.out.println("已退出登录!!"); 12 } 13 } else if (questionNum == -1) { 14 System.out.println("已退出登录!!"); 15 System.out.println("请输入用户名、密码:"); 16 Login login = new Login(); 17 login.loginSystem(sr.next(), sr.next()); 18 } else { 19 System.out.println("请输入范围为10-30的题目数量:"); 20 outPapers(user, sr.nextInt(), user.getGrade()); 21 } 22 }outPapers
writeFile函数是生成试卷,使用了一系列文件生成与书写的函数,运用了try-catch-finally结构,代码完整清晰。
1 public void writeFile(User user, int questionNum, String grade, StyleChange output) { 2 String filePath = "D:/mathPapers/" + user.getGrade() + "/" + user.getUserName(); 3 Date date = new Date(); 4 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss"); 5 String childPath = sdf.format(date) + ".txt"; 6 FileWriter fw = null; 7 try { 8 File file = new File(filePath, childPath); 9 if (!file.exists()) { 10 file.createNewFile(); 11 } 12 fw = new FileWriter(filePath + "/" + childPath); 13 BufferedWriter bw = new BufferedWriter(fw); 14 int tcount = 0; // 题目编号 15 while (true) { 16 tcount++; 17 if (!checkRepeat(file, output.doPapersOut(grade))) { 18 bw.write(tcount + ":" + output.doPapersOut(grade) + "=\n"); 19 bw.write("\n"); 20 questionNum--; 21 } 22 if (questionNum == 0) { 23 break; 24 } 25 } 26 bw.close(); 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } finally { 30 try { 31 fw.close(); 32 } catch (Exception e) { 33 e.printStackTrace(); 34 } 35 } 36 37 }writeFile
checkRepeat函数对检查生成的题目是否在为该用户生成过,方法为遍历用户的每一个试卷文件,对每一个试卷文件遍历其每一行,获取题目,与生成题目进行比对,生成过则返回true,否则返回false。
1 public boolean checkRepeat(File file, String problem) { 2 boolean repeatOrNot = false; 3 String path = file.getParent(); 4 File parentFile = new File(path); 5 File[] files = parentFile.listFiles(); 6 for (int i = 0; i < files.length; i++) { 7 try { 8 FileReader fileReader = new FileReader(files[i]); 9 BufferedReader br = new BufferedReader(fileReader); 10 while (true) { 11 String line = br.readLine(); 12 // if (line.equals("\n")) { 13 // continue; 14 // } 15 if (line == null) { 16 break; 17 } 18 String[] testLine = line.split(":"); 19 if (testLine.length > 1) { 20 if (testLine[1].equals(problem)) { 21 repeatOrNot = true; 22 } 23 } 24 } 25 } catch (Exception e) { 26 e.printStackTrace(); 27 } 28 } 29 return repeatOrNot; 30 }checkRepeat
Function.java
试卷生成接口,包含outPapers函数,Primary、Junior、Senior类分别实现该函数。
1 interface Function { 2 public void outPapers(User user, int questionNum, String grade); 3 }interface Function
User.java
用户类,包含用户的账号、密码、类型属性和getUserName、getPassPort、getGrade获取属性方法。
1 public class User { 2 private String userName; 3 private String passPort; 4 private String grade; 5 public User(String userName, String passPort, String grade) { 6 this.userName = userName; 7 this.passPort = passPort; 8 this.grade = grade; 9 } 10 public String getUserName() { 11 return userName; 12 } 13 public String getPassPort() { 14 return passPort; 15 } 16 public String getGrade() { 17 return grade; 18 } 19 }class User
Login.java
用户登录之前通过生成Login实体加载已存在用户,存入哈希表userHashMap中,用户输入账号密码进行登录时,loginSysem函数进行检查,看输入的用户是否存在于userHashMap。
分析loginSystem函数,利用containsKey函数判断用户名及密码是否存在,如果存在则登陆成功,提示输入题目数量来生成试卷,否则重新输入。
1 public void loginSystem (String userName, String passPort) { 2 if (userHashMap.containsKey(userName)) { 3 User user = userHashMap.get(userName); 4 if (user.getPassPort().equals(passPort)) { 5 System.out.println("当前选择为" + user.getGrade() + "出题"); 6 System.out.println("准备生成" + user.getGrade() + "数学题目,请输入生成题目数量:"); 7 Scanner sr = new Scanner(System.in); 8 if (user.getGrade().equals("小学")) { 9 Function outWithPapers = new Primary(); 10 outWithPapers.outPapers(user, sr.nextInt(), user.getGrade()); 11 } else if (user.getGrade().equals("初中")) { 12 Function outWithPapers = new Junior(); 13 outWithPapers.outPapers(user, sr.nextInt(), user.getGrade()); 14 } else if (user.getGrade().equals("高中")) { 15 Function outWithPapers = new Senior(); 16 outWithPapers.outPapers(user, sr.nextInt(), user.getGrade()); 17 } 18 } else { 19 System.out.println("请输入正确的用户名和密码:"); 20 Scanner sr = new Scanner(System.in); 21 loginSystem(sr.next(), sr.next()); 22 } 23 } else { 24 System.out.println("请输入正确的用户名和密码:"); 25 Scanner sr = new Scanner(System.in); 26 loginSystem(sr.next(), sr.next()); 27 } 28 }loginSystem
Main.java
包含主函数,用来加载系统。存在的不足是,输入的账号密码是一个包含空格的字符串,应该分别提取账号密码传入loginSysem,而直接用(input.next(), input.next())传入参数会使账号和密码可以换行输入,不满足要求。
1 public class Main { 2 public static void main(String[] args) { 3 Scanner input = new Scanner(System.in); 4 System.out.println("请输入用户名、密码:"); 5 Login login = new Login(); 6 login.loginSystem(input.next(), input.next()); 7 } 8 }class Mian
四. 功能测试
按照正确格式输入正确的账号密码可以成功登录,但是没有提示输入格式“输入账号和密码,中间用空格隔开”;另外,可以看出代码书写也有问题,账号密码用空格隔开或者换行都可以成功登录。
输入有效的题目数量可以成功生成试卷,输入无效的题目数量也会给出提示。
生成的题目数量正确,每题操作数为1-5,操作数的值为1-100;但是有的题目括号里只有一个操作数,有的题目首尾用括号括起,不符合习惯。
正确输入后可以成功切换类型,之后也可以正常生成切换类型后的试卷,但是没有提示正确的输入格式;输入错误后没有给出提示,而是直接退出系统,即未对错误输入作出处理,不能连续生成试卷。
输入-1可以退出程序,但是没有给出文字描述提示该功能;要求输入-1退出登录后可以重新登录,而这里直接退出程序,不满足要求。
五. 优缺点总结
优点:
- 代码书写规范,遵守Google的Java代码规范,清晰易读;
- 对数据结构及相关函数的使用合理,程序编写简洁完善;
- 进行了类的提取和方法封装,使用接口,便于扩展;
- 题目生成基本满足要求;
- 进了异常处理操作;
- 实现了基本功能,功能完善。
缺点:
- 有的方法的功能混乱,没能进行合理的封装。比如,outPapers函数的切换类型功能单独分离出来作为一个方法更好;
- 类的方法划分不合理,有多处函数重复实现、某个函数在某个类中不合理的问题。比如,Primary、Junior、Senior类的三个方法函数完全相同,可以将三个函数提取出来封装到一个类中,再考虑这三个类与新产生的类的关系,合理划分;
- 函数调用混乱,使程序存在多处不合理的地方;比如,切换类型输入错误直接退出程序、生成一次试卷后直接询问是否切换类型而无法多次生成试卷等;
- 缺少文字提示,影响使用;比如,没有提示账号密码、切换类型的输入格式,没有提示输入-1退出登录等;
- 生成的题目规范性不强;比如,单个操作数会加上括号、整个式子会加上括号等;
- 文件使用了绝对地址,使用相对地址更好。