首页 > 编程语言 >湖南大学结对编程个人项目互评

湖南大学结对编程个人项目互评

时间:2023-09-20 16:36:08浏览次数:33  
标签:结对 CODE 题目 String 湖南大学 互评 static return public


1. 简介

本博客用于湖南大学结对编程项目中对王明扬同学个人项目的互评工作。

个人项目的功能是实现一个中小学数学试卷自动生成程序,基于命令行进行操作。

2. 代码

2.1 项目结构

项目由多个包组成,分别执行不同的功能:

2.1.1 controller:

接收用户的输入,调用service层进行相应的逻辑处理,并返回处理结果,再由Application类进行处理。

2.1.2 service:

对controller层传来的数据进行登录或出题的逻辑处理,并调用mapper层进行数据操作。

2.1.3 mapper:

进行数据操作,包括用户信息的读取和题目的写入。

2.1.4 utils

定义了一系列工具类,包括读写文件、题目相关和返回结果相关。

2.1.5 entity

定义了用户实体类,封装了用户的相关属性和方法,用于存储登录时返回的用户信息。

2.1.6 小结

如此定义项目的结构,有助于结对编程时项目的移植。

2.2 代码分析

采用按类分析,其中每个类若含有多个方法,将分别分析每个方法。

2.2.1 Application类

main方法: 该方法调用Application类下定义的另外两个方法:

setUserByLogingenerateQuestion,是程序的入口。

采用while(true)循环,可以实现重复登录和重复出题。

退出系统的操作由于代码很少,直接在main里实现了,根据exitFlag的值决定是否继续循环。

private static User user = new User(); // 存储当前操作的用户

private static boolean exitFlag = false; // 是否退出系统的标志

/**
* 主函数,也是启动函数.
*/
public static void main(String[] args) {
while (true) {
setUserByLogin();
if (exitFlag) {
System.out.println("感谢使用!");
break;
}
generateQuestion(user.getUserName(), user.getUserType());
}
}

setUserByLogin方法: 登陆部分的实现直接或间接由它调用。

while(true)循环中根据LoginController类中login方法的返回值,判断不同的情景进行响应:

注:不额外说明"结束循环"的情景,均会重新执行login方法,下文不额外赘述。

(1) 登陆成功: 结束循环并返回登录的用户信息。

(2) 登陆信息输入数量不符:返回"请输入用空格分开的用户名和密码,不要少输入或多输入信息"提示。

(3) 登陆信息输入用户不存在: 返回"请输入正确的用户名、密码"提示。

(4) 退出登录:设置exitFlag为false,结束循环。

private static void setUserByLogin() {
while (true) {
Result result = LoginController.login();
if (result.getCode().equals(ResultConstant.LOGIN_SUCCESS_CODE)) {
user = (User) result.getData();
break;
} else if (result.getCode().equals(ResultConstant.LOGIN_COUNT_ERROR_CODE)) {
System.out.println("请输入用空格分开的用户名和密码,不要少输入或多输入信息");
} else if (result.getCode().equals(ResultConstant.LOGIN_MISTAKE_ERROR_CODE)) {
System.out.println("请输入正确的用户名、密码");
} else if (result.getCode().equals(ResultConstant.EXIT_SUCCESS_CODE)) {
exitFlag = true;
break;
}
}
}

generateQuestion方法: 出题部分的实现直接或间接由它调用。

while(true)循环中根据QuestionController类中generateQuestion方法的返回值,判断不同的情景进行响应:

注:不额外说明"结束循环"的情景,均会重新执行generateQuestion方法,下文不额外赘述。

(1) 退出登录:输出"您已退出当前用户,请重新登陆"并结束循环。

(2)题目数量不符合要求:返回"题目数量的有效输入范围是“10-30”,请重新输入"提示。

(3)切换试卷类型时不包含要求的关键字:返回"请输入小学、初中和高中三个选项中的一个"提示。

(4)输入题目数量时输入了小数点后不为0的浮点数:返回"请输入无歧义的题目数量"提示。

(5)不输入任何指令,直接回车:返回"无效指令,请输入题目数量或切换请求"提示。

(6) 切换试卷成功:更改userType的值,重新调用generateQuestion方法时便可改变类型。

