HNU个人项目互评:中小学数学卷子自动生成程序

发布时间 2023-09-20 20:53:01作者: stellar2080

 HNU个人项目互评:中小学数学卷子自动生成程序


项目名称:中小学数学卷子自动生成程序

结对成员:杨安然、朱志星

项目作者:杨安然

编程语言:JAVA

博客作者:朱志星


目录

  • 一、前言
  • 二、项目要求
  • 三、黑盒测试
  • 四、代码评价
  • 五、整体评价

一、前言

上一周本结对小组成员双方各自完成了中小学数学卷子自动生成程序这一个人项目,本博客的目的在于对结对编程队友杨安然同学的个人项目进行代码测试与评价。

二、项目要求

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

备注

只能有+,-,*./和()

题目中至少有一个平方或开根号的运算符

题目中至少有一个sin,cos或tan的运算符

三、黑盒测试

黑盒测试部分不关心代码,只考虑功能是否正常以及用户使用体验等方面,并且将尽可能地全面测试程序。

1、登录部分

1、运行程序后会显示“请输入用户名和密码”字样,符合项目要求,使用者也能清楚知道要输入的是什么。

2、若用户名和密码均输入失败则显示以下内容。

优点:

1)这里杨安然同学实现了用户名和密码输入不正确时给予用户退出的功能,避免了用户忘记密码但又无法正常退出程序的尴尬处境,值得学习。

3、接下来输入-1则正常退出程序。

4、按其他字符则可重新登录

5、输入正确的用户名和密码,可以正常登录。按用户类型输出了对应的“选择为XX出题”,符合项目要求,用户能清楚知道当前的出题类型。接下来提示了用户输入题目数量的范围,以及按-1能够退出当前用户重新登录。

可改进:

1)没有提示用户可以输入“切换为XX”来暂时切换出题类型。

2)输入-1重新登录的文本段表达的意思有点不清楚,括号可能会让用户觉得输入题目数量将退出当前用户。这里可以改为“请输入生成题目数量(输入-1退出当前用户,重新登录)”。

6、测试输入-1退出登录,该功能正常。

7、测试切换类型的功能,输入错误的切换为XX以测试,可以发现程序均能识别出输入错误并予以提示,功能正常。

可改进:

1)有输入非“切换为XX”的文本则会直接退出登录的bug。

8、输入正确文本,可以发现系统提示请输入题目数量,范围10~30个,生成卷子之后回退到登录成功之后的界面。

可改进:

1)这里输入为切换为XX之后,只能生成一次该类型的试卷,如果要生成多份该类型试卷,就要输入多次的切换为XX,比较不方便。

2)如果切换完类型之后输入错误,那么就会显示和登录后主界面一样的“准备生成XX数学题目”,但是这里的类型又是账户类型,即非临时切换的类型,可能会让用户感到迷惑。

3)输入30之后会发现系统提示输入不正确,而10~29都能正常生成试卷,猜测是类似"num<=30"写成了"num<30"的问题。

可改进:

1)测试出如果切换为高中,会有生成卷子之后就会自动退出该用户,回到登录界面的bug。

9、查看文件夹和试卷,发现文件夹名,文件名和试卷题目类型均正确,题目内容也符合项目要求。这里由于全部题目都截图会导致图片太长,为节省篇幅只截取一部分题目。

可改进:

1、三角函数的角度是随机生成的,而不是高中生常做的30°,45°和60°三个角度,不太合理。

 10、切换功能已测试完,接下来测试直接生成试卷,刚刚生成了小学和高中的试卷,那么现在则登录李四1账号来测试初中试卷,可以发现直接按本账号类型生成试卷的功能正常,生成试卷后回退到主界面,可以继续生成试卷或进行其他操作。

11、查重功能无法测试,因为我通过阅读代码发现该功能几乎不可用,代码评价部分会详细说明。

