当前位置: 首页 > news >正文

软件工程第三次作业——结对作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470
这个作业的目标 完成结队项目:学习设计一个自动生成小学四则运算题目的命令行程序

一、个人信息

姓名 学号
何珊 3223004211
吴泓霏 3223004647

GitHub地址:https://github.com/Streflay/3223004211_-FourOperation

二、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 20
· Estimate · 估计这个任务需要多少时间 10 20
Development 开发 400 600
· Analysis · 需求分析 (包括学习新技术) 40 90
· Design Spec · 生成设计文档 20 15
· Design Review · 设计复审 10 15
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 10
· Design · 具体设计 20 30
· Coding · 具体编码 180 200
· Code Review · 代码复审 20 30
· Test · 测试(自我测试,修改代码,提交修改) 100 210
Reporting 报告 50 60
· Test Report · 测试报告 20 20
· Size Measurement · 计算工作量 10 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 20
合计 460 680

三、效能分析

微信图片_20251010124433_20_348

微信图片_20251010124433_21_348

四、设计实现过程

开发环境:IntelliJ IDEA Community Edition 2022.2.2
开发语言:JAVA

系统分为三层

入口层(Main):负责参数解析、调度各模块、结果输出。

功能层(Main、CheckAnswer、FourOperations、Fraction、IOUtils):实现文件 I/O、批改用户答案、自动生成题目、进行四则运算。

运维/测试层:单元测试(JUnit4)、性能分析(JProfiler/async-profiler 脚本)与代码质量工具(Checkstyle/PMD/SpotBugs)。

wechat_2025-10-10_173834_919

类与接口

Main:
职责:作为程序入口,解析命令行参数,执行生成题目或批改答案操作,处理程序运行流程及异常。
主要方法:

  • public static void main(String[] args)
    • 初始化扫描器、四则运算生成器 FourOperations、答案批改器 CheckAnswer,输出程序介绍与帮助信息;进入循环,获取用户输入命令,若为 exit 则退出程序,否则调用 executeCommand 执行命令,过程中捕获并处理各类异常,最后关闭扫描器。
  • private static void executeCommand(String commandLine, FourOperations generator, CheckAnswer checker) throws IOException
    • 拆分命令行参数,检查参数数量;根据命令首参数(-n、-r、-e),分别执行生成指定数量和范围的四则运算题目,或批改指定题目与答案文件的操作,若命令格式错误则抛出异常。
  • private static int parsePositiveInt(String s, String name)
    • 将字符串参数解析为正整数,若无法解析或数值非正,抛出包含具体错误信息的异常。
  • private static void printHelp()
    • 输出程序支持的命令及说明。

CheckAnswer:
职责:实现自动批改功能,读取题目文件、标准答案文件和用户答案文件,比对答案并生成批改结果文件。
接口:

  • public void check(String exerciseFile, String userAnswerFile) throws IOException
    • 读取题目文件、标准答案文件(Answers.txt)、用户答案文件内容;检查三者数量是否一致,不一致则抛出异常;遍历比对每道题答案,记录正确与错误题号;生成包含每题详情和总评的内容,写入 Grade.txt。

FourOperations:
职责:作为题目生成模块,负责生成指定数量和数值范围的四则运算题目,并生成对应的答案文件。
接口:

  • public void generateExercises(int count, int range) throws IOException
    • 校验题目数量和数值范围为正整数;在尝试次数上限内,循环生成候选表达式,检查是否重复,评估并验证表达式中间约束(减法中间结果非负、除法子表达式为真分数等),符合要求则记录;最后将题目和答案分别写入Exercises.txt和Answers.txt,若尝试超限则抛运行时异常。
  • private String generateCandidateExpression(int range)
    • 随机确定 1 - 3 个运算符数量,生成操作数和运算符组成的 tokens 列表;若运算符数≥2 且随机条件满足,随机插入一对包围至少一个运算符的括号;将 tokens 以空格分隔成候选表达式字符串返回。
  • private String randomOperand(int range)
    • 随机生成一个操作数:整数或分数(可能为带分数形式)
  • private void writeListToFile(String filename, List lines) throws IOException
    • 将 list 写入文件(每个元素一行)
  • private Fraction evaluateExpressionAndValidate(String expr)
    • 调用infixToPostfix转中缀为后缀表达式,计算时验证减法、除法等约束,符合则返回最终结果(Fraction类型),否则返回null。
  • private List infixToPostfix(String expr)
    • 用调度场算法,将中缀表达式(token 以空格分隔)转为后缀表达式列表。
  • private boolean isOperator(String t)
    • 判断字符串t是否为+、-、×、÷运算符。
  • private int precedence(String op)
    • 返回运算符优先级,+、-为 1,×、÷为 2,其他为 0。