private static void generateQuestion(String userName, String userType) {
while (true) {
Result result = QuestionController.generateQuestion(userName, userType);
if (result.getCode().equals(ResultConstant.LOGOUT_SUCCESS_CODE)) {
System.out.println("您已退出当前用户,请重新登陆");
break;
} else if (result.getCode().equals(ResultConstant.QUESTION_NUMBER_ERROR_CODE)) {
System.out.println("题目数量的有效输入范围是“10-30”,请重新输入");
} else if (result.getCode().equals(ResultConstant.TURN_INPUT_ERROR_CODE)) {
System.out.println("请输入小学、初中和高中三个选项中的一个");
} else if (result.getCode().equals(ResultConstant.NUMBER_STYLE_ERROR_CODE)) {
System.out.println("请输入无歧义的题目数量");
} else if (result.getCode().equals(ResultConstant.INSTRUCTION_INVALID_ERROR_CODE)) {
System.out.println("无效指令,请输入题目数量或切换请求");
} else if (result.getCode().equals(ResultConstant.TURN_SUCCESS_CODE)) {
userType = (String) result.getData();
}
}
}

小结: 不同的状态码对应不同的情景,代码逻辑清晰。登录和出题分别执行各自的功能,又通过用户类型进行关联,使程序可以正常执行的同时,大大简化了main方法。

2.2.2 LoginController类

login方法: 接收登录时用户的输入,并根据Application类中的setUserByLogin方法中描述的四种情形,返回相应的状态码。

用户信息的查询是通过调用UserService类中的queryByUserNameAndPassword方法实现的。

private static final UserService userService = new UserServiceImplement();

/**
* 登录的核心实现.
*
* @return 不同的状态码,以便与Application中能够进行不同的响应。
*/
public static Result login() {
Scanner scanner = new Scanner(System.in);

System.out.println("请先后输入用户名和密码,用空格分开,单输入一个0退出系统:");
String input = scanner.nextLine().trim(); // 使用 trim() 方法去除起始和末尾的空格
String[] strings = input.split("\\s+");

if (strings.length != 2) {
if (strings.length == 1 && strings[0].equals("0")) {
return Result.success(ResultConstant.EXIT_SUCCESS_CODE);
}
return Result.error(ResultConstant.LOGIN_COUNT_ERROR_CODE);
}

String userName = strings[0];
String password = strings[1];

User user = userService.queryByUserNameAndPassword(userName, password);

if (user != null) {
System.out.println("登录成功");
System.out.println("当前选择为" + user.getUserType() + "出题");
return Result.success(ResultConstant.LOGIN_SUCCESS_CODE, user);
}

return Result.error(ResultConstant.LOGIN_MISTAKE_ERROR_CODE);
}

小结: 看得出来,该同学在开发的过程中对方法进行了不断的完善,对各种错误的情况进行了划分,方便使用者了解自己操作错误的原因。

2.2.3 QuestionController类

generateQuestion方法: 接收出题时用户的输入,并根据Application类中的generateQuestion方法描述的六种情形,返回相应的状态码。

题目的生成与写入是通过调用QuestionService类中的generateAndWriteQuestion方法实现的。

题目的切换时通过调用本类中的getType方法实现的。

private static final QuestionService questionService = new QuestionServiceImplement();

/**
* 生成题目的核心实现.
*
* @param userName 需要生成题目的用户名,决定了将题目文件存储在哪一个文件夹下。
* @param type 需要生成的题目类型。
* @return 不同的状态码,以便与Application中能够进行不同的相应。
*/
public static Result generateQuestion(String userName, String type) {
Scanner scanner = new Scanner(System.in);

System.out.println("准备生成" + type + "数学题目,请输入生成题目数量(10-30)(输入-1将退出当前用户,重新登录):");
String instruction = scanner.nextLine();
try {
double numberDouble = Double.parseDouble(instruction);
int numberFloor = (int) Math.floor(numberDouble);
int numberCeil = (int) Math.ceil(numberDouble);
if (numberFloor != numberCeil) { // 输入的题目数量是一个非 ".0" 的浮点数
return Result.error(ResultConstant.NUMBER_STYLE_ERROR_CODE);
}
int number = (int) Math.floor(numberDouble);
if (number == -1) {
return Result.success(ResultConstant.LOGOUT_SUCCESS_CODE);
} else if (number >= 10 && number <= 30) {
questionService.generateAndWriteQuestion(userName, type, number);
return Result.success(ResultConstant.GENERATE_SUCCESS_CODE);
} else {
return Result.error(ResultConstant.QUESTION_NUMBER_ERROR_CODE);
}
} catch (NumberFormatException expected) { // 当输入的内容不能转化成数字时
if (instruction.isEmpty()) {
return Result.error(ResultConstant.INSTRUCTION_INVALID_ERROR_CODE);
}
return getType(instruction);
}
}