黑盒测试评价:程序基本上完成了项目要求,功能基本正常。但是由于项目作者提交之前可能没有全面测试程序,导致有一些比较容易解决的bug没有处理,例如切换成高中生成试卷自动退出登录bug,未完善输入值处理导致会出现非用户本意的退出登录bug,以及无法查重的bug等,另外也有一些可以改进的地方,例如打印输出的提示信息不够完善,随机角度导致高中三角函数难度巨大等方面,用户体验有一定提升空间。

四、代码评价

为了节省篇幅,这里只评价核心类以及核心方法,像User类这种只存储了账号密码以及只有get,set方法的类就不写了。

1、Login类

该类含有一个main方法,是程序入口点,作用为从文件中加载用户信息,提示用户输入他们的用户名和密码,然后检查这些凭据是否与加载的用户数据匹配。

1)main方法:

public static void main(String[] args) {

    boolean flag = false;//是否想要退出,flag等于true则直接退出

    // 从文件加载用户信息
    List<User> users = loadUsersFromFile();
    while (true) {
      System.out.println("请输入用户名和密码");
      Scanner scanner = new Scanner(System.in);
      String name = scanner.next();
      String password = scanner.next();
      LoginPage loginPage = new LoginPage();
      flag = loginPage.consoleArea(name, password, users);//进入登录页面的操作位置

      if (flag == true) {//flag等于true则直接退出
        break;
      }
    }

  }

本方法使用loadUsersFromFile静态方法来从文件加载用户信息,接下来则进入循环,输入用户名和密码后创建一个loginPage对象,并调用其方法进行登录。

优点:

1、有比较好的可扩展性,通过使用对象和将代码逻辑封装成方法来处理登录的用户数据,从而能够方便地进行扩展和修改。

2、可读性很高,代码非常简洁,除了输入其他功能均在其他类里面实现。而且方法和变量命名均按照其功能和作用来命名,阅读起来简单易懂。

 2、LoginPage类

该类接受用户提供的用户名和密码,并与加载的用户数据进行匹配,以确定用户的身份和权限。如果登录成功,用户将被引导到适当的系统页面。 如果登录失败,用户将获得相关提示并可以选择继续登录或退出程序。

1、consoleArea方法

public boolean consoleArea(String name, String password, List<User> users) {
    Scanner sc = new Scanner(System.in);
    //1.获取用户类型,使用userservice获取,若user为null则输出您输入的账号或密码不对,是否要退出,若要退出则return true。若user不为
    UserService userService = new UserService();
    User user = userService.selectPerson1(name, password, users);
    int state = userService.userState(user);//看看user是否存在,以及看一看他的类型
    if (state == 0) {
      System.out.println(
          "请输入正确的用户名、密码.若要退出程序则按-1,若要继续登录则按除1外的任意字符");
      String ss = sc.next();//ss为判断是否需要继续登录
      if (ss.equals("-1")) {
        return true;
      } else {
        return false;
      }
    } else {//说明用户存在,准备生成XX数学题目,请输入生成题目数量(输入-1重新登录)将退出当前用户
      //System.out.println("登陆成功");
      if (state == 1) {
        System.out.println("当前选择为小学出题");
      } else if (state == 2) {
        System.out.println("当前选择为初中出题");
      } else if (state == 3) {
        System.out.println("当前选择为高中出题");
      }
      int mark = 0;//用来标定是否退出登录
      while (mark == 0) {
        LoginPageA loginPageA = new LoginPageA();
        mark = LoginPageA.consoleArea(state, user);
      }

    }
    return false;
  }

本方法创建一个userService对象用于检验用户名和密码的正确性,失败则判断是否要继续登录或退出,成功则通过用户类型来输出对应的文本,同时创建loginPageA对象进入登陆后主界面。

优点:

1、代码结构清晰明了,无复杂和冗余部分,通过创建局部对象来调用其他类的方法,这样做使得本方法的扩展性和可维护性都较高。

可改进:

1、有一些变量命名使用了简单的一两个字母,如ss,sc等,这可能会影响代码的可读性,更建议使用有意义,即能表明变量作用的变量名。

