结对项目

这个作业属于哪个课程 软件工程
这个作业要求在哪里 结对项目
这个作业的目标 团队协作与沟通、代码规范与项目管理
项目成员 郑邦洲3123004767林佳俊3123004748

Github仓库

PSP表格

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

模块设计

类名 职责描述
Main 解析命令行参数,选择生成模式或校验模式
ExpressionGenerator 递归生成表达式树,确保数值范围和运算合法性,避免重复题目
Fraction 封装分数运算(加减乘除),处理带分数、约分和负数逻辑
ExerciseChecker 解析题目文件,计算正确答案并与用户答案对比
FileUtils 统一处理文件读写操作

效能分析


关键代码展示

分数类 Fraction.java

public class Fraction {
    private int numerator;   // 分子
    private int denominator; // 分母

    // 构造方法:自动约分并处理负数
    public Fraction(int numerator, int denominator) {
        int gcd = MathUtils.gcd(numerator, denominator);
        this.numerator = numerator / gcd;
        this.denominator = denominator / gcd;
        if (this.denominator < 0) { // 确保分母为正
            this.numerator *= -1;
            this.denominator *= -1;
        }
    }

    // 加法运算
    public Fraction add(Fraction other) {
        int num = this.numerator * other.denominator + other.numerator * this.denominator;
        int den = this.denominator * other.denominator;
        return new Fraction(num, den);
    }

    // 输出格式化:带分数或真分数
    @Override
    public String toString() {
        if (denominator == 1) return String.valueOf(numerator);
        int whole = numerator / denominator;
        int num = Math.abs(numerator % denominator);
        return whole != 0 ? whole + "'" + num + "/" + denominator : numerator + "/" + denominator;
    }
}

2. 表达式生成器 ExpressionGenerator.java

public class ExpressionGenerator {
    private Node buildNode(int operators) {
        if (operators == 0) {
            return new NumberNode(generateNumber()); // 生成叶子节点(数值)
        }

        char op = randomOperator(); // 随机选择运算符
        Node left = buildNode(leftOps);
        Node right = buildNode(rightOps);

        // 交换左右子树保证 + 和 × 不重复
        if (op == '+' || op == '×') {
            if (left.value().compareTo(right.value()) > 0) {
                swap(left, right);
            }
        }

        // 计算并验证结果合法性
        Fraction value = calculate(op, left.value(), right.value());
        return new OperatorNode(op, left, right, value);
    }

    private interface Node {
        Fraction value();
        String toString();
    }
}

3. 答案校验器 ExerciseChecker.java

public class ExerciseChecker {
    public static void check(String exerciseFile, String answerFile) {
        List<String> exercises = FileUtils.readLines(exerciseFile);
        List<String> userAnswers = FileUtils.readLines(answerFile);

        for (int i = 0; i < exercises.size(); i++) {
            String expr = exercises.get(i).replace(" =", "");
            Fraction correct = evaluate(expr); // 计算正确答案
            Fraction userAns = new Fraction(userAnswers.get(i));
            if (correct.equals(userAns)) {
                correctIndices.add(i + 1);
            } else {
                wrongIndices.add(i + 1);
            }
        }
    }

    // 使用逆波兰表达式求值
    private static Fraction evaluate(String expr) {
        List<String> tokens = Tokenizer.tokenize(expr);
        List<String> postfix = InfixToPostfix.convert(tokens);
        return PostfixEvaluator.evaluate(postfix);
    }
}

关键设计思路

  1. 分数统一化
    • 所有数值(自然数、真分数)均用 Fraction 类处理,避免混合运算错误。
    • 自动约分保证结果唯一性,例如 4/8 存储为 1/2
  2. 表达式树生成
    • 递归构建二叉树结构,确保运算符数量不超过3个。
    • 通过交换左右子树避免 a + bb + a 的重复。
  3. 答案校验优化
    • 使用逆波兰表达式算法保证计算顺序正确。
    • 分离 TokenizerInfixToPostfixPostfixEvaluator 实现模块化。
  4. 文件操作封装
    • FileUtils 统一处理编码和异常,避免重复代码

单元测试

import static org.junit.Assert.*;
import java.util.*;
import org.junit.jupiter.api.*;
import org.mockito.*;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

// 测试类定义
public class ExpressionGeneratorTest {

    @Mock
    private Random random;

    @InjectMocks
    private ExpressionGenerator expressionGenerator;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    /**
     * 测试正常场景:生成多个表达式
     * 预期结果:返回指定数量的表达式,每个表达式都是唯一的
     */
    @Test
    @DisplayName("测试正常场景:生成多个表达式")
    public void testGenerate_NormalScenario() {
        // 模拟输入参数
        int count = 5;

        // 模拟生成表达式的方法
        when(random.nextInt(3)).thenReturn(1, 2, 1, 2, 1);
        when(expressionGenerator.generateExpression(1)).thenReturn(new Node("1 + 1", 2), new Node("2 + 2", 4));
        when(expressionGenerator.generateExpression(2)).thenReturn(new Node("1 + 2 + 3", 6), new Node("2 + 3 + 4", 9));

        // 调用被测方法
        List<String> result = expressionGenerator.generate(count);

        // 验证结果
        assertEquals(5, result.size());
        assertTrue(result.contains("1 + 1 = "));
        assertTrue(result.contains("2 + 2 = "));
        assertTrue(result.contains("1 + 2 + 3 = "));
        assertTrue(result.contains("2 + 3 + 4 = "));
        // 假设生成的表达式是唯一的
        assertEquals(5, new HashSet<>(result).size());
    }