getType方法: 根据本类中generateQuestion方法传入的指令,进行判断后返回相应的状态码(或包括要切换的类型)。

状态码返回给本类中generateQuestion方法,再由其返回给Application类中的generateQuestion方法。

/**
* 服务于 generateQuestion 的方法,用于处理切换题目的逻辑.
*
* <p>只要输入的指令中包含“小学”、”初中“、”高中“字段之一,即可切换题目类型。如果输入中出现多个字段,以第一个出现的字段为准。</p>
*
* @param turnInstruction 输入的切换指令。
* @return 不同的状态码,以便 generateQuestion 能够返回给 Application。
*/
private static Result getType(String turnInstruction) {
turnInstruction = turnInstruction.replaceAll("\\s+", ""); // 替换掉空格,使得“小 学”这种也能被识别到
if (turnInstruction.length() < 2) {
return Result.error(ResultConstant.TURN_INPUT_ERROR_CODE);
}
String[] strings = {"小学", "初中", "高中"};
int[] index = new int[strings.length];
for (int i = 0; i < strings.length; i++) {
index[i] = turnInstruction.indexOf(strings[i]);
index[i] = (index[i] == -1 ? turnInstruction.length() - 2 : index[i]);
}
Arrays.sort(index); // 出现多个切换关键字时,以第一个出现的为准
String type = turnInstruction.substring(index[0], index[0] + 2);
if ((!type.equals("小学")) && (!type.equals("初中")) && (!type.equals("高中"))) {
return Result.error(ResultConstant.TURN_INPUT_ERROR_CODE);
}
return Result.success(ResultConstant.TURN_SUCCESS_CODE, type);
}

小结: 切换类型做的非常巧妙,切换成功后,最终会返回给Application类中的generateQuestion方法,直接更改userType,这样做不需要为切换后的出题额外写方法,提高了代码的复用性和简洁性。

2.2.4 UserServiceImplement类

该类继承了UserService接口,方便以后进行扩展,即可以为UserService定义多个实现类,按需调用。

queryByUserNameAndPassword方法: 该方法根据用户名和密码获取用户信息。

该方法较简单,只是调用了UserMapper类中的queryByUserNameAndPassword方法。

是为了实现分层接耦,即2.1部分中论述的多个包。

/**
* 通过用户名和密码获取用户信息,如此实现是为了实现分层解耦.
*
* @param userName 用户名。
* @param password 密码。
* @return 返回查询到的用户,若无,则返回空
*/
@Override
public User queryByUserNameAndPassword(String userName, String password) {
return UserMapper.queryByUserNameAndPassword(userName, password);
}

小结: 体现了三层架构。

2.2.5 QuestionServiceImplement类

该类继承了QuestionService接口。

generateAndWriteQuestion方法: 该方法用于生成并写入指定用户目录下指定类型和指定数量的题目。

该方法首先获取当前的日期,作为文件的命名。

接下来先调用一次QuestionMapper类中的writeQuestion方法,写入试卷类型。

之后在循环中调用QuestionUtils工具类的questionGenerator方法生成题目指定数量题目。

每生成一道题目后会进行查重,一旦重复,会重新进行本次循环,重新生成题目。

生成题目并且确定未重复后会调用QuestionMapper类中的writeQuestion方法写入题目。

题目生成完毕后会进行信息提示。

