HNU结对编程队友代码分析

结对编程队友代码分析


项目需求


用户:小学、初中和高中数学老师。

功能:

1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;

2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;

3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准);

4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;

5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;

image

代码分析

队友的代码中有三个类,User类,Producer类,Server类。主函数在Server类中,通过调用在类中的函数实现各个功能模块。
User类:User()        传入的用户信息的初始化
Server类:userInit()       已有用户的初始化
     login()       用户登录
     startFunction()   获取题目类型、数量
Producer类:txtProduce()   生成试卷
     getPast()      获取以前的题目放入哈希表
     stringProduce()   生成算式

功能实现的流程如下:

①主函数中调用函数进行初始化以及登录

public static void main(String[] args) {
	userInit();
	login();
}

②初始化已有用户信息User类,存入Server类的user类型的成员数组中

public static void userInit() {
    users = new ArrayList<>();
    
    users.add(new User("小学","张三1","123"));
    users.add(new User("小学","张三2","123"));
    users.add(new User("小学","张三3","123"));
    
    users.add(new User("初中","李四1","123"));
    users.add(new User("初中","李四2","123"));
    users.add(new User("初中","李四3","123"));
    
    users.add(new User("高中","王五1","123"));
    users.add(new User("高中","王五2","123"));
    users.add(new User("高中","王五3","123"));
}

③登录时调用login(), 不断循环,获取输入的用户名和密码,与数组中的user进行匹配

匹配成功则进入下一步,匹配不成功则会一直在循环中不断获取输入的用户名和密码,直到匹配成功,即进入登录状态,从而执行下一步;

public static void login() {
    while (true) {
        //输入
        currentUser.type = "";  //type是一个输入是否成功的标志,必须初始化
        String tmpUsername = "", tmpPassword = "";
        System.out.println("请输入用户名和密码");
        Scanner in = new Scanner(System.in);
        String line = in.nextLine();
        int pos = line.indexOf(" ");
        if (pos >= 0) {
            tmpUsername = line.substring(0, pos);
            tmpPassword = line.substring(pos + 1);
        }
        
        //判断是否正确
        for (User user : users) {
            //查找有无匹配
            if (user.username.equals(tmpUsername) && user.password.equals(tmpPassword)) {
                currentUser.type = user.type;
                currentUser.password = user.password;
                currentUser.username = user.username;
            }
        }
        //错误处理和跳出循环
        if (currentUser.type == "") {
            System.out.println("请输入正确的用户名、密码");
        } else {
            System.out.println("当前选择为"+currentUser.type+"出题");
            startFunction();
            break;		
        }
    }
}

④在登录函数中匹配成功后,调用确认用户需要的题目类型以及数量

这里是根据用户的几种输入分成了四个分支,对每一个分支进行具体的处理;根据输入:

-1:退出登录,进行重新登录,调用login()函数,即回到步骤③中;

切换为...:格式正确,则类型切换,设置标志位,重新进入循环,继续获取输入;格式不正确,打印提示信息后,设置标志位,重新进入循环,继续获取输入;

整数:数量正确:生成题目(进入下一个步骤),待完成返回后,再次进入循环(即再次从步骤④开始),进行新一轮的用户要求的获取;数量不正确:打印信息后,重新进入循环,获取用户的题目要求;

public static void startFunction() {
    int num;  //生成题目数量
    boolean flag = true;//标志是否需要输出“准备生成...”
    String type = currentUser.type; //因为可能修改,另存一个type
    while(true) {
        if (flag) {
            System.out.println("准备生成" + type + "数学题目,请输入生成题目数量"
                    + "(输入-1将退出当前用户,重新登录):");
        }
        flag = true;
        
        Scanner in = new Scanner(System.in);
        String command = in.nextLine();
        //重新登陆
        if (command.equals("-1")) {
            login(); 
            return;
        } else if (command.contains("切换为")) {	//切换
            if (command.equals("切换为小学")) {
                type = "小学";
            } else if (command.equals("切换为初中")) {
                type = "初中";
            } else if (command.equals("切换为高中")) {
                type = "高中";
            } else {
                System.out.println("请输入小学、初中和高中三个选项中的一个");
                flag = false;
            }
        }
        //生成卷子
        else if (command.matches("\\d+")) {
            num = Integer.valueOf(command);
            if (num >= 10 && num <= 30) {
                String name = Producer.txtProduce(type, num);
                System.out.println("生成完毕,试卷已经保存在相应文件夹中,文件名:" + name);
            } else {
                System.out.println("请输入10~30之间的数字");
                flag = false;
            }
        } else {
            System.out.println("没用该指令,请重新输入");
        }
    }
}