2、state变量的判断部分if...else if行的数量较多,或许可以考虑改为使用switch case语句,因为判断条件较多时,swtich case的效率高于if...else if。

3、LoginPage类和LoginPageA类的命名太过相近,且它们的方法名都是consoleArea,这容易混淆,可读性较差,建议将LoginPageA及其方法更换命名。

4、mark只有一个作为判断标志的功能,更建议使用boolean类型的变量,更加直观。

5、state==0处的输出文本有误,应将1改为-1

3、LoginPageA类

该类提供了不同的操作选项,允许用户根据输入执行相应的操作,包括生成试卷和切换用户角色。

1、consoleArea方法

public static int consoleArea(int state, User user) {
    LoginPageAUtils loginPageAUtils = new LoginPageAUtils();
    loginPageAUtils.outPututil(state);
    Scanner scanner = new Scanner(System.in);
    String str = scanner.nextLine();
    if (str.equals("-1")) {//退出用户
      return 1;
    } else if (str.contains("切换为")) {//这种方法是"选择方法出试卷"
      return loginPageAUtils.toggleLogic(str, user);
    } else if (str.matches("\\d+")) {//这种方法"是默认方法出题"
      int num = Integer.valueOf(str);
      if (num >= 10 && num < 30) {//正常使用出卷
        PaperUtils paperUtils = new PaperUtils();      //使用出卷工具
        paperUtils.getPaper(user.getName(), state, num);
        return 0;
      } else {
        System.out.println("输入在10-30之间,请重新输入");
        return 0;
      }
    }
    return 1;
  }

本方法先创建了一个loginPageAUtils对象,并调用该对象的outPutuitl方法来输出提示信息,然后接收用户输入,判断用户输入值来调用对应的方法处理。

优点:

1、使用了正则表达式来检查String中是否全为数字,若采用遍历字符串的方法则写出的代码会比较长,且可读性和可维护性也不如使用正则表达式,值得学习。

可改进:

1、该类只有唯一一个方法,且该方法也是创建LoginPageAUtils类和PageUtils类并调用其方法来实现功能,那么这个类完全没有必要存在,直接把这个方法放到LoginPageAUtils类中就可以了,这样可以让代码更加简洁。

2、题目数量判断有误,应是num<=30。

4、LoginPageAUtils类

该类封装了用于处理 LoginPageA 页面的各种功能函数。 这些函数包括输出提示信息、处理切换用户状态和生成题目等操作。

1、outPututil方法

  public void outPututil(int state) {//获取题目并输出相应对话
    if (state == 1) {
      System.out.println(
          "准备生成小学数学题目(输入在10-30之间),请输入生成题目数量(输入-1重新登录)将退出当前用户");
    } else if (state == 2) {
      System.out.println(
          "准备生成初中数学题目(输入在10-30之间),请输入生成题目数量(输入-1重新登录)将退出当前用户");
    } else if (state == 3) {
      System.out.println(
          "准备生成高中数学题目(输入在10-30之间),请输入生成题目数量(输入-1重新登录)将退出当前用户");
    }
  }

这个类用于按照类型输出一串提示信息。

可改进:

1、由于三个输出内容基本相同,只有类型名不同,可以考虑写一个String数组存入“小学”,“初中”,“高中”,然后按 state-1 去取值即可,这样能够减少代码量,更加简洁。

2、toggleLogic方法