/**
* 生成题目并写入文件.
*
* <p>具备查重功能,由 QuestionGeneratorUtils 类的 duplicationCheck 实现。</p>
*
* @param userName 需要生成题目的用户名,决定了将题目文件存储在哪一个文件夹下。
* @param type 需要生成的题目类型。
* @param number 需要生成的题目数量
*/
@Override
public void generateAndWriteQuestion(String userName, String type, int number) {
Calendar calendar = new GregorianCalendar();
String year = String.valueOf(calendar.get(Calendar.YEAR));
String month = String.valueOf(calendar.get(Calendar.MONTH) + 1);
String day = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
String hour = String.valueOf(calendar.get(Calendar.HOUR_OF_DAY));
String minute = String.valueOf(calendar.get(Calendar.MINUTE));
String second = String.valueOf(calendar.get(Calendar.SECOND));
// 文件命名
String questionName = year + "-" + month + "-" + day
+ "-" + hour + "-" + minute + "-" + second + ".txt";
QuestionMapper.writeQuestion(userName, questionName, type + "数学题");
for (int i = 0; i < number; i++) { // 生成题目并写入
String question = QuestionUtils.questionGenerator(type, i + 1, 5, 1, 100);
boolean questionIsRepeat = QuestionUtils.duplicationCheck(question, userName, type);
if (questionIsRepeat) { // 重复了就在下一次循环的时候重新生成
i--;
continue;
}
QuestionMapper.writeQuestion(userName, questionName, question);
}
System.out.println("用户" + userName + "的" + number + "道" + type + "数学题目生成完毕,请在文件管理器中查看生成的文件");
}

小结: 采用Calendar类获取当前的时间,注意了月份要加一的细节,并且会先将试卷的类型写入文件,可以节省查重的时间。

2.2.6 UserMapper类

queryByUserNameAndPassword方法: 根据用户名和密码获取用户信息。

该方法调用FileUtils工具类的readLine方法从指定目录下读取用户信息,并根据传入的用户名和密码查询用户。

查询到则返回相应用户信息,否则返回空。

private static final String PathName = "./Generator/data/userInformation.txt";

/**
* 通过用户名和密码获取用户信息.
*
* @param userName 用户名。
* @param password 密码。
* @return 返回查询到的用户,若无,则返回空
*/
public static User queryByUserNameAndPassword(String userName, String password) {
User user = new User();
List<String> userInformationList = FileUtils.readLine(PathName);
assert userInformationList != null;
for (String item : userInformationList) {
String[] strings = item.split(",");
if (strings[0].equals(userName) && strings[1].equals(password)) {
user.setUserName(userName);
user.setPassword(password);
user.setUserType(strings[2]);
return user;
}
}
return null;
}

小结: 返回用户或空,供调用者进行判断。

2.2.7 QuestionMapper类

writeQuestion方法:向指定目录下的指定文件夹内写入题目(内容)。

该方法调用FileUtils工具类的writeLine方法进行文件换行写入。

/**
* 创建指定文件夹并写入题目.
*
* @param directoryName 目标目录。
* @param fileName 文件名。
* @param question 写入的题目内容。
*/
public static void writeQuestion(String directoryName, String fileName, String question) {
FileUtils.writeLine(directoryName, fileName, question);
}

小结: 通过调用FileUtils工具类中的方法实现。

2.2.8 FileUtils类

该类是一个工具类,用于对文件进行操作。

readLine方法:通过指定的文件路径,按行读取整个文件,存储到一个List列表中。

文件是事先创建好的,用于存储用户信息,因此若文件不存在,只会提示文件不存在,不会创建。

/**
* 从指定文件读取.
*
* <p>通过指定的文件路径,按行读取整个文件,存储到一个List列表中</p>
*
* @return 返回存储文件信息的列表
*/
public static List<String> readLine(String pathName) {
File file = new File(pathName); // 根据指定文件路径找到文件

List<String> userInformationList = new ArrayList<>();
try { // 正常读取文件
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
String userInformation;
while ((userInformation = bufferedReader.readLine()) != null) {
userInformationList.add(userInformation);
}
bufferedReader.close();
return userInformationList;
} catch (IOException expected) { // 文件不存在,抛出异常
System.out.println("文件不存在,请重新检查文件路径");
return null;
}
}

writeLine方法: 创建指定目录下的文件,并向其中写入内容。