⑤在上一步骤成功获取题目类型和数量后,调用Producer.txtProduce(),根据传入题目类型和数量,生成试卷;

这个函数中首先是调用Producer.getpast(),获取之前的题目,将它们加入Producer中的哈希表,用于之后的查重;之后,建立路径,生成文件名,创建文件,尝试向创建的文件放入从Producer.stringProduce()中获取生成的题目,并将题目放入哈希表中,便于查重;

public static String txtProduce(String _type, int num) {
    type = _type;
    
    //绝对路径 
    dir = "C:\\Users\\渔羊\\Desktop\\" + currentUser.username + "\\";
    //相对路径
    //dir = "pages/"  + currentUser.username + "/";
    
    getPast();  //获得之前的题目放到哈希表中
    
    Calendar time = Calendar.getInstance();
    int year = time.get(Calendar.YEAR);
    int month = time.get(Calendar.MONTH) + 1;
    int date = time.get(Calendar.DATE);
    int hour = time.get(Calendar.HOUR);
    int minute = time.get(Calendar.MINUTE);
    int second = time.get(Calendar.SECOND);
    //生成文件路径
    String name = String.valueOf(year)+"-"+String.valueOf(month)+"-"+String.valueOf(date)+
            "-"+String.valueOf(hour)+"-"+String.valueOf(minute)+"-"+String.valueOf(second)+".txt";
    String path = dir + name;
    try {
        FileWriter filewriter = new FileWriter(path, true);  //追加
        for (int i = 0; i < num; i++) {
            String content = stringProduce(); 
            String line = String.valueOf(i + 1) + ") " + content;
            filewriter.write(line + "\n" + "\n");
            filewriter.flush();
            //System.out.println(line);
            //新生成的题目加入哈希表
            pastProblem.add(content);
        }
        filewriter.close();
    } catch (IOException e) {
        e.printStackTrace();
    }	
    return name;
}

//获得以前的题目放入哈希表
static void getPast() {
    File files = new File(dir);  //获取目录下的file
    if (!files.exists()) {
        files.mkdir();
    }
    
    File[] fileList = files.listFiles();
    //遍历文件list,放入哈希表
    for (File f : fileList) {
        if (!f.isDirectory()) {  //判断:非目录
            try {
                String line;  //存储行字符串
                BufferedReader br = new BufferedReader(new FileReader(f));
                while((line = br.readLine()) != null) {
                    String content;  //提取题目
                    int pos = line.indexOf(" ");
                    content = line.substring(pos + 1);
                    pastProblem.add(content);
                }
                br.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

⑥Producer.stringProduce()在生成题目后,会利用哈希表查重,不重复才会返回生成的题目

括号的生成:随机加入左括号,设置标志位,在同一个操作数前后不同时出现左右括号,实时记录左括号位置,在标志位许可,且左括号数量大于零时,随机加入右括号,在表达式的最后,将剩余为加入的右括号全部加入;
初中与高中的高级运算符的加入:才前numCount-1个操作数中随机加入高级操作符,加入时要改变其标志位,如果在之前始终未加入过(通过相关标志位判断),那么就在最后一个操作数位置加入一个随机的高级运算符,以确保满足需求中的至少有一个高级运算符;

static String stringProduce() {
    String problem = "";
    do {
        problem = "";
        String[] operator = {"+", "-", "*", "/", "^2", "√", "sin", "cos", "tan"};
        Random random = new Random();
        boolean flagOp = false;  //记录是否有必须的运算符
        int num = 0;
        if (type == "小学") {
            num = random.nextInt(4) + 2; //操作数个数:区间[2, 6)
        }
        else {
            num = random.nextInt(5) + 1; //操作数个数:区间[1, 6)
        }
        int numLeft = 0;  //记录无匹配的左括号数目
    
        //前面num-1个操作数
        for (int i = 0; i < num - 1; i++) {	
            boolean flagLeft = false; 
            //左括号, 1/3概率加入
            if (i != 0) {
                if (random.nextInt(3) == 1) {
                    problem += "(";
                    numLeft++;
                    flagLeft = true;
                }
            }
            
            //操作数单元(包含高阶运算符的操作数)
            String cell;
            String op = "";
            String strNum = String.valueOf(random.nextInt(100) + 1);  //操作数
            if (type == "初中") {
                if (random.nextInt(2) == 1) {  // 1/2概率加入高阶运算符
                    op = operator[random.nextInt(2) + 4]; 
                    flagOp = true;
                }
            }
            else if (type == "高中") {
                if (random.nextInt(3) == 1) {  // 1/3概率
                    op = operator[random.nextInt(2) + 4]; 
                }
                else if (random.nextInt(2) == 1) {  // 1/3概率
                    op = operator[random.nextInt(3) + 6]; 
                    flagOp = true;
                }
            }	
            if (op == "^2") {
                cell = strNum + op;
            }else {
                cell = op + strNum;		
            }
            problem += cell;
            
            //右括号,注意左右括号不能都加
            if (numLeft > 0 && flagLeft == false) {
                if (random.nextInt(2) == 1) {
                        problem += ")";
                        numLeft--;
                }
            }
            
            //运算符
            problem += operator[random.nextInt(4)];  				
        }
        
        //最后一个cell
        //操作数单元(包含高阶运算符的操作数)
        String cell;
        String op = "";
        String strNum = String.valueOf(random.nextInt(100) + 1);  //操作数
        if (type == "初中") {
            if (flagOp == false) {
                op = operator[random.nextInt(2) + 4]; 
            }
        }
        else if (type == "高中") {
            if (flagOp == false) { 
                op = operator[random.nextInt(3) + 6]; 
            }
        }	
        if (op == "^2") {
            cell = strNum + op;
        }
        else {
            cell = op + strNum;		
        }
        problem += cell;
        
        //补完右括号
        for (;numLeft > 0; numLeft--) {
            problem += ")";
        }			
    } while (pastProblem.contains(problem));
    return problem;
}

运行测试

类型切换及数量获取的分支判断

请输入用户名和密码
张三1 123
当前选择为小学出题
准备生成小学数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):
切换为初中
准备生成初中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):
30
生成完毕,试卷已经保存在相应文件夹中,文件名:2022-9-13-10-32-37.txt

登录并直接获取正确数量的题目

准备生成初中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):
切换
没用该指令,请重新输入
准备生成初中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):
切换为高
请输入小学、初中和高中三个选项中的一个
切换为高中
准备生成高中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):
40
请输入10~30之间的数字
30
生成完毕,试卷已经保存在相应文件夹中,文件名:2022-9-13-10-33-4.txt

