结对编程之个人项目代码互评

简介

这篇文章是结对编程的个人项目代码互评。

我的结对编程搭档是杨廷元同学(以下简称杨少,笑),下面就对杨少的代码进行简单的总结评价。

项目需求

  • 假想用户为小学、初中和高中数学老师;
  • 要完成的功能有:
    • 命令行输入用户名和密码,两者之间用空格隔开;
    • 用户登录后根据账户类型提示要生成的题目类型,此时用户可以输入需要的题目数量,该数量在 10 到 30 之间,包括 10 和 30,如果用户输入 -1 则退出当前账户重新登录;
    • 要将生成的题目保存到文件,以"年-月-日-时-分-秒.txt"的形式保存,每个账号一个文件夹,每道题目有题号,每题之间空一行,新生成的题目不能和已存在的重复;
    • 用户可以在登录后切换类型选项,符合要求的类型选项只有小学、初中和高中。
  • 补充说明:
    • 预置账户:

      账户类型 账户 密码
      小学 张三1 123
      小学 张三2 123
      小学 张三3 123
      初中 李四1 123
      初中 李四2 123
      初中 李四3 123
      高中 王五1 123
      高中 王五2 123
      高中 王五3 123
    • 题目难度要求:

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

代码结构

杨少的代码分了三个类,来一一分析一下。

用户类 User

/**
 * Users 用户类
 */
public class Users {

  private Map<String, String> controller = new LinkedHashMap<>();
  private Map<String, String> userPrimary = new LinkedHashMap<>();
  private Map<String, String> userMiddle = new LinkedHashMap<>();
  private Map<String, String> userHigh = new LinkedHashMap<>();

  public Map<String, String> getController() {
    return controller;
  }

  public Map<String, String> getPrimary() {
    return userPrimary;
  }

  public Map<String, String> getMiddle() {
    return userMiddle;
  }

  public Map<String, String> getHigh() {
    return userHigh;
  }

  /**
   * userInit 用户类初始化
   */
  public void userInit() {
    controller.put("Mr.Yang", "091520");
    userPrimary.put("张三1", "123");
    userPrimary.put("张三2", "123");
    userPrimary.put("张三3", "123");
    userMiddle.put("李四1", "123");
    userMiddle.put("李四2", "123");
    userMiddle.put("李四3", "123");
    userHigh.put("王五1", "123");
    userHigh.put("王五2", "123");
    userHigh.put("王五3", "123");
  }
}

可以看到这里的成员属性包括四个 Map,分别存储管理员账户以及三种类型的教师账户。类的方法包括四个属性的 get 方法和一个可以将账户放入 Map 的初始化方法。

出题类 ProblemSetting

由于代码量较大,就放出类的属性和方法声明,不贴所有方法的具体实现了。

  /* 读写对象 */
  public BufferedWriter bufferedWriter;
  /* 左括号 */
  static List<String> bracketsLeft = Arrays.asList("(", "", "(", "", "(");
  /* 四则运算符号 */
  static List<String> primary = Arrays.asList("+", "-", "*", "/");
  /* 根号 */
  static List<String> middle1 = Arrays.asList("√", "");
  /* 平方 */
  static List<String> middle2 = Arrays.asList("^2", "");
  /* 三角函数 */
  static List<String> high = Arrays.asList("sin", "sin√", "cos", "", "cos√", "tan", "tan√");
  /* 格式化时间 */
  SimpleDateFormat problemName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

成员属性包括写文件的 BufferedWriter对象、限定文件名时间格式的 SimpleDateFormat对象和各个难度的题目所涉及的符号。

  /**
   * problemWriter 写入题目
   *
   * @param bufferedWriter 读写对象
   * @param problem        题目
   */
  public void problemWriter(BufferedWriter bufferedWriter, String problem) {
    ...
  }

  /**
   * problemClose 停止写入题目
   *
   * @param bufferedWriter 读写对象
   */
  public void problemClose(BufferedWriter bufferedWriter) {
    ...
  }

  /**
   * saveProblem 保存题目
   *
   * @param name 用户名
   */
  public void saveProblem(String name) {
    ...}
  }

上面三个方法完成对题目的写入和保存,其中 writer 的各项动作分得很清楚,好评。

  /**
   * primarySetting 随机出题--小学
   *
   * @param name          出题者的姓名
   * @param problemNumber 题目数量
   */
  public void primarySetting(String name, int problemNumber) {
    saveProblem(name);
    for (int i = 0; i < problemNumber; i++) {
      StringBuilder problem = new StringBuilder(" ");
      int exampleNumber = 0;
      int flag = 0;
      int operandNumber = 2 + new Random().nextInt(3); /* 操作数个数 */
      List<String> operandArr = new ArrayList<>();
      List<Integer> Locations = new ArrayList<>();
      while (exampleNumber < operandNumber) {
        exampleNumber += 1; /* 操作数计数 */
        if (exampleNumber <= operandNumber - 2) {/* 左括号 */
          if (0 == flag) {
            String S = bracketsLeft.get(new Random().nextInt(5));
            operandArr.add(S);
            if ("(".equals(S)) {
              flag = exampleNumber;
            }
          }
        }
        operandArr.add(String.valueOf(1 + new Random().nextInt(100)));
        operandArr.add(primary.get(new Random().nextInt(4)));
        if (exampleNumber == operandNumber) {
          operandArr.add(String.valueOf(1 + new Random().nextInt(100)));
        }
      }
      for (int j = 0; j < operandArr.size(); j++) {
        if (primary.contains(operandArr.get(j))) {
          Locations.add(j);
        }
      }
      while (0 != flag) {
        int locate = new Random().nextInt(Locations.size());
        if (locate >= flag) {
          operandArr.add(Locations.get(locate), ")");
          flag = 0;
        }
      }
      for (String s : operandArr) {
        problem.append(s);
      }
      problemWriter(bufferedWriter, (i + 1) + "、" + problem);
    }
    problemClose(bufferedWriter);
  }

  /**
   * middleSetting 随机出题--初中
   *
   * @param name          出题者的姓名
   * @param problemNumber 题目数量
   */
  public void middleSetting(String name, int problemNumber) {
    ...
  }

  /**
   * highSetting 随机出题--高中
   *
   * @param name          出题者的姓名
   * @param problemNumber 题目数量
   */
  public void highSetting(String name, int problemNumber) {
    ...
  }