创建的是存储题目的文件夹,为了方便其他同学测试,将文件夹存储在了项目所在目录下。

question文件夹不存在的情况下,也可以正常运行,因为程序进行了判断,不存在时会创建。

/**
* 存储每个用户的文件夹的父路径.
*/
private static final String parentDirectoryName = "./Generator/data/question/";

/**
* 写入文件.
*
* <p>创建指定目录下的文件,并向其中写入内容。</p>
*
* @param directoryName 需要创建的目录
* @param fileName 需要创建并写入的文件名
* @param content 需要写入的文件内容
*/
public static void writeLine(String directoryName, String fileName, String content) { //
File dir = new File(parentDirectoryName + directoryName);
boolean mkdirs = dir.mkdirs();
assert !mkdirs;

try {
File file = new File(dir, fileName);
boolean newFile = file.createNewFile();
assert !newFile;
} catch (IOException expected) {
System.out.println("创建文件失败");
}

try {
File file = new File(dir, fileName);
OutputStream outputStream = new FileOutputStream(file, true);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(content + "\n");
printWriter.close();
outputStream.close();
} catch (IOException expected) {
System.out.println("文件写入失败");
}
}

listFilesNameInFolder方法:查询指定用户目录下的文件名。

文件夹不存在时,返回空,否则返回相应的文件名列表。

查重功能根据文件名访问相应的文件,再进行查重判断。

/**
* 查询指定目录下的文件名.
*
* @param folderName 需要获取其中文件名的文件夹名称
* @return 返回文件名列表,返回空表示文件夹不存在或文件夹中不存在文件。
*/
public static List<String> listFilesNameInFolder(String folderName) {
File folder = new File(parentDirectoryName + folderName + "/");

File[] files = folder.listFiles();
List<String> filesName = new ArrayList<>();
if (files != null) {
for (File file : files) {
filesName.add(parentDirectoryName + folderName + "/" + file.getName());
}
return filesName;
}
return null;
}

小结: 文件创建是最容易出现异常的情景,该同学充分采用try catch语句,有助于程序的稳定性,即便除了差错,也不至于导致程序崩溃,这在开发中是很重要的。

2.2.9 QuestionUtils类

该类是一个工具类,用于进行题目生成和查重。

questionGenerator方法:根据传入的类型,生成符合要求的题目。

该方法的核心功能是通过调用本类下的其他三个方法generatePrimarygenerateJuniorgenerateSenior实现的。

它们分别生成小学、初中和高中的题目。

/**
* 随机生成指定类型、题号和相关限定条件的一道题目.
*
* @param type 题目类型
* @param order 题目序号
* @param maxCount 最大的操作数数量
* @param minValue 最小的操作数的值
* @param maxValue 最大的操作数的值
* @return 返回存储文件信息的列表
*/
public static String questionGenerator(String type,
int order,
int maxCount,
int minValue,
int maxValue) {
String question = null;
switch (type) {
case "小学":
question = generatePrimary(maxCount, minValue, maxValue, order);
break;
case "初中":
question = generateJunior(maxCount, minValue, maxValue, order);
break;
case "高中":
question = generateSenior(maxCount, minValue, maxValue, order);
break;
default:
}
return question;
}

duplicationCheck方法:查重方法。

该方法根据用户名找到(或找不到,直接返回false,不会重复)相应的文件夹。

再根据FileUtils类中的listFilesNameInFolder方法列出所有的文件名。

将传入的题目与文件中已经生成的题目一一比较,进行查重。

巧妙之处在于,当文件中题目的类型与传入的类型不符是,直接返回false,因为不同类型的题不会重复。