在用户进行类型切换时,能够正确处理错误的输入;

准备生成小学数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):
-1
请输入用户名和密码

输入“-1”时,能够退出登录

文件的创建

image

在正确的路径上创建文件,文件名符合要求;

题目的生成

image

生成的题目符合小初高的不同要求

题目的查重

为了实现查重功能的验证,利用我在自己的代码中验证查重功能的方法,在代码中加入以下测试代码:

boolean testFlag = false;
do {
	if(testFlag) {
		problem = "";
		········

设置标志位,在尝试加入已有题目后,标志位设置为true,开始运行原来的题目生成代码

	······
} else {
	problem = "11+35*64-6-38";
	testFlag = true;
}

从之前生成过并加入到文件的题目中选取一道,尝试加入正在生成的试卷,若无法加入,则说明查重功能实现;这里是用了张三1文件夹中保存的一道小学题,运行结果:

image

可以看到,之后生成的题目中没有这道题(因为测试代码在一开始就尝试加入旧题目,所以第一题不是旧题目,就说明查重功能实现);

优缺点分析

优点

  • 在创建文件时,使用了相对路径,代码可移植性较好;
  • 功能实现思路清晰,层次分明,具体的功能在Producer和Server中实现;其中,Server主要实现与用户的交互,通过用户输入获取题目类型及数量,同时判断用户输入是否合法,将筛选出的信息传入Producer中进行下一步处理;Producer主要根据传入的参数,实现文件创建、查重、题目生成等核心功能;
  • 代码简洁、明了;在登录、需求获取等于用户交互的操作模块,设置循环;在每一次循环中,根据用户可能的输入设置相应的分支,针对不同分支再进行不同的处理;循环会根据需要设置几个标志位,每一次的处理都会改变相应的标志位,从而为下一次的分支判断提供条件;
  • 变量名和方法名使用驼峰命名法,符合编程规范;

缺点

  • 括号的生成方面:由于为了避免计算式最前和最后加上括号,所以在加入括号时,不在最前方加入括号,即不会生成最前方有括号的情况;由于每次只会插入一个左括号,所以也不会生成左括号连用的情况,例如,不会生成((1+1)+2)这种式子;
  • 利用Calendar.HOUR生成的时间是12小时制,如果上下午相隔12小时创建文件,会出现文件的重名;
  • 类中的变量及方法都是public的,缺乏安全性
  • 代码规范方面:块缩进应为两个空格;在调用静态对象时最好使用类名调用静态的类成员;源文件编码格式未设置为UTF-8;
posted @ 2022-09-13 23:44  火火火日立  阅读(545)  评论(0编辑  收藏  举报