public int toggleLogic(String str, User user) {//用来对输入值为”切换为“的字符串进行判定
    Scanner scanner = new Scanner(System.in);
    int newState = 0;
    int get = 0;
    PaperUtils paperUtils = new PaperUtils();
    if (str.equals("切换为小学")) {//满足条件跳出循环
      newState = 1;
      System.out.println("请输入需要生成的题数(10-30)");
      get = scanner.nextInt();
      if (get >= 10 && get < 30) {//正常使用出卷
        paperUtils.getPaper(user.getName(), newState, get);
        return 0;
      } else {
        System.out.println("输入在10-30之间,请重新输入");
        return 0;
      }
    } else if (str.equals("切换为初中")) {
      newState = 2;
      System.out.println("请输入需要生成的题数(10-30)");
      get = scanner.nextInt();
      if (get >= 10 && get < 30) {//正常使用出卷
        paperUtils.getPaper(user.getName(), newState, get);
        return 0;
      } else {
        System.out.println("输入在10-30之间,请重新输入");
        return 0;
      }
    } else if (str.equals("切换为高中")) {
      newState = 3;
      System.out.println("请输入需要生成的题数(10-30)");
      get = scanner.nextInt();
      if (get >= 10 && get < 30) {//正常使用出卷
        paperUtils.getPaper(user.getName(), newState, get);
      } else {
        System.out.println("输入在10-30之间,请重新输入");
        return 0;
      }
    } else {
      System.out.println("请输入小学、初中和高中三个选项中的一个");//不满足条件继续重新输入}
      return 0;
    }
    return 1;
  }

这个方法判断用户输入的切换信息,并执行对应操作。

优点:

1、本方法基本把所有的“切换为XX”的输入形式都考虑到了,而且不会有输入错误后再次输入溢出或无反应的情况。

可改进:

1、对切换为小学,初中和高中的三种if语句判断后的代码几乎一样,可以考虑写成一个方法,将输入的切换字符串作为参数,这样即可减少很大部分的代码行,同时还能增加代码复用性。

2、return 0; 语句出现太多次,且有一部分是没有必要的,可以考虑删减来使代码更加简洁。

3、get<30有误,应为get<=30。

5、PaperUtils类

该类提供了一组用于生成试卷并管理试题的实用工具函数。 这些函数包括获取过去生成的题目、生成新的试卷、以及管理试卷的存储路径。

该类拥有一个String成员变量,存储的是文件夹的相对路径字符串。

public String dir = "output_folder/";

1、getPaper方法