/**
* 查询某道题目是否与指定用户文件夹下已存在的文件中的题目重复.
*
* <p>题目的操作数相关的范围可以通过调整上面三个值改变。</p>
*
* @param question 某道题目
* @param userName 指定用户文件夹的名称
* @param type 题目类型,当生成的题目的类型与查询文件中题目的类型不符时,无需继续查询该文件。
* @return 是(true)否(false)重复
*/
public static boolean duplicationCheck(String question, String userName, String type) {
// 文件夹中所有文件的文件名列表
List<String> folderNames = FileUtils.listFilesNameInFolder(userName);
question = question.substring(question.indexOf("、") + 1);
if (folderNames != null) { // 文件夹存在且里面有文件,此时需要遍历每个文件
for (String folderName : folderNames) { // 遍历每个文件
List<String> contents = FileUtils.readLine(folderName); // 某个文件中的内容
if (contents != null) {
// 类型不一样时一定不会重复,无需继续循环
if (!contents.get(0).substring(0, 2).equals(type)) {
continue;
}
for (String content : contents) {
content = content.substring(content.indexOf("、") + 1);
if (question.equals(content)) { // 找到重复题目
return true;
}
}
}
}
}
// 文件夹不存在或文件夹中不存在文件,此时一定不会重复
return false;
}

fourRuleArithmetic方法:用于生成基本的四则运算。

注:方法generatePrimary、generateJunior和generateSenior均基于该方法实现,即在此基础上加上相应符号,因此不再单独展示这三个方法。

采用随机的思想,随机生成符合要求的操作数、随机生成相应的操作符。

// 生成基本的四则运算
private static StringBuilder fourRuleArithmetic(int opCount, /* 操作数的数量 */
int minValue, /* 操作数的最大值 */
int maxValue, /* 操作数的最小值 */
int[] opStart, /* 存储每个操作数开始位置的索引 */
int[] opEnd /* 存储每个操作数结束位置的索引 */) {
StringBuilder expression = new StringBuilder(); // 表达式

// 先第一个操作数
expression.append(random.nextInt(maxValue - minValue + 1) + minValue);
opStart[0] = 0;
opEnd[0] = expression.length();

for (int i = 0; i < opCount - 1; i++) { // 后续的操作数
char operator = "+-*/".charAt(random.nextInt(4)); // 随机选择运算符
expression.append(" ").append(operator).append(" ");
opStart[i + 1] = expression.length();
expression.append(random.nextInt(maxValue - minValue + 1) + minValue); // 随机选择操作数
opEnd[i + 1] = expression.length();
}
return expression;
}

小结: 这部分是相对不完美的地方,出题代码具有一定的冗余,在结对编程中我们会进行优化。

2.2.10 Result类和ResultConstant类

Result类定义了通用的返回结果。

方便统一管理,是前后端分离操作中很有意义的一步。

虽然本项目并不具备前端,但采用这种方式依然体现了统一性的方便。

private Integer code; // 状态码

private String message; // 提示信息

private Object data; // 返回数据

public Result() {
}

public Result(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}

public static Result success(Integer code) {
return new Result(code, "操作成功", null);
}

public static Result success(Integer code, Object data) {
return new Result(code, "操作成功", data);
}

public static Result error(Integer code) {
return new Result(code, "操作失败", null);
}

public static Result error(Integer code, Object data) {
return new Result(code, "操作失败", data);
}

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

@Override
public String toString() {
return "Result{"
+ "code=" + code
+ ", message='" + message + '\''
+ ", data=" + data
+ '}';
}

ResultConstant类定义了一些指定的状态码。

前文多次提到的状态码便来自此类。

对一些常数取了具备含义的名字,方便代码的阅读。

package edu.hnu.utils;

/**
* 返回结果常量类.
*
* @author 王明扬
* @version 1.0 (程序的当前版本号)
* @since 1.0 (加入该类时程序的版本号)
*/
public class ResultConstant {

public static final Integer LOGIN_COUNT_ERROR_CODE = 0; // 输入的登陆信息数量错误

public static final Integer LOGIN_MISTAKE_ERROR_CODE = 1; // 用户名或密码错误

public static final Integer NUMBER_STYLE_ERROR_CODE = 2; // 切换类型时输入不合规

public static final Integer QUESTION_NUMBER_ERROR_CODE = 3; // 题目数量不符合规矩异常

public static final Integer TURN_INPUT_ERROR_CODE = 4; // 切换类型时输入不合规

public static final Integer INSTRUCTION_INVALID_ERROR_CODE = 5; // 无效指令(只输入回车)

public static final Integer EXIT_SUCCESS_CODE = 10; // 退出系统

public static final Integer GENERATE_SUCCESS_CODE = 11; // 题目生成完毕

public static final Integer LOGIN_SUCCESS_CODE = 12; // 登录成功

public static final Integer LOGOUT_SUCCESS_CODE = 13; // 输入-1退出登陆

public static final Integer TURN_SUCCESS_CODE = 14; // 切换试卷类型

}