Fraction:
职责:精确分数类,用于表示和处理分数运算,支持分数的解析、四则运算及相关属性判断,能按要求格式输出分数。
接口:

  • public static Fraction parse(String s)
    • 解析字符串为分数,支持整数、a/b 形式的普通分数、a’b/c 形式的带分数。
  • public Fraction add(Fraction o)
    • 加法
  • public Fraction sub(Fraction o)
    • 减法
  • public Fraction mul(Fraction o)
    • 乘法
  • public Fraction div(Fraction o)
    • 除法
  • public boolean isZero()
    • 判断是否为0
  • public boolean isNegative()
    • 判断是否为负数
  • public boolean isPositive()
    • 判断是否为正数
  • public boolean isProperFraction()
    • 判断是否为真分数
  • private void simplify()
    • 约分
  • private long gcd(long a, long b)
    • 计算最大公约数
  • public String toString()
    • 按要求格式输出分数,整数、普通分数或带分数形式。

IOUtils:
职责:文件操作工具类,提供文件的行读取和行写入功能。
接口:

  • public static List readLines(String filename) throws IOException
    • 读取文件每一行到 List
  • public static void writeLines(String filename, List lines) throws IOException
    • 将 List 写入文件,每行一条

五、代码说明

1、Fraction.java - 精确分数核心类(保障分数运算准确性)

分数是小学四则运算的核心,此类解决 “浮点数精度丢失” 问题,支持分数解析、运算与格式化输出。

点击查看代码
/*** Fraction - 精确分数类,使用 long 存储分子与分母* 支持:*  - 解析字符串(整数、a/b、带分数 a’b/c)*  - 加减乘除(返回化简后的 Fraction)*  - 判断是否为负、是否为零、是否为真分数(|num| < den)*  - toString 按题目要求输出:整数 / a/b / 带分数 a’b/c*/
public class Fraction {private long num; // 分子private long den; // 分母,始终 >0public Fraction(long num, long den) {if (den == 0) throw new ArithmeticException("分母不能为 0");// 规范化:把符号放到分子上,分母始终 >0if (den < 0) {den = -den;num = -num;}this.num = num;this.den = den;simplify();}// 解析字符串:支持 "3", "3/5", "2’3/8" (注意这里的分隔符为 U+2019 或 ASCII apostrophe)public static Fraction parse(String s) {if (s == null) return null;s = s.trim();if (s.isEmpty()) return null;try {// 带分数检查(可能使用 ’ 或 ')if (s.contains("’") || s.contains("'")) {String[] parts = s.contains("’") ? s.split("’") : s.split("'");if (parts.length != 2) return null;long whole = Long.parseLong(parts[0]);String fracPart = parts[1];String[] fr = fracPart.split("/");if (fr.length != 2) return null;long a = Long.parseLong(fr[0]);long b = Long.parseLong(fr[1]);long numerator = Math.abs(whole) * b + a;numerator = whole >= 0 ? numerator : -numerator;return new Fraction(numerator, b);} else if (s.contains("/")) {String[] p = s.split("/");if (p.length != 2) return null;long a = Long.parseLong(p[0]);long b = Long.parseLong(p[1]);return new Fraction(a, b);} else {long v = Long.parseLong(s);return new Fraction(v, 1);}} catch (NumberFormatException ex) {return null;}}public Fraction add(Fraction o) {long n = this.num * o.den + o.num * this.den;long d = this.den * o.den;return new Fraction(n, d);}public Fraction sub(Fraction o) {long n = this.num * o.den - o.num * this.den;long d = this.den * o.den;return new Fraction(n, d);}public Fraction mul(Fraction o) {long n = this.num * o.num;long d = this.den * o.den;return new Fraction(n, d);}public Fraction div(Fraction o) {if (o.num == 0) throw new ArithmeticException("除以零");long n = this.num * o.den;long d = this.den * o.num;if (d < 0) { n = -n; d = -d; }return new Fraction(n, d);}public boolean isZero() {return this.num == 0;}public boolean isNegative() {return this.num < 0;}public boolean isPositive() {return this.num > 0;}public boolean isProperFraction() {return Math.abs(this.num) < this.den && this.num != 0;}// 约分private void simplify() {long g = gcd(Math.abs(num), den);if (g != 0) {num /= g;den /= g;}}private long gcd(long a, long b) {if (b == 0) return a;return gcd(b, a % b);}@Overridepublic String toString() {if (den == 1) {return String.valueOf(num); // 整数} else {long absNum = Math.abs(num);if (absNum > den) {long whole = absNum / den;long rem = absNum % den;String sign = num < 0 ? "-" : "";return rem == 0 ? sign + whole : sign + whole + "’" + rem + "/" + den;} else {String sign = num < 0 ? "-" : "";return sign + absNum + "/" + den;}}}
}