这三个方法的基本逻辑是一样的,就只贴出一个实现。它们完成不同年级难度的随机出题:先利用 List生成操作数和运算符号的序列,再把它们组合成字符串,每出一道题就保存一道,出完所有题之后关闭写文件对象。

主类 AutomaticGenerator

成员属性:

  /* userInformation 用户信息 */
  static String userInformation;
  /* userArr 用户信息数组 */
  static String[] userArr;
  /* typeUser 用户类型 */
  static String typeUser;
  /* currentUser 当前用户 */
  static String currentUser;

  /* 当前用户编号 */
  static int currentUserID;

  /* userGroup 用户列表 */
  static List<Map<String, String>> userGroup;

  /* USER_TYPES 教师类型 */
  static final String[] USER_TYPES = {"小学", "初中", "高中"};

  /* problemSetting 出题类实例 */
  static ProblemSetting problemSetting;

  /* problemNumber 题目数量 */
  static int problemNumber;

  /* allowIn 是否允许登录 */
  boolean allowIn;

  // 新建输入对象
  Scanner in = new Scanner(System.in);

成员属性的注释很明了,就不多解释了。

  /**
   * exeInit 程序初始化
   */
  public void exeInit() {
    //新建用户对象
    Users myUser = new Users();

    //初始化用户对象
    myUser.userInit();

    //初始化用户组
    userGroup = Arrays
        .asList(myUser.getPrimary(), myUser.getMiddle(), myUser.getHigh(), myUser.getController());
  }
  /**
   * exeInput 程序输入
   */
  public void exeInput() {
    /* 初始化每一次的输入 */
    allowIn = false;

    /* 输入用户信息 */
    userInformation = in.nextLine();

    /* 输入-1退出程序 */
    if ("-1".equals(userInformation)) {
      System.exit(0);
    }
    userArr = userInformation.split(" ");
  }

一目了然,也不用多解释。

  /**
   * typeJudge 用户类型判断
   */
  public void typeJudge(AutomaticGenerator automaticGenerator) {
      ...
  }

typeJudge 方法检查密码正确性,输入了正确的密码就允许登录,判断用户是否是管理员并设置当前用户信息。

  /**
   * userMenu 用户菜单
   *
   * @param automaticGenerator 用户对象
   */
  public void userMenu(AutomaticGenerator automaticGenerator) {
      ...
  }

userMenu 负责普通用户的操作。除了出题和切换用户之外,还附加了修改密码和退出程序的功能。

  /**
   * controlMenu 管理员菜单
   *
   * @param automaticGenerator 管理员对象
   */
  public void controlMenu(AutomaticGenerator automaticGenerator) {
      ...
  }

controlMenu 负责管理员用户的操作。管理员可以查看所有用户、增删用户以及重置账户密码,也可以退出管理员模式重新登录。

  /**
   * main 主函数入口
   */
  public static void main(String[] args) {
    AutomaticGenerator automaticGenerator = new AutomaticGenerator();
    System.out.print("请输入您的用户名和密码(以空格分隔,输入-1退出程序):");
    automaticGenerator.exeInit();
    automaticGenerator.exeInput();
    automaticGenerator.typeJudge(automaticGenerator);
  }

主函数就很简单了,新建 AutomaticGenerator 对象,进行系统初始化,然后读取输入、判断用户类型并等待操作。

优缺点分析

优点

  • 对 Java 的应用能力很强,在我看来代码也很规范;
  • 功能强大,不仅基本实现了项目原本的要求,还增添了新的实用功能,使得用户体验更加完整;
  • 增加了管理员用户,管理用户数据更方便了;
  • 类中成员的动作划分很清楚(前面也讲到了),类间联系和整个程序的逻辑结构较为清晰;
  • 生成题目的步骤很有启发性,使用 List 使得题目的“组装”方法十分简明。

缺点

  • 各个年级的题目生成逻辑类似,相同的部分复用性不够好,既然在正式构建题目前使用 List 先获取操作数和符号列表,不妨在高年级部分利用低年级题目的成果,在此基础上附加其他符号;
  • 似乎只能加一对括号(其实我也是)……
  • 比较主观的见解:主类和用户类的部分功能划分得不太明显,用户类型属性没有在用户类中体现,而是相当于被直接放到了主类中。

嗯?杨少好像没有检查新题目和出过的题是否重复?XD

总结心得

这种分析搭档代码的学习形式确实裨益良多。一方面,我们都能学习对方代码中优秀的地方,另一方面,通过检查对方代码的不足,我们也都能反观自身的缺点,共同进步。

结对好耶

posted @ 2021-09-27 22:51  CharlieDu  阅读(135)  评论(0)    收藏  举报