小结: 这里是模仿的web开发的习惯,方便统一管理。

3. 运行测试

注:在王同学的设定中,空格是无效输入。即每个输入的前后的空格都不会算输入的内容。

3.1 登陆测试

3.1.1 正常登录

由于空格算无效输入,因此以下几种状况都可以正常登录,符合要求。



3.1.2 登录失败

少输入或多输入(即便其中包含正确信息)或输入错误信息,都无法正常登录,符合要求。




3.2 出题测试

3.2.1 题目数量符合要求

只要输入的数量在规定范围内,不论是整数还是浮点数,都可以识别到,符合要求,并且考虑的很周到。



可以看到题目生成了:

3.2.2 题目数量不符合要求

通过下图可以看到,题目数量不在规定范围内时,会发出提示。

当输入的题目数量不能被看成一个整数时,也会发出提示,并且优先级高于范围判定。

符合要求。




3.3 切换类型测试

3.3.1 正常切换

基于空格无效的设定,图二可以正常切换。

王同学新增了一个设定:只要输入的切换指令中含有三个关键字,就会执行相应切换。

当含有不止一个关键字时,会以第一次出现的关键字为准,符合“第一直觉”的思想,如图四。

由于切换类型后再出题调用的是同一个方法,因此不额外展示切换后的出题测试,经测试没问题。

符合要求。




3.3.2 切换失败

当不含关键字时,切换失败或指令无效(直接回车)。



3.3 退出测试

3.3.1 退出登录测试

输入-1可以正常退出登录。

由于退出登录后再登录后调用的是同一个方法,因此不额外展示退出登录后的登录测试,经测试没问题。

符合要求。

3.3.2 退出系统测试

可以正常退出,符合功能(需求中未要求)。

3.4 题目查重

由于随机出题遇到重复的概率很小,因此手动调用QuestionUtils类中的duplicationCheck方法,并分别传入已出的和未出过的题目,然后打印其返回结果。

传入的是张三2文件夹下已经有的一道小学题目:

注释掉Application类中原有的main方法方法体,写下新的测试代码:

public static void main(String[] args) {
/*while (true) {
setUserByLogin();
if (exitFlag) {
System.out.println("感谢使用!");
break;
}
generateQuestion(user.getUserName(), user.getUserType());
}*/

// 重复题目
boolean result1 = QuestionUtils.duplicationCheck("5、68 * 84 * ( 62 - 42 / 68 ) =", "张三2", "小学");
System.out.println("题目是否重复:" + result1);
// 未重复题目
boolean result2 = QuestionUtils.duplicationCheck("5、69 * 84 * ( 62 - 42 / 68 ) =", "张三2", "小学");
System.out.println("题目是否重复:" + result2);

}

打印输出结果如下,说明查重功能正常,符合要求。

4. 优缺点分析

4.1 优点

(1)采用Web开发中后端的项目结构,每个包各司其职,并将Application类充当前端,项目结构清晰,易于维护。

(2)通过定义工具类封装一些方法,使每个类更加简洁,也增强了代码的可读性。

(3)将其他类不需要访问的方法定义成private,提高了程序的安全性,更加规范。

(4)当涉及到读写文件时,会进行try catch操作,防止程序异常终止,提高了程序的稳定性。

(5)文件操作均采用的相对路径,将生成的题目存在当前项目文件夹下,可移植性较高,同时也方便其他同学进行测试。

(6)定义通用的返回结果和一些见名之意的返回值,提高了代码的可读性和可维护性。

(7)JavaDoc和注释完整且规范,提高了程序的可读性。

4.2 缺点

(1)每道题目只会出现一组括号,并且第一个操作数前不会出现括号,是因为要防止括号括起来整个算式。

(2)高中题目的中三角函数只会出现一次。

(3)高中题目三角函数的角度是自然数,实际上是很难计算的。

(4)当查重的题目类型和文件中题目类型相同时,会遍历整个文件查询,效率较低,可能有更好的办法。