2、 FourOperations.java - 题目生成核心类(按约束生成合规题目)

此类负责生成符合小学教学要求的题目,核心约束:减法中间结果非负、除法结果为真分数、题目不重复。

点击查看代码
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;/*** FourOperations - 题目生成模块* 提供 public void generateExercises(int count, int range)* 会在当前目录写出 Exercises.txt 和 Answers.txt*/
public class FourOperations {private static final String[] OPERATORS = {"+", "-", "×", "÷"};private final Random random = new Random();private final Set<String> generatedExpressions = new HashSet<>();public FourOperations() {// 无参构造,Main 中以 new FourOperations() 使用}/*** 由 Main 调用:生成 count 道题,数值范围为 [0, range)* 会生成 Exercises.txt 和 Answers.txt*/public void generateExercises(int count, int range) throws IOException {if (count <= 0) throw new IllegalArgumentException("count 必须为正整数");if (range <= 0) throw new IllegalArgumentException("range 必须为正整数");List<String> exercises = new ArrayList<>(count);List<String> answers = new ArrayList<>(count);// 尝试次数上限:根据题目数量自动扩大int attemptsLimit = Math.max(10000, count * 20);int attempts = 0;while (exercises.size() < count) {if (++attempts > attemptsLimit) {throw new RuntimeException("生成题目失败:尝试次数过多,可能范围太小或约束过严。\n" +"建议:增加数值范围 (-r 参数) 或减少题目数量 (-n 参数) 以生成足够题目。\n" +"当前已生成题目数:" + exercises.size() + " / " + count +"\n尝试次数上限:" + attemptsLimit);}String expr = generateCandidateExpression(range);if (expr == null) continue;// 检查重复(按字符串)if (generatedExpressions.contains(expr)) continue;// 评估表达式并检查约束Fraction result = evaluateExpressionAndValidate(expr);if (result == null) continue;generatedExpressions.add(expr);exercises.add(expr);answers.add(result.toString());}writeListToFile("Exercises.txt", exercises);writeListToFile("Answers.txt", answers);System.out.println("已生成 " + exercises.size() + " 道题目和答案文件。");}// 生成一个候选表达式(字符串,token 之间以空格分隔)private String generateCandidateExpression(int range) {int operatorCount = random.nextInt(3) + 1; // 1 ~ 3 个运算符List<String> tokens = new ArrayList<>();for (int i = 0; i < operatorCount + 1; i++) {tokens.add(randomOperand(range));if (i < operatorCount) {tokens.add(OPERATORS[random.nextInt(OPERATORS.length)]);}}// 随机插入一对括号(有 50% 概率),括号包围至少一个运算符if (operatorCount >= 2 && random.nextBoolean()) {int leftOpIdx = random.nextInt(operatorCount); // 0..operatorCount-1 表示左侧操作数索引int rightOpIdx = leftOpIdx + 1 + random.nextInt(operatorCount - leftOpIdx); // 至少覆盖一个运算符int leftTokenIndex = leftOpIdx * 2; // token 索引int rightTokenIndex = rightOpIdx * 2;// 插入 "(" 在 leftTokenIndex 处,插入 ")" 在 rightTokenIndex 后(要加偏移)tokens.add(leftTokenIndex, "(");tokens.add(rightTokenIndex + 2, ")"); // +2:因为上面插入 "(" 后索引右移 1}// 返回以空格分隔的表达式return String.join(" ", tokens);}// 随机生成一个操作数:整数或分数(可能为带分数形式)private String randomOperand(int range) {// 40% 生成整数,60% 生成分数/带分数(可调)boolean makeFraction = random.nextDouble() < 0.6;if (!makeFraction) {int val = random.nextInt(range); // 0 .. range-1return String.valueOf(val);} else {// 生成分母和分子、或带分数int denom = random.nextInt(Math.max(1, range - 1)) + 1; // 1..range-1 (至少1)int numer = random.nextInt(Math.max(1, denom)) + 1;    // 1..denom (暂时允许 >= denom 以产带分数)// 50% 可能做带分数if (numer >= denom && random.nextBoolean()) {int whole = numer / denom;int remain = numer % denom;if (remain == 0) return String.valueOf(whole);return whole + "’" + remain + "/" + denom; // 带分数格式 2’3/8} else {// 普通真分数或假分数都允许(后面会在校验时剔除不符合 ÷ 约束的项)// 但尽量使分子 < 分母 提高成为真分数的概率if (numer >= denom) {// 使其更可能成为真分数int n = random.nextInt(Math.max(1, denom - 1)) + 1; // 1..denom-1return n + "/" + denom;} else {return numer + "/" + denom;}}}}// 将 list 写入文件(每个元素一行)private void writeListToFile(String filename, List<String> lines) throws IOException {try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename))) {for (String s : lines) {bw.write(s);bw.newLine();}}}/*** 评估表达式并验证中间约束:* - 在执行任何减法(a - b)时,不允许中间结果为负(即 a - b >= 0)。* - 在执行任何除法(a ÷ b)时,该子表达式的结果必须是“真分数”(0 < value < 1)。** 若表达式满足,返回最终结果(Fraction);否则返回 null。*/private Fraction evaluateExpressionAndValidate(String expr) {try {List<String> postfix = infixToPostfix(expr);// evaluate postfix while checking constraintsDeque<Fraction> stack = new ArrayDeque<>();for (String token : postfix) {if (isOperator(token)) {// pop b then aif (stack.size() < 2) return null;Fraction b = stack.pop();Fraction a = stack.pop();Fraction res;switch (token) {case "+":res = a.add(b);break;case "-":res = a.sub(b);// 中间结果不能为负if (res.isNegative()) return null;break;case "×":res = a.mul(b);break;case "÷":// 除法:分母不能为 0if (b.isZero()) return null;res = a.div(b);// 要求该子表达式结果为真分数(大于0且小于1)if (!(res.isPositive() && res.isProperFraction())) return null;break;default:return null;}stack.push(res);} else {// 操作数Fraction f = Fraction.parse(token);if (f == null) return null;stack.push(f);}}if (stack.size() != 1) return null;Fraction finalRes = stack.pop();// 最终结果也要求非负(题目中通常不希望最终答案为负)if (finalRes.isNegative()) return null;return finalRes;} catch (Exception e) {// 任何解析/计算异常都视为无效表达式return null;}}// 中缀转后缀(shunting-yard),输入 token 间以空格分隔private List<String> infixToPostfix(String expr) {String[] tokens = expr.trim().split("\\s+");List<String> output = new ArrayList<>();Deque<String> ops = new ArrayDeque<>();for (String t : tokens) {if (t.equals("(")) {ops.push(t);} else if (t.equals(")")) {while (!ops.isEmpty() && !ops.peek().equals("(")) {output.add(ops.pop());}if (!ops.isEmpty() && ops.peek().equals("(")) ops.pop();} else if (isOperator(t)) {while (!ops.isEmpty() && isOperator(ops.peek())&& precedence(ops.peek()) >= precedence(t)) {output.add(ops.pop());}ops.push(t);} else {// 操作数output.add(t);}}while (!ops.isEmpty()) {output.add(ops.pop());}return output;}private boolean isOperator(String t) {return "+".equals(t) || "-".equals(t) || "×".equals(t) || "÷".equals(t);}private int precedence(String op) {if ("+".equals(op) || "-".equals(op)) return 1;if ("×".equals(op) || "÷".equals(op)) return 2;return 0;}
}

六、测试运行

测试覆盖率

微信图片_20251010124434_23_348

测试展示

微信图片_20251010124434_22_348

点击查看代码
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.io.TempDir;import java.io.IOException;
import java.nio.file.*;
import java.util.*;import static org.junit.jupiter.api.Assertions.*;/*** AllTests - 将对 Fraction, IOUtils, FourOperations, CheckAnswer 的测试放入同一文件。** 运行前请确保你的项目能够引用 JUnit 5。*/public class AllTest {private String originalUserDir;@BeforeEachpublic void beforeEach(@TempDir Path tempDir) {// 保存原始工作目录以便恢复originalUserDir = System.getProperty("user.dir");// 每个测试会在自己的临时目录下运行(由 @TempDir 提供)System.setProperty("user.dir", tempDir.toAbsolutePath().toString());}@AfterEachpublic void afterEach() {// 恢复原始工作目录if (originalUserDir != null) {System.setProperty("user.dir", originalUserDir);}}@Testpublic void testFractionParsingAndArithmetic() {// 解析整数Fraction f1 = Fraction.parse("3");assertNotNull(f1);assertEquals("3", f1.toString());// 解析真分数Fraction f2 = Fraction.parse("3/5");assertNotNull(f2);assertEquals("3/5", f2.toString());// 解析带分数(两种可能的单引号)Fraction f3 = Fraction.parse("2’3/8");assertNotNull(f3);assertEquals("2’3/8", f3.toString());Fraction f3a = Fraction.parse("2'3/8");assertNotNull(f3a);assertEquals("2’3/8".replace("’", "’"), f3a.toString().replace("'", "’")); // 格式化差异容忍// 四则运算: 1/6 + 1/8 = 7/24Fraction a = Fraction.parse("1/6");Fraction b = Fraction.parse("1/8");Fraction sum = a.add(b);assertEquals("7/24", sum.toString());// 乘除: 3 * 2 = 6Fraction three = new Fraction(3, 1);Fraction two = new Fraction(2, 1);assertEquals("6", three.mul(two).toString());// 减法与负数判断Fraction sub = two.sub(three); // 2 - 3 = -1assertTrue(sub.isNegative());assertEquals("-1", sub.toString());}@Testpublic void testIOUtilsReadWriteFiles(@TempDir Path tempDir) throws IOException {// 在临时目录下写入并读取文件,IOUtils 使用相对路径,因此 user.dir 已设置为 tempDirList<String> toWrite = Arrays.asList("line1", "line2", "1/2 + 1/2 =");IOUtils.writeLines("io_test.txt", toWrite);List<String> readBack = IOUtils.readLines("io_test.txt");assertEquals(toWrite.size(), readBack.size());assertEquals(toWrite, readBack);// 再测试写空文件IOUtils.writeLines("empty.txt", Collections.emptyList());List<String> e = IOUtils.readLines("empty.txt");assertTrue(e.isEmpty());}@Testpublic void testFourOperationsGenerateExercisesAndAnswersFiles() throws Exception {FourOperations generator = new FourOperations();// 生成 5 道题,范围 20(足够多)generator.generateExercises(5, 20);// 检查生成的 Exercises.txt 与 Answers.txt 存在并行数正确List<String> exercises = IOUtils.readLines("Exercises.txt");List<String> answers = IOUtils.readLines("Answers.txt");assertNotNull(exercises);assertNotNull(answers);assertEquals(5, exercises.size(), "Exercises.txt 应包含 5 行");assertEquals(5, answers.size(), "Answers.txt 应包含 5 行");// 确认每道题对应的答案非空for (String ans : answers) {assertNotNull(ans);assertFalse(ans.trim().isEmpty());}}@Testpublic void testFourOperationsFailureWhenRangeTooSmall() {FourOperations generator = new FourOperations();try {// 调用可能会抛异常,也可能在随机尝试中恰好生成成功generator.generateExercises(1000, 2);// 如果没有抛异常,则验证生成的文件存在且看起来合理List<String> ex = IOUtils.readLines("Exercises.txt");List<String> an = IOUtils.readLines("Answers.txt");// 基本断言:至少写入了 1 道题,答案行数与题目行数一致assertNotNull(ex);assertNotNull(an);assertTrue(ex.size() > 0, "当未抛异常时应至少生成 1 道题");assertEquals(ex.size(), an.size(), "题目数与答案数应相等");} catch (RuntimeException e) {// 抛异常也接受,断言消息包含我们期望的提示(可根据实际消息微调)String msg = e.getMessage() == null ? "" : e.getMessage();assertTrue(msg.contains("尝试次数") || msg.toLowerCase().contains("range") || msg.contains("失败"),"抛出的异常信息应表明生成失败或范围/约束不足: " + msg);} catch (IOException io) {fail("IO 操作异常: " + io.getMessage());}}@Testpublic void testCheckAnswerAllCorrectAndSomeWrong() throws Exception {// 创建简单的题目、标准答案与用户答案文件List<String> exercises = Arrays.asList("1 + 1", "1/2 + 1/2", "2 + 3");List<String> answers = Arrays.asList("2", "1", "5"); // Answers.txt (标准答案)List<String> userAllCorrect = Arrays.asList("2", "1", "5");List<String> userSomeWrong = Arrays.asList("2", "2", "5"); // 第二题答错// 写入文件(CheckAnswer.check 期望读取 Exercises.txt、Answers.txt,用户答案由参数传入)IOUtils.writeLines("Exercises.txt", exercises);IOUtils.writeLines("Answers.txt", answers);IOUtils.writeLines("UserAllCorrect.txt", userAllCorrect);IOUtils.writeLines("UserSomeWrong.txt", userSomeWrong);// 1) 全对CheckAnswer checker = new CheckAnswer();checker.check("Exercises.txt", "UserAllCorrect.txt");List<String> grade1 = IOUtils.readLines("Grade.txt");// 应包含 Correct: 3boolean containsCorrect3 = grade1.stream().anyMatch(l -> l.startsWith("Correct:") && l.contains("3"));assertTrue(containsCorrect3, "Grade.txt 应显示 3 道题正确");// 2) 有错checker.check("Exercises.txt", "UserSomeWrong.txt");List<String> grade2 = IOUtils.readLines("Grade.txt");boolean containsCorrect2 = grade2.stream().anyMatch(l -> l.startsWith("Correct:") && l.contains("2"));boolean containsWrong1 = grade2.stream().anyMatch(l -> l.startsWith("Wrong:") && l.contains("1"));assertTrue(containsCorrect2, "Grade.txt 应显示 2 道题正确");assertTrue(containsWrong1, "Grade.txt 应显示 1 道题错误");}
}

1. 分数处理核心类(Fraction)的正确性

测试方法:testFractionParsingAndArithmetic
解析功能验证:测试整数("3")、真分数("3/5")、带分数("2’3/8" 和 "2'3/8")的解析是否正确,确保不同格式的分数都能被准确转换为内部表示。
运算逻辑验证:通过具体案例(如 1/6 + 1/8 = 7/24、3 × 2 = 6)验证加减乘除的结果是否符合数学逻辑,同时检查负数判断(如 2 - 3 = -1)是否准确。
格式输出验证:确保分数的字符串输出符合小学题目要求(如带分数用 ’ 分隔,真分数直接显示 a/b)。
结论:分数的解析、运算和格式化均通过测试,说明基础数据处理层正确。

2. 文件操作工具(IOUtils)的可靠性

测试方法:testIOUtilsReadWriteFiles
读写一致性验证:向临时文件写入内容后立即读取,检查读写内容是否完全一致,确保文件操作无数据丢失或篡改。
边界情况验证:测试空文件的读写(写入空列表,读取后仍为空),确保工具类对特殊场景的处理正确。
结论:文件读写功能稳定,数据传输准确,为题目生成和批改提供了可靠的 IO 支持。

3. 题目生成模块(FourOperations)的合规性

测试方法:testFourOperationsGenerateExercisesAndAnswersFiles 和 testFourOperationsFailureWhenRangeTooSmall
正常场景验证:生成 5 道范围为 20 的题目,检查输出文件(Exercises.txt 和 Answers.txt)的行数是否匹配,且答案非空,确保题目生成流程完整。
约束与容错验证:测试极端场景(如生成 1000 道范围为 2 的题目),验证程序是否能正确处理 “因范围过小导致生成失败” 的情况(要么生成部分题目,要么抛出包含明确提示的异常)。
结论:题目生成逻辑符合预期,能在合理范围内生成合规题目,并对极端情况做出容错处理。

4. 答案批改模块(CheckAnswer)的准确性

测试方法:testCheckAnswerAllCorrectAndSomeWrong
全对场景验证:用户答案与标准答案完全一致时,检查批改结果(Grade.txt)是否正确统计 “全对”(3 道正确)。
部分错误场景验证:人为设置 1 道错误答案,检查批改结果是否准确识别错误题目,统计 “2 对 1 错”。
结论:答案比对逻辑准确,批改结果的统计和输出符合预期,能正确反映用户答题情况。

七、项目小结

1、系统核心功能分为两大模块

题目生成:支持指定题目数量(-n 参数)和数值范围(-r 参数),自动生成包含整数、分数(含带分数)、括号的四则运算题,输出 Exercises.txt(题目文件)和 Answers.txt(标准答案文件),并确保题目无重复、运算约束合规(减法中间结果非负、除法结果为真分数)。
答案批改:读取题目文件(-e 参数)、用户答案文件(-a 参数)和标准答案文件,逐题比对答案并生成 Grade.txt(批改结果),包含每题详情(题目、用户答案、标准答案、对错)及总评(正确 / 错误题数与题号)。
系统技术架构采用 Java 语言开发,通过 5 个类实现功能解耦:Main(入口与命令解析)、FourOperations(题目生成)、CheckAnswer(答案批改)、Fraction(精确分数处理)、IOUtils(文件操作工具),确保代码可维护性与扩展性。

2、开发分工与协作流程

我们采用 “功能模块分工 + 交叉 review” 的协作模式,具体分工如下:

核心负责模块 辅助工作
分数处理(Fraction)、题目生成(FourOperations) 参与测试用例设计、命令行交互优化
答案批改(CheckAnswer)、文件工具(IOUtils) 负责 Main 类逻辑、测试代码编写、文档整理

协作流程遵循 “小步迭代 + 即时沟通” 原则:

  • 每日同步:开发前明确当天任务目标,开发后同步进度与问题(如在实现分数除法时遇到 “分母为负” 问题,两人讨论后确定 “符号转移到分子” 的解决方案);
  • 交叉 review:完成一个模块后,对方需 review 代码(如 review FourOperations 时,发现 “括号插入索引计算错误”,及时修正避免后续问题);
  • 共同调试:遇到集成问题(如题目生成后批改结果异常),两人共同排查,定位到 “题目末尾未加‘=’导致读取时格式不匹配” 的问题,快速修复。

3、总结结对项目的要点以及改进方向

前期需统一接口规范(如文件格式、方法参数)避免后期返工,开发中应同步编写测试用例实现早发现早修复,未来还可从功能扩展等方面完善项目,让协作更高效、成果更贴合需求。

http://www.hskmm.com/?act=detail&tid=29284

相关文章:

  • 20232310 2025-2026-1 《网络与系统攻防技术》 实验一实验报告
  • 2025年CNC高压清洗机订做厂家权威推荐榜:技术实力与定制
  • K8s学习笔记(八) K8s资源对象 - 教程
  • 小分子抗体药物:突破传统抗体瓶颈,在精准治疗中开辟新赛道
  • python nms
  • 2025年PE涂布机定做厂家权威推荐榜:技术实力与定制服务深
  • OI 笑传 #18
  • 2025加药装置厂家权威推荐榜:精准计量与稳定运行优选指南
  • Linux文本搜索工具grep命令使用
  • 一款基于 .NET 开源免费、高效且用户友好文件搜索工具!
  • 2025上海保洁公司最新权威推荐榜:专业服务与用户口碑深度解
  • DedeCMS命令执行复现研究 | CVE-2025-6335 - 指南
  • 算法训练.16 - 实践
  • __pycache__是什么
  • 心得体会
  • 2025视频拍摄厂家最新权威推荐榜:专业设备与创意方案首选
  • Java连接MySQL数据库
  • Redis基础命令与数据结构概览
  • 2025年媒体投放机构权威推荐榜:精准策略与创新执行优选厂家
  • PHP计算过去一定时间段内日期范围函数
  • Git版本控制工具合并分支merge命令操作流程
  • 第七章 手写数字识别(终)
  • 2025南通摄影公司最新权威推荐榜:专业团队与创意服务口碑之
  • 在Kubernetes环境中引用变量的方法
  • 2025恒温恒湿车间厂家权威推荐:精密环境控制解决方案TOP
  • 2025预应力千斤顶厂家权威推荐榜:定制技术与耐用品质深度解
  • 实用指南:用Spark+Django打造食物营养数据可视化分析系统
  • 2025液压阀块厂家权威推荐榜:精密加工与直销优势深度解析
  • NOI/1.7编程基础之字符串/18:验证子串
  • 深入解析:【Linux网络】Socket编程:UDP网络编程实现DictServer