    /**
     * 测试边界条件:生成0个表达式
     * 预期结果:返回空的List
     */
    @Test
    @DisplayName("测试边界条件:生成0个表达式")
    public void testGenerate_ZeroCount() {
        // 模拟输入参数
        int count = 0;

        // 调用被测方法
        List<String> result = expressionGenerator.generate(count);

        // 验证结果
        assertTrue(result.isEmpty());
    }

    /**
     * 测试边界条件:生成1个表达式
     * 预期结果:返回包含1个表达式的List
     */
    @Test
    @DisplayName("测试边界条件:生成1个表达式")
    public void testGenerate_SingleExpression() {
        // 模拟输入参数
        int count = 1;

        // 模拟生成表达式的方法
        when(random.nextInt(3)).thenReturn(1);
        when(expressionGenerator.generateExpression(1)).thenReturn(new Node("1 + 1", 2));

        // 调用被测方法
        List<String> result = expressionGenerator.generate(count);

        // 验证结果
        assertEquals(1, result.size());
        assertTrue(result.contains("1 + 1 = "));
    }

    /**
     * 测试异常处理:生成表达式时返回null
     * 预期结果:跳过null表达式,继续生成其他表达式
     */
    @Test
    @DisplayName("测试异常处理:生成表达式时返回null")
    public void testGenerate_NullExpression() {
        // 模拟输入参数
        int count = 3;

        // 模拟生成表达式的方法
        when(random.nextInt(3)).thenReturn(1, 2, 1);
        when(expressionGenerator.generateExpression(1)).thenReturn(new Node("1 + 1", 2), null);
        when(expressionGenerator.generateExpression(2)).thenReturn(new Node("1 + 2 + 3", 6));

        // 调用被测方法
        List<String> result = expressionGenerator.generate(count);

        // 验证结果
        assertEquals(2, result.size());
        assertTrue(result.contains("1 + 1 = "));
        assertTrue(result.contains("1 + 2 + 3 = "));
    }

    /**
     * 测试异常处理:生成表达式时抛出异常
     * 预期结果:捕获异常,继续生成其他表达式
     */
    @Test
    @DisplayName("测试异常处理:生成表达式时抛出异常")
    public void testGenerate_Exception() {
        // 模拟输入参数
        int count = 3;

        // 模拟生成表达式的方法
        when(random.nextInt(3)).thenReturn(1, 2, 1);
        when(expressionGenerator.generateExpression(1)).thenReturn(new Node("1 + 1", 2), mock(Node.class));
        when(expressionGenerator.generateExpression(2)).thenThrow(new RuntimeException("Generate expression error"));

        // 调用被测方法
        List<String> result = expressionGenerator.generate(count);

        // 验证结果
        assertEquals(1, result.size());
        assertTrue(result.contains("1 + 1 = "));
    }

    /**
     * 测试异常处理:生成表达式时重复表达式
     * 预期结果:跳过重复的表达式,继续生成其他表达式
     */
    @Test
    @DisplayName("测试异常处理:生成表达式时重复表达式")
    public void testGenerate_DuplicateExpression() {
        // 模拟输入参数
        int count = 3;

        // 模拟生成表达式的方法
        when(random.nextInt(3)).thenReturn(1, 1, 2);
        when(expressionGenerator.generateExpression(1)).thenReturn(new Node("1 + 1", 2), new Node("1 + 1", 2));
        when(expressionGenerator.generateExpression(2)).thenReturn(new Node("1 + 2 + 3", 6));

        // 调用被测方法
        List<String> result = expressionGenerator.generate(count);

        // 验证结果
        assertEquals(2, result.size());
        assertTrue(result.contains("1 + 1 = "));
        assertTrue(result.contains("1 + 2 + 3 = "));
    }
}

测试数据

 -n 15 -r 10

结对小结

在本次结对编程项目中,我们通过紧密合作,成功完成了项目的各个阶段。郑邦洲在代码实现和性能优化方面表现出色,而林佳俊则在测试和文档编写方面提供了极大的帮助。我们发现结对编程能够有效提高代码质量,并且在遇到问题时能够更快地找到解决方案。

经验总结

  • 优点: 结对编程能够有效减少代码中的错误,并且在设计阶段能够提供更多的思路。
  • 教训: 在项目初期,我们低估了性能优化的难度,导致在后期花费了较多时间进行性能改进。
posted @ 2025-03-22 17:09  0-+-0  阅读(44)  评论(0)    收藏  举报