(5)即便已经将基本的四则运算抽出,但是生成初中和高中试卷的代码还是有一定重复。

5. 总结

王同学对于项目结构安排很有想法,采用Web开发的形式,是值得借鉴的一种方式,特别是对于熟悉Web开发的同学,会很亲切。

美中不足的就是出题部分,可以进一步去优化出题的逻辑,使得题目的实用性更高,也可以去抽取下初中高中部分的代码,降低那部分的冗余性。

标签:结对,CODE,题目,String,湖南大学,互评,static,return,public
From: https://www.cnblogs.com/lxhnu/p/17717653.html

相关文章

  • 【个人项目互评】————中小学生题目生成程序
    在完成个人项目的建设后,我和搭档互相交换了代码进行分析。在阅读过程中,看到了她代码的优势,也同时看到了自己代码的不足。再次写下这一次的项目分析;一、题目要求   二、测试与分析功能完成情况: 在输入正确的账户名以及密码后,能够获得当前用户年级信息以及后续操作步骤......
  • 【个人项目互评】结对互评-中小学数学试卷自动生成程序
    目录1.简介2.项目要求3.代码分析4.运行测试5.优缺点分析  1.简介本篇博客是对结对编程队友对于项目《中小学数学卷子自动生成系统》的学习,分析与总结,选用的编程语言为Java.  2.项目要求1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中......
  • 个人项目-小初高算数题目生成(Java互评)
    目录简介测试与分析功能测试代码分析总结一、简介本博客对结对编程队友高义林同学的个人项目进行分析测试,使用语言为Java。需求如下图所示:二、测试与分析1.功能测试1.登录功能测试分析:登录时输入错误账密、错误密码、空的账密均不可成功登录,可......
  • 结对编程队友个人项目互评
    一、项目简介1.项目名称:中小学数学卷子自动生成程序2.项目作者:党郑骅霖3.项目编程语言:Java 二、项目分析1.类图 2.流程图3.代码分析核心方法解析:1.`main`方法:-程序的入口点,包含主要的程序逻辑。-初始化已有用户列表,接收用户输入,允许用户登录,并根据用户的......
  • 软件工程导论个人项目互评
    本博客记录湖南大学2021级软件工程导论个人项目互评与代码分析成员:评价人:软件5班高义林项目作者:软件5班谢宇鑫需求:命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,X......
  • 【HNU个人项目互评】--java
    湖南大学软件工程个人项目互评评测者:李姝萱项目作者:张乐盈 一.基本信息:  1.被评队友满足了个人项目要求的所有需求  1.1.登录:只有规定的账户可以登陆,其他的输入不会因为异常抛出强制终止程序 输入不满足格式要求,不会越界异常错误账户不能登陆登陆成功,清晰......
  • 中小学数学卷子自动生成程序——结对编程队友互评
    结对编程队友互评代码:软件2105何志成评价:软件2105陈相彤一、题目介绍用户:小学、初中和高中数学老师。功能:1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为......
  • HNU个人项目互评博客
    HNU个人项目——中小学数学卷子自动生成程序一.前言本次个人项目中我的搭档袁秀广同学使用了Java进行开发。袁秀广同学的代码实现了自动生成题目的基本功能,注解详细,内容丰富,但在一些细节上也存在一些可优化的地方。二.需求分析1、命令行输入用户名和密码,两者之间用空格隔开(程......
  • 【结对编程互评-C++】中小学数学卷子自动生成程序
    【结对编程互评-C++】中小学数学卷子自动生成程序项目名称:中小学数学卷子自动生成程序编程语言:C++代码作者:李义评价人:张恒硕目录[1.项目要求][1.1目标用户][1.2实现功能][2.代码分析][3.功能测试][3.1登录功能测试][3.2出题功能测试][4.优缺点分析与总结]......
  • 个人项目互评
    结队编程,分析结队队友的代码能够让小组队员之间更加了解彼此的风格。指出队友的优缺点,既能两个人互相提醒改进,也可以在往后的结队编程项目达到更好的效果。后续我将分析队友方缘的C++代码的功能及性能。基本功能实现基本要求:用户:小学、初中和高中数学老师。功能:1、命令行输......