public void getPaper(String name, int state, int num) {
    // 使用File构建相对路径
    File outputFolder = new File(dir);

    // 检查文件夹是否存在,如果不存在则创建它
    if (!outputFolder.exists()) {
      if (outputFolder.mkdirs()) {
        System.out.println("文件夹已创建");
      } else {
        System.err.println("无法创建文件夹");
        return;
      }
    }
    HashSet<String> past = getPast(name);//获得之前生成的题目的hashset
    String dirPath = dir + name;// 文件夹路径
    Calendar cal = Calendar.getInstance();
    int year = cal.get(Calendar.YEAR);//获取年份
    int month = cal.get(Calendar.MONTH)
        + 1;//获取月份,Calendar类中的月份是从0开始计数的,即0代表一月,1代表二月,以此类推,11代表十二月。因此,你在代码中获取的月份实际上是8,而不是9
    int day = cal.get(Calendar.DATE);//获取日
    int hour = cal.get(Calendar.HOUR);//小时
    int minute = cal.get(Calendar.MINUTE);//
    int second = cal.get(Calendar.SECOND);//
    String path =
        dirPath + "/" + String.valueOf(year) + "-" + String.valueOf(month) + "-" + String.valueOf(
            day) + "-" + String.valueOf(hour) + "-" + String.valueOf(minute) + "-" + String.valueOf(
            second) + ".txt";// 设置文件名
    try {
      FileWriter fw = new FileWriter(path, true);//设置追加属性,不断对文件进行追加
      for (int i = 0; i < num; i++) {//生成对应数量题目
        String problem = getProblem(state);//生成相应状态的题目
        if (!past.contains(problem)) {//如果已有的题目中不包含该题目
          problem = String.valueOf(i + 1) + "." + problem;//加上序号
          fw.write(problem + "\n" + "\n");//追加至问价尾
        }
      }
      System.out.println("卷子生成完毕");
      fw.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

该方法主要功能为创建文件夹(如果不存在)和文件,调用getProblem方法来生成题目,从而生成试卷并输出到文件中。

优点:

1、使用了一个成员变量去存储相对路径,这样避免了每一次创建File对象时都要写上相对路径,使代码更加简洁。

2、使用了HashSet容器去存储之前生成过的卷子的题目,这样做的优点在于HashSet相对ArrayList有着更高效的查找效率,可以通过HashCode快速定位,而后者则需要遍历。

3、使用了try catch结构,可以对写文件过程出现的错误进行捕获,并输出给用户看,这样能够让用户更好地知道错误出现在哪里,用户体验更好。

可改进:

1、用SimpleDateFormat类来格式化输出时间,代码量会比项目中实现方式少很多,如下:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
Calendar date = Calendar.getInstance();
System.out.println(sdf.format(date.getTime()));

2、getPast方法

HashSet<String> getPast(String name) {//用来获取过去的题目getPast
    String path = dir + name;    //要遍历的路径
    File file = new File(path);    //获取其file对象
    if (!file.exists()) {//如果没有该文件夹进行创建
      file.mkdir();
    }
    File[] fs = file.listFiles();  //遍历path下的文件和目录,放在File数组中
    HashSet<String> pastProblem = new HashSet<String>();
    for (File f : fs) {                    //遍历File[]数组
      if (!f.isDirectory())        //若非目录(即文件),则读入
      {
        try {
          BufferedReader br = new BufferedReader(new FileReader(f));
          String line;
          while ((line = br.readLine()) != null) {
            // 一次读入一行数据并加入到结果的hashset中
            pastProblem.add(line);
          }
          br.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    return pastProblem;

  }

本方法用于获得之前生成的试卷题目,并将它们都放入一个HashSet中,返回该HashSet。

优点:

1、遍历文件夹时使用了isDirectory方法,作者考虑到了可能会有文件夹的情况,这一点我当时写的时候没有考虑到,值得学习。

可改进:

1、使用readLine方法读取一行数据时没有将题目和题号分割开来,也就是说题号也被加入到HashSet中了,这导致查重功能几乎不可用。一个可能的修改方式为:

String[] problem = line.split(" ");
if (problem.length > 1) {
    pastProblem.add(problem[1]);
  }
}

 3、getProblem方法

private String getProblem(int state) {
    String s = null;
    if (state == 1) {
      PrimaryGetProblem primaryGetProblem = new PrimaryGetProblem();
      s = primaryGetProblem.getProblem();
    } else if (state == 2) {
      JuniorGetProblem juniorGetProblem = new JuniorGetProblem();
      s = juniorGetProblem.getProblem();
    } else if (state == 3) {
      SeniorGetProblem seniorGetProblem = new SeniorGetProblem();
      s = seniorGetProblem.getProblem();
    }
    return s;
  }

该方法通过传入出题类型来调用对应的出题类。

优点:

1、使用了三个类来分别生成小学,初中和高中题目,这使得代码可扩展性较好,如果将来需要生成比如奥数题目,修改代码很方便。

可改进:

1、可以考虑使用工厂模式,定义一个工厂类GetProblemFactory来生成三个类,因为三个类都是继承自同一个抽象类AbstractGetProblem,如果使用工厂模式代码不需要有这么多重复部分,也会有更好的模块化和扩展性。如下:

  AbstractGetProblem abstractGetProblem = GetProblemFactory.get(state);
  s = abstractGetProblem.getProblem();

6、AbstractGetProblem类

该类是出卷工具的抽象类,用于生成数学题目。 它包含一组数学运算符号和随机数生成器,可以用于派生具体的数学题目生成器。

  String[] symbol = new String[]{"+", "-", "*", "/", "^2", "√", "sin", "cos", "tan"};//设置字符串数组保存符号
  Random r = new Random();//产生随机数种子
  String problem = new String();//创建字符串来保存结果

  /**
   * 生成数学题目的抽象方法。
   *
   * @return 生成的数学题目字符串
   */
  public String getProblem() {
    return null;
  }

优点:

1、使用了抽象类,再用具体类泛化的形式来定义生成题目类,优点在于可以提高代码的复用性,同时让类于类之间产生了关系,这也是多态的前提。

可改进:

1、抽象类里面没有抽象方法,getProblem方法完全不需要有方法体,写成如下变成抽象方法即可:

public abstract String getProblem();

因为生成小学,初中和高中题目类内容基本都差不多,为节省篇幅就以SeniorGetProblem类为例子来评价。

7、SeniorGetProblem类

该类是用于生成高中数学试题的实用工具类,继承自AbstractGetProblem,实现了生成高中数学试题的具体逻辑。

1、getProblem方法

public String getProblem() {
    while (true) {
      problem = "";
      int length = 0;
      int left = 0;
      int gap = 0;//length用于跟踪数学题目的长度,即题目中包含的操作符和数字的数量。left用于跟踪未关闭的左括号的数量。在代码中,当 left 的值大于0时,会尝试添加右括号来关闭它们。gap用于跟踪两个操作符之间的数字的数量。在代码中,当 gap 的值大于等于2时,会尝试添加右括号
      int sym = 1;//这个变量用于控制生成的数学题目中符号(操作符)的数量1-5
      int sym2 = r.nextInt(3) + 1;//初始化三角函数个数大于1的整数
      int bracket = r.nextInt(3);//这个变量初始化了括号的数量。它从0到2之间随机生成一个数,表示在生成的数学题目中有多少个括号对。括号用于改变运算的优先级。
      while (true) {
        if (r.nextInt(2) == 0 && bracket > 0) {//它的作用是控制是否在数学表达式中添加左括号 "(",有二分之一的概率加左括号
          problem += "(";
          bracket--;
          left++;
          gap = 0;
        }
        gap++;//操作符间数字数量+1
        int num = r.nextInt(100) + 1;
        if (r.nextInt(3) == 0) {//1/3的概率加入平方根号
          if (r.nextInt(2) == 0) {
            problem += (String.valueOf(num) + symbol[4]);//这里是加平方的
          } else {
            problem += (symbol[5] + String.valueOf(num));//这里是加√的
          }
          sym--;
        } else {
          if (r.nextInt(2) == 0) {//1/2的概率加入三角函数
            int symloc = r.nextInt(3) + 6;
            problem += (symbol[symloc] + String.valueOf(num));
            sym2--;
          } else {//1/2的概率加入普通数字
            problem += String.valueOf(num);
          }
        }
        if (r.nextInt(2) == 0 && left > 0 && gap >= 2) {
          problem += ")";
          left--;
        }
        if (length >= 2 && sym <= 0 && sym2 <= 0 && bracket <= 0 && left <= 0) {
          break;
        }
        problem += symbol[r.nextInt(4)];//那些+-* /是从这一句整出来的
        length++;
      }
      if (length < 5 && gap < 5) {
        break;
      }
    }
    problem += "=";
    return problem;
  }

该方法主要通过随机产生符号,随机数和括号等元素来随机出题。

优点:

1、使用了Random类来控制是否插入某个元素,Random类默认使用当前系统时钟作为种子,这意味着每一次的随机都不一样,极大提高了题目生成的多样性。

2、使用了bracket变量来控制括号的数量,left变量确保了有多少个左括号就会有多少个右括号,这解决了随机生成括号可能会出现左右括号数量和位置不匹配的情况。

可改进:

1、该方法的局部变量很多,可以考虑定义一个类专门用于储存变量,在抽象类中创建一个对象,每出完一道题就把该对象的变量重置即可,这样就不必每一次生成题目都要声明那些变量了。

2、小学,初中和高中生成题目有很多重复部分,可以考虑将一些部分写成方法放在抽象类中,比如插入运算符,插入操作数等,这样子类直接调用方法即可,提高代码的简洁度和可复用性。

3、生成三角函数时建议只生成30,45和60度,否则题目过于太难。

8、UserService类

该类提供了对用户进行各种操作的功能。 这些操作包括获取用户状态、将用户信息保存到文件以及从文件加载用户列表等。

1、userState方法

public int userState(User user) {
    if (user == null) {
      return 0;
    } else if (user.getState() == 1) {
      return 1;
    } else if (user.getState() == 2) {
      return 2;
    } else if (user.getState() == 3) {
      return 3;
    }
    return 0;
  }

该方法根据用户状态获取对应的整数值。

可改进:

1、除了user==null的情况,其他的只要写return user.getState()就可以了,原代码有些冗余了。

2、saveUsersToFile方法&loadUsersFromFile方法

private static final String FILE_NAME = "users.txt"; // 文件名

  public static void saveUsersToFile(List<User> users) { // 把用户存到 users.txt
    try (FileWriter fileWriter = new FileWriter(FILE_NAME)) {
      for (User user : users) {
        fileWriter.write(user.getName() + "," + user.getPassword() + "," + user.getState() + "\n");
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
public static List<User> loadUsersFromFile() {// 把文件当中的用户列表加载到List当中
    List<User> users = new ArrayList<>();
    try (BufferedReader reader = new BufferedReader(new FileReader(FILE_NAME))) {
      String line;
      while ((line = reader.readLine()) != null) {
        String[] parts = line.split(",");
        if (parts.length == 3) {
          String name = parts[0];
          String password = parts[1];
          int state = Integer.parseInt(parts[2]);
          users.add(new User(name, password, state));
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return users;
  }

saveUsersToFile方法将用户信息列表保存到文件中(users.txt),loadUsersFromFile方法从文件中加载用户信息,并将其存储到列表中。

优点:

1、使用了txt文件来保存用户信息,方便修改且容易导出,我是使用了HashMap来储存用户信息的,在实际使用时相比写入文件来说有很多不方便之处,值得学习。

2、同样使用了try catch结构,保存过程中出现问题可以马上提示用户,提升了用户体验。

3、selectPerson1

public User selectPerson1(String name, String password,
      List<User> users) {//用来查找是否有符合name和password的用户
    for (User user : users) {
      if (user.getName().equals(name) && user.getPassword().equals(password)) {
        return user; // 找到匹配的用户,返回该用户
      }
    }
    return null; // 没有找到匹配的用户,返回null
  }

该方法根据用户名和密码查找匹配的用户。

可改进:

1、这里是使用了List来存储,通过遍历来查找匹配的用户,当用户量很大时遍历所需时间可能会很长,可以考虑使用HashMap,因为HashMap查找的效率极高。

五、整体评价

通过对杨安然同学的整个程序的代码的阅读和理解,绘制出程序流程图及UML类图如下:

1、程序流程图

2、UML类图

 

以下为结合了程序流程图,UML类图和程序代码的整体评价:

1、程序逻辑:该程序的逻辑,结构和执行流程都是比较完善的,对各种输入也进行了对应的处理,具有一定的健壮性,也没有冗余部分,符合项目要求。

2、可维护性:程序中每个类都有自己的作用,且都跟其他类有一定联系,主要体现为依赖和继承,各个类将程序划分为不同的模块或组件,每个类负责特定的功能。这样简化了代码结构,提高代码的可维护性。

3、可复用性:继承关系的使用和模块化的设计使得程序结构更加清晰,便于代码复用。

4、灵活性:依赖关系建立了松耦合的关系,降低了类与类之间的耦合度,使得程序的修改和维护更加灵活和可靠。

5、可扩展性:程序的可扩展性也较高,当需要添加新的功能或修改现有功能时,可以通过创建新的类或修改现有类之间的联系来实现,而不需要对整个程序进行大规模的修改。

6、代码风格:符合Google Style,每行代码长度不超过80个字符。方法行数不超过40行。缩进和空格均符合要求。类都使用了大驼峰命名法,方法和变量都使用了小驼峰命名法,且基本都有合适的可以表明功能的命名,使得代码易于理解。在每个类以及方法的前面都有相应的javadoc来解释其作用,参数和返回值等。

7、程序BUG:存在一些bug,而且有些bug比较恶性,影响程序正常运行,降低用户体验。

8、无法查重:由于从文件中读入题目时处理不当,导致查重功能几乎不可用。