一、结对编程初体验
1.1 什么是结对编程?
结对编程(Pair Programming)是敏捷开发中的核心实践:两位程序员并肩坐在一台电脑前,一人担任“驾驶员”(负责敲代码),另一人担任“导航员”(负责思考方向、审查代码、发现潜在问题)。两人定期轮换角色。
这种看似“浪费人力”的方式,实则能带来:
代码质量提升:双人审查,bug更少
知识传递:经验共享,技能互补
减少干扰:两人互相督促,专注度更高
1.2 我们的组合
| 角色 | 成员 | 主要职责 |
|---|---|---|
| 驾驶员 | 2452621 | 代码实现、调试运行 |
| 导航员 | 2452622 | 架构设计、逻辑审查、文档记录 |
约定:每30分钟轮换一次角色,确保双方都能体验两种视角。
二、项目背景与需求
2.1 需求分析
我们要开发一个简易在线考试系统,核心功能如下:

2.2 技术选型
语言:Java(简单、易上手)
存储:内存 + 文件持久化
交互:命令行界面(CLI)
开发工具:IntelliJ IDEA + Git
三、开发过程全记录
3.1 第一阶段:搭建骨架(30分钟)
导航员的思路:
先设计核心类的结构,使用面向对象的思想。题目作为抽象基类,三种题型分别继承。
点击查看代码
// 导航员画出类图,驾驶员实现代码
abstract class Question {
protected String text;
protected int score;
protected Difficulty difficulty;
public abstract boolean checkAnswer(String userInput);
public abstract String getDisplayString();
}
class SingleChoice extends Question { /* 实现 */ }
class MultiChoice extends Question { /* 实现 */ }
class TrueFalse extends Question { /* 实现 */ }
3.2 第二阶段:题目管理模块(45分钟)
驾驶员小王实现了题目的增删改查功能。导航员小李发现了几个问题:
点击查看代码
// 导航员发现问题:选项和答案的存储格式需要统一
// 建议:使用List<String>存储选项,答案也统一为字符串
// 驾驶员修改后
public void addQuestion(Scanner scanner) {
// 统一使用逗号分隔输入
System.out.print("请输入选项(用逗号分隔): ");
List<String> options = Arrays.asList(scanner.nextLine().split(","));
}
点击查看代码
// 导航员建议使用Set,忽略顺序
Set<String> userSet = new HashSet<>(userAnswers);
Set<String> correctSet = new HashSet<>(correctAnswers);
return userSet.equals(correctSet); // 完美解决!
3.3 第三阶段:考生答题模块(60分钟)
这是最核心的部分,我们遇到了几个挑战:
挑战1:限时功能如何实现?
导航员提议:使用ScheduledExecutorService实现倒计时自动交卷。
点击查看代码
// 驾驶员实现
timerTask = scheduler.schedule(() -> {
if (currentUser != null) {
System.out.println("\n时间到!系统自动交卷...");
autoGradeAndReport();
}
}, EXAM_DURATION_MINUTES, TimeUnit.MINUTES);
点击查看代码
// 每个考生的答案保存在Map中
private static Map<String, List<String>> userAnswers = new HashMap<>();
// 每次作答立即更新
currentAnswers.set(qid, answer.trim());
System.out.println("✓ 答案已实时保存。");
3.4 第四阶段:自动判分模块(45分钟)
判分逻辑需要考虑不同题型的差异:
点击查看代码
// 导航员设计判分流程
for (int i = 0; i < questions.size(); i++) {
Question q = questions.get(i);
boolean isCorrect = q.checkAnswer(userAnswers.get(i));
if (isCorrect) totalScore += q.getScore();
// 记录错题用于报告
}
点击查看代码
╔════════════════════════════════════════╗
║ 考试成绩报告 ║
║ 考生: 张三 ║
║ 总分: 85 / 100 ║
║ 正确率: 85% ║
║ 评级: 良好 (B) ║
╠════════════════════════════════════════╣
║ 错题解析 ║
║ 3. 哪些是质数? ║
║ 你的答案: 2,4 ║
║ 正确答案: [2, 5] ║
╚════════════════════════════════════════╝
3.5 第五阶段:增强功能(60分钟)
基础功能完成后,我们决定自由发挥,增加以下特性:
| 功能 | 实现者 | 耗时 | 效果 |
|---|---|---|---|
| 难度等级 | 小李 | 15min | ⭐⭐⭐ |
| 随机抽题 | 小王 | 20min | 🎲 |
| 成绩排名 | 小李 | 15min | 🏆 |
| 倒计时提醒 | 小王 | 10min | ⏰ |
| 历史记录 | 共同 | 20min | 💾 |
四、关键代码解析
4.1 抽象类设计(多态的魅力)
点击查看代码
abstract class Question {
protected String text;
protected int score;
protected Difficulty difficulty;
// 核心方法:判分逻辑由子类实现
public abstract boolean checkAnswer(String userInput);
public abstract String getCorrectAnswerDisplay();
}
// 三种题型各自实现
// 单选题:直接字符串比较
// 多选题:集合比较(忽略顺序)
// 判断题:处理多种输入格式(正确/对/true)
点击查看代码
private static List<Question> selectRandomQuestions() {
List<Question> selected = new ArrayList<>();
List<Question> available = new ArrayList<>(questionBank);
Collections.shuffle(available); // 打乱顺序
int count = Math.min(EXAM_QUESTION_COUNT, available.size());
for (int i = 0; i < count; i++) {
selected.add(available.get(i));
}
return selected;
}
点击查看代码
// 定时任务:自动交卷
timerTask = scheduler.schedule(() -> {
if (currentUser != null) {
autoGradeAndReport();
}
}, EXAM_DURATION_MINUTES, TimeUnit.MINUTES);
// 同时设置提醒任务
scheduler.schedule(() -> {
System.out.println("\n⚠️ 还剩1分钟!");
}, EXAM_DURATION_MINUTES - 1, TimeUnit.MINUTES);
五、遇到的问题与解决方案
| 问题 | 发现者 | 解决方案 | 时间成本 |
|---|---|---|---|
| 多选题答案顺序导致判分错误 | 导航员 | 使用Set集合比较 | 5min |
| 程序崩溃答案丢失 | 导航员 | 增加文件持久化备份 | 20min |
| 倒计时线程未正确关闭 | 驾驶员 | 使用ScheduledFuture.cancel() | 10min |
| 判断题输入格式不统一 | 驾驶员 | 支持多种同义词(对/正确/true) | 10min |
经验教训:导航员的角色至关重要——驾驶员专注于实现,导航员则能发现思维盲区。
六、结对编程的心得体会
6.1 做得好的地方
✅ 角色轮换:每30分钟轮换,双方都保持了新鲜感
✅ 即时沟通:遇到问题立即讨论,而不是各自埋头
✅ 测试驱动:每写完一个功能就手动测试,bug及时发现
✅ 代码审查:每段代码都被两人审视过
6.2 可以改进的地方
🔧 前期设计不足:应该花更多时间做架构设计
🔧 缺乏单元测试:手动测试效率较低,可以引入JUnit
🔧 代码注释不够:部分复杂逻辑缺少注释说明
七、项目成果展示
7.1完整代码
点击查看代码
// 文件:Main.java
// 简易在线考试系统 - 增强版(含历史记录、难度等级、随机抽题、成绩排名等功能)
import java.util.*;
import java.util.concurrent.*;
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// 考试系统主类
class ExamSystem {
// 数据存储
private static List<Question> questionBank = new ArrayList<>();
private static Map<String, List<String>> userAnswers = new HashMap<>(); // 考生答案暂存
private static Map<String, List<ExamRecord>> examHistory = new HashMap<>(); // 考试历史记录
private static String currentUser = null;
private static List<Question> currentExamQuestions = null; // 当前考试的题目(随机抽取)
private static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private static ScheduledFuture<?> timerTask = null;
private static long examStartTime = 0;
private static final int EXAM_DURATION_MINUTES = 5; // 限时5分钟
private static final int EXAM_QUESTION_COUNT = 5; // 每次考试题目数量
public static void main(String[] args) {
initSampleData(); // 初始化一些题目
loadExamHistory(); // 加载历史记录
Scanner scanner = new Scanner(System.in);
boolean running = true;
while (running) {
if (currentUser == null) {
System.out.println("\n╔════════════════════════════════╗");
System.out.println("║ 简易在线考试系统 v2.0 ║");
System.out.println("╚════════════════════════════════╝");
System.out.println("1. 管理员登录(题目管理)");
System.out.println("2. 考生登录(参加考试)");
System.out.println("3. 查看成绩排名");
System.out.println("4. 退出系统");
System.out.print("请选择: ");
String choice = scanner.nextLine();
switch (choice) {
case "1":
adminMenu(scanner);
break;
case "2":
studentLogin(scanner);
break;
case "3":
showRanking();
break;
case "4":
running = false;
saveExamHistory(); // 保存历史记录
System.out.println("感谢使用!");
break;
default:
System.out.println("无效输入,请重新选择。");
}
} else {
// 考生考试界面
examMenu(scanner);
}
}
scanner.close();
scheduler.shutdown();
}
// 初始化样例题目(小学数学,带难度等级)
private static void initSampleData() {
// 简单题目
questionBank.add(new SingleChoice("1+1=?", Arrays.asList("1", "2", "3", "4"), "2", 2, Difficulty.EASY));
questionBank.add(new SingleChoice("5-3=?", Arrays.asList("1", "2", "3", "4"), "2", 2, Difficulty.EASY));
questionBank.add(new TrueFalse("3 + 5 = 8", true, 2, Difficulty.EASY));
// 中等题目
questionBank.add(new MultiChoice("下列哪些数字是偶数?", Arrays.asList("1", "2", "3", "4"), Arrays.asList("2","4"), 4, Difficulty.MEDIUM));
questionBank.add(new SingleChoice("8÷2=?", Arrays.asList("2", "3", "4", "5"), "4", 3, Difficulty.MEDIUM));
questionBank.add(new TrueFalse("7 × 6 = 42", true, 3, Difficulty.MEDIUM));
// 困难题目
questionBank.add(new MultiChoice("哪些是质数?", Arrays.asList("2", "4", "5", "6"), Arrays.asList("2","5"), 4, Difficulty.HARD));
questionBank.add(new SingleChoice("12 × 13 = ?", Arrays.asList("144", "156", "146", "166"), "156", 5, Difficulty.HARD));
questionBank.add(new TrueFalse("11 × 11 = 121", true, 5, Difficulty.HARD));
}
// 随机抽取题目
private static List<Question> selectRandomQuestions() {
List<Question> selected = new ArrayList<>();
List<Question> available = new ArrayList<>(questionBank);
Collections.shuffle(available);
int count = Math.min(EXAM_QUESTION_COUNT, available.size());
for (int i = 0; i < count; i++) {
selected.add(available.get(i));
}
return selected;
}
// 显示成绩排名
private static void showRanking() {
System.out.println("\n========== 考试成绩排名 ==========");
List<ExamRecord> allRecords = new ArrayList<>();
for (List<ExamRecord> records : examHistory.values()) {
allRecords.addAll(records);
}
if (allRecords.isEmpty()) {
System.out.println("暂无考试记录");
return;
}
// 按总分排序
allRecords.sort((a, b) -> Integer.compare(b.totalScore, a.totalScore));
System.out.println("排名\t考生\t总分\t正确率\t考试时间");
System.out.println("----------------------------------------");
int rank = 1;
for (ExamRecord record : allRecords) {
System.out.printf("%d\t%s\t%d\t%d%%\t%s\n",
rank++, record.studentName, record.totalScore,
record.correctRate, record.examTime);
}
System.out.println("========================================");
}
// 管理员菜单
private static void adminMenu(Scanner scanner) {
boolean admin = true;
while (admin) {
System.out.println("\n--- 题目管理 ---");
System.out.println("1. 添加题目");
System.out.println("2. 删除题目");
System.out.println("3. 修改题目");
System.out.println("4. 查看所有题目");
System.out.println("5. 按难度查看题目");
System.out.println("6. 返回主菜单");
System.out.print("请选择: ");
String choice = scanner.nextLine();
switch (choice) {
case "1":
addQuestion(scanner);
break;
case "2":
deleteQuestion(scanner);
break;
case "3":
modifyQuestion(scanner);
break;
case "4":
listQuestions();
break;
case "5":
listQuestionsByDifficulty(scanner);
break;
case "6":
admin = false;
break;
default:
System.out.println("无效选项");
}
}
}
private static void addQuestion(Scanner scanner) {
System.out.println("选择题目类型: 1.单选题 2.多选题 3.判断题");
String type = scanner.nextLine();
System.out.print("请输入题干: ");
String text = scanner.nextLine();
System.out.print("请输入分值: ");
int score = Integer.parseInt(scanner.nextLine());
System.out.print("请输入难度(1.简单 2.中等 3.困难): ");
int diffChoice = Integer.parseInt(scanner.nextLine());
Difficulty difficulty = Difficulty.values()[diffChoice - 1];
if (type.equals("1")) { // 单选
System.out.print("请输入选项(用逗号分隔, 如: 1,2,3,4): ");
String optsStr = scanner.nextLine();
List<String> options = Arrays.asList(optsStr.split(","));
System.out.print("请输入正确答案(选项内容): ");
String correct = scanner.nextLine();
questionBank.add(new SingleChoice(text, options, correct, score, difficulty));
System.out.println("单选题添加成功!");
} else if (type.equals("2")) { // 多选
System.out.print("请输入选项(用逗号分隔): ");
String optsStr = scanner.nextLine();
List<String> options = Arrays.asList(optsStr.split(","));
System.out.print("请输入正确答案(多个选项用逗号分隔): ");
String correctStr = scanner.nextLine();
List<String> correct = Arrays.asList(correctStr.split(","));
questionBank.add(new MultiChoice(text, options, correct, score, difficulty));
System.out.println("多选题添加成功!");
} else if (type.equals("3")) { // 判断
System.out.print("请输入正确答案(正确/错误): ");
String boolStr = scanner.nextLine();
boolean correct = boolStr.equals("正确");
questionBank.add(new TrueFalse(text, correct, score, difficulty));
System.out.println("判断题添加成功!");
} else {
System.out.println("无效类型");
}
}
private static void deleteQuestion(Scanner scanner) {
listQuestions();
System.out.print("请输入要删除的题目编号: ");
int idx = Integer.parseInt(scanner.nextLine()) - 1;
if (idx >= 0 && idx < questionBank.size()) {
questionBank.remove(idx);
System.out.println("删除成功");
} else {
System.out.println("编号无效");
}
}
private static void modifyQuestion(Scanner scanner) {
listQuestions();
System.out.print("请输入要修改的题目编号: ");
int idx = Integer.parseInt(scanner.nextLine()) - 1;
if (idx < 0 || idx >= questionBank.size()) {
System.out.println("无效编号");
return;
}
Question q = questionBank.get(idx);
System.out.println("当前题目: " + q.getDisplayString());
System.out.print("输入新题干(直接回车保留原样): ");
String newText = scanner.nextLine();
if (!newText.trim().isEmpty()) q.setText(newText);
System.out.print("输入新分值(直接回车保留原值): ");
String newScore = scanner.nextLine();
if (!newScore.trim().isEmpty()) q.setScore(Integer.parseInt(newScore));
// 根据类型修改选项/答案
if (q instanceof SingleChoice) {
System.out.print("新选项(逗号分隔, 回车跳过): ");
String opts = scanner.nextLine();
if (!opts.trim().isEmpty()) ((SingleChoice) q).setOptions(Arrays.asList(opts.split(",")));
System.out.print("新正确答案(选项内容, 回车跳过): ");
String ans = scanner.nextLine();
if (!ans.trim().isEmpty()) ((SingleChoice) q).setCorrectAnswer(ans);
} else if (q instanceof MultiChoice) {
System.out.print("新选项(逗号分隔, 回车跳过): ");
String opts = scanner.nextLine();
if (!opts.trim().isEmpty()) ((MultiChoice) q).setOptions(Arrays.asList(opts.split(",")));
System.out.print("新正确答案(多个选项逗号分隔, 回车跳过): ");
String ans = scanner.nextLine();
if (!ans.trim().isEmpty()) ((MultiChoice) q).setCorrectAnswers(Arrays.asList(ans.split(",")));
} else if (q instanceof TrueFalse) {
System.out.print("新正确答案(正确/错误, 回车跳过): ");
String ans = scanner.nextLine();
if (!ans.trim().isEmpty()) ((TrueFalse) q).setCorrect(ans.equals("正确"));
}
System.out.println("修改完成");
}
private static void listQuestions() {
if (questionBank.isEmpty()) {
System.out.println("暂无题目");
return;
}
System.out.println("\n当前题库共 " + questionBank.size() + " 题:");
for (int i = 0; i < questionBank.size(); i++) {
System.out.println((i+1) + ". " + questionBank.get(i).getDisplayString());
}
}
private static void listQuestionsByDifficulty(Scanner scanner) {
System.out.print("选择难度(1.简单 2.中等 3.困难): ");
int diff = Integer.parseInt(scanner.nextLine());
Difficulty difficulty = Difficulty.values()[diff - 1];
System.out.println("\n" + difficulty.getChinese() + "题目:");
int count = 0;
for (int i = 0; i < questionBank.size(); i++) {
Question q = questionBank.get(i);
if (q.getDifficulty() == difficulty) {
System.out.println((i+1) + ". " + q.getBrief());
count++;
}
}
if (count == 0) {
System.out.println("暂无此难度的题目");
}
}
// 考生登录
private static void studentLogin(Scanner scanner) {
System.out.print("请输入考生姓名: ");
String name = scanner.nextLine();
currentUser = name;
// 随机抽取题目
currentExamQuestions = selectRandomQuestions();
if (currentExamQuestions.isEmpty()) {
System.out.println("题库为空,请管理员先添加题目!");
currentUser = null;
return;
}
userAnswers.putIfAbsent(currentUser, new ArrayList<>());
List<String> ans = userAnswers.get(currentUser);
ans.clear();
for (int i = 0; i < currentExamQuestions.size(); i++) ans.add("");
System.out.println("\n欢迎 " + currentUser + ",考试即将开始!");
System.out.println("本次考试共 " + currentExamQuestions.size() + " 题,限时 " + EXAM_DURATION_MINUTES + " 分钟。");
// 统计难度分布
long easyCount = currentExamQuestions.stream().filter(q -> q.getDifficulty() == Difficulty.EASY).count();
long mediumCount = currentExamQuestions.stream().filter(q -> q.getDifficulty() == Difficulty.MEDIUM).count();
long hardCount = currentExamQuestions.stream().filter(q -> q.getDifficulty() == Difficulty.HARD).count();
System.out.printf("难度分布:简单%d题 中等%d题 困难%d题\n", easyCount, mediumCount, hardCount);
System.out.println("输入 'save' 可实时保存当前答案,输入 'submit' 提前交卷。");
examStartTime = System.currentTimeMillis();
// 启动倒计时提醒
scheduleReminders();
// 启动倒计时自动交卷
timerTask = scheduler.schedule(() -> {
if (currentUser != null) {
System.out.println("\n⏰ 时间到!系统自动交卷...");
autoGradeAndReport();
currentUser = null;
timerTask = null;
}
}, EXAM_DURATION_MINUTES, TimeUnit.MINUTES);
}
// 倒计时提醒
private static void scheduleReminders() {
scheduler.schedule(() -> {
if (currentUser != null) {
System.out.println("\n⚠️ 注意:考试还剩1分钟!请抓紧时间答题 ⚠️");
}
}, EXAM_DURATION_MINUTES - 1, TimeUnit.MINUTES);
scheduler.schedule(() -> {
if (currentUser != null) {
System.out.println("\n⚠️ 最后30秒!⚠️");
}
}, EXAM_DURATION_MINUTES - 1, TimeUnit.MINUTES);
}
// 考生答题界面
private static void examMenu(Scanner scanner) {
List<String> currentAnswers = userAnswers.get(currentUser);
boolean examActive = true;
while (examActive && currentUser != null) {
long elapsed = System.currentTimeMillis() - examStartTime;
long remaining = EXAM_DURATION_MINUTES * 60 * 1000 - elapsed;
if (remaining <= 0) {
System.out.println("考试时间已结束");
break;
}
int minutesLeft = (int)(remaining / 60000);
int secondsLeft = (int)((remaining % 60000)/1000);
System.out.printf("\n╔════════ %s 的答题卡 ════════╗\n", currentUser);
System.out.printf("║ 剩余时间:%02d:%02d ║\n", minutesLeft, secondsLeft);
System.out.println("╚════════════════════════════════╝");
for (int i = 0; i < currentExamQuestions.size(); i++) {
String status = currentAnswers.get(i).isEmpty() ? "❌未答" : "✅已答";
String difficultyIcon = getDifficultyIcon(currentExamQuestions.get(i).getDifficulty());
System.out.println((i+1) + ". " + difficultyIcon + " " + currentExamQuestions.get(i).getBrief() + " " + status);
}
System.out.println("\n请输入题号进行作答(1-" + currentExamQuestions.size() + "),或输入:");
System.out.println(" 'save' - 保存当前答案");
System.out.println(" 'list' - 查看所有题目");
System.out.println(" 'submit' - 交卷");
System.out.print("命令: ");
String input = scanner.nextLine();
if (input.equalsIgnoreCase("save")) {
System.out.println("✓ 当前答案已自动保存。");
} else if (input.equalsIgnoreCase("list")) {
showAllQuestions();
} else if (input.equalsIgnoreCase("submit")) {
System.out.println("确认交卷?(y/n): ");
String confirm = scanner.nextLine();
if (confirm.equalsIgnoreCase("y")) {
autoGradeAndReport();
examActive = false;
currentUser = null;
if (timerTask != null) timerTask.cancel(false);
break;
}
} else {
try {
int qid = Integer.parseInt(input) - 1;
if (qid >= 0 && qid < currentExamQuestions.size()) {
Question q = currentExamQuestions.get(qid);
displayQuestion(q);
System.out.print("请输入你的答案: ");
String answer = scanner.nextLine();
if (answer.trim().isEmpty()) {
System.out.println("答案不能为空,此题未保存。");
} else {
currentAnswers.set(qid, answer.trim());
System.out.println("✓ 答案已实时保存。");
}
} else {
System.out.println("无效题号,请输入1-" + currentExamQuestions.size());
}
} catch (NumberFormatException e) {
System.out.println("无效命令");
}
}
}
if (currentUser == null) System.out.println("返回主菜单。");
}
private static String getDifficultyIcon(Difficulty d) {
switch(d) {
case EASY: return "⭐";
case MEDIUM: return "⭐⭐";
case HARD: return "⭐⭐⭐";
default: return "";
}
}
private static void showAllQuestions() {
System.out.println("\n========== 全部题目 ==========");
for (int i = 0; i < currentExamQuestions.size(); i++) {
Question q = currentExamQuestions.get(i);
System.out.println((i+1) + ". " + getDifficultyIcon(q.getDifficulty()) + " " + q.getBrief());
}
System.out.println("==============================");
}
private static void displayQuestion(Question q) {
System.out.println("\n┌─────────────────────────────────┐");
if (q instanceof SingleChoice) {
SingleChoice sq = (SingleChoice) q;
System.out.println("│ 【单选题】" + sq.getBrief());
System.out.println("├─────────────────────────────────┤");
List<String> options = sq.getOptionsForDisplay();
for (int j = 0; j < options.size(); j++) {
System.out.println("│ " + (char)('A' + j) + ". " + options.get(j));
}
} else if (q instanceof MultiChoice) {
MultiChoice mq = (MultiChoice) q;
System.out.println("│ 【多选题】" + mq.getBrief());
System.out.println("├─────────────────────────────────┤");
List<String> options = mq.getOptionsForDisplay();
for (int j = 0; j < options.size(); j++) {
System.out.println("│ " + (char)('A' + j) + ". " + options.get(j));
}
System.out.println("├─────────────────────────────────┤");
System.out.println("│ 提示:多选答案请用逗号分隔 │");
System.out.println("│ 例如:A,B 或 A,B,C │");
} else if (q instanceof TrueFalse) {
System.out.println("│ 【判断题】" + q.getBrief());
System.out.println("├─────────────────────────────────┤");
System.out.println("│ 请输入:正确 或 错误 │");
}
System.out.println("└─────────────────────────────────┘");
}
// 自动判分并生成报告
private static void autoGradeAndReport() {
if (currentUser == null) return;
List<String> answers = userAnswers.get(currentUser);
int totalScore = 0;
int maxPossible = 0;
List<ResultDetail> details = new ArrayList<>();
for (int i = 0; i < currentExamQuestions.size(); i++) {
Question q = currentExamQuestions.get(i);
maxPossible += q.getScore();
String userAns = (i < answers.size()) ? answers.get(i) : "";
boolean isCorrect = q.checkAnswer(userAns);
if (isCorrect) totalScore += q.getScore();
details.add(new ResultDetail(i+1, q.getBrief(), userAns,
q.getCorrectAnswerDisplay(), isCorrect, q.getScore(), q.getDifficulty()));
}
int correctRate = maxPossible == 0 ? 0 : totalScore * 100 / maxPossible;
System.out.println("\n╔════════════════════════════════════════╗");
System.out.println("║ 考试成绩报告 ║");
System.out.println("╠════════════════════════════════════════╣");
System.out.println("║ 考生: " + currentUser);
System.out.println("║ 总分: " + totalScore + " / " + maxPossible);
System.out.println("║ 正确率: " + correctRate + "%");
// 评级
String grade;
if (correctRate >= 90) grade = "优秀 (A)";
else if (correctRate >= 75) grade = "良好 (B)";
else if (correctRate >= 60) grade = "及格 (C)";
else grade = "不及格 (D)";
System.out.println("║ 评级: " + grade);
System.out.println("╠════════════════════════════════════════╣");
System.out.println("║ 错题解析 ║");
boolean hasWrong = false;
for (ResultDetail rd : details) {
if (!rd.correct) {
hasWrong = true;
System.out.println("║ " + rd.id + ". " + rd.questionText);
System.out.println("║ 你的答案: " + (rd.userAnswer.isEmpty()?"未作答":rd.userAnswer));
System.out.println("║ 正确答案: " + rd.correctAnswer);
System.out.println("║ 分值: " + rd.score + "分 难度: " + rd.difficulty.getChinese());
System.out.println("║ ─────────────────────────────");
}
}
if (!hasWrong) {
System.out.println("║ 🎉 恭喜!全做对了!太棒了! 🎉");
}
System.out.println("╚════════════════════════════════════════╝");
// 保存考试记录
ExamRecord record = new ExamRecord(currentUser, totalScore, maxPossible,
correctRate, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
examHistory.computeIfAbsent(currentUser, k -> new ArrayList<>()).add(record);
saveExamHistory();
// 清除考试状态
currentUser = null;
currentExamQuestions = null;
if (timerTask != null) timerTask.cancel(false);
}
// 保存历史记录到文件
private static void saveExamHistory() {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("exam_history.dat"))) {
oos.writeObject(examHistory);
} catch (IOException e) {
// 忽略保存失败
}
}
// 加载历史记录
@SuppressWarnings("unchecked")
private static void loadExamHistory() {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("exam_history.dat"))) {
examHistory = (Map<String, List<ExamRecord>>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
examHistory = new HashMap<>();
}
}
}
// 难度枚举
enum Difficulty {
EASY("简单"), MEDIUM("中等"), HARD("困难");
private String chinese;
Difficulty(String chinese) { this.chinese = chinese; }
public String getChinese() { return chinese; }
}
// 考试记录类
class ExamRecord implements Serializable {
String studentName;
int totalScore;
int maxScore;
int correctRate;
String examTime;
ExamRecord(String name, int total, int max, int rate, String time) {
this.studentName = name;
this.totalScore = total;
this.maxScore = max;
this.correctRate = rate;
this.examTime = time;
}
}
// 题目抽象类
abstract class Question implements Serializable {
protected String text;
protected int score;
protected Difficulty difficulty;
public Question(String text, int score, Difficulty difficulty) {
this.text = text;
this.score = score;
this.difficulty = difficulty;
}
public abstract boolean checkAnswer(String userInput);
public abstract String getDisplayString();
public abstract String getBrief();
public abstract String getCorrectAnswerDisplay();
public void setText(String text) { this.text = text; }
public void setScore(int score) { this.score = score; }
public int getScore() { return score; }
public Difficulty getDifficulty() { return difficulty; }
}
// 单选题
class SingleChoice extends Question {
private List<String> options;
private String correctAnswer;
public SingleChoice(String text, List<String> options, String correctAnswer, int score, Difficulty difficulty) {
super(text, score, difficulty);
this.options = options;
this.correctAnswer = correctAnswer;
}
@Override
public boolean checkAnswer(String userInput) {
String input = userInput.trim();
if (input.length() == 1 && input.matches("[A-Za-z]")) {
int idx = input.toUpperCase().charAt(0) - 'A';
if (idx >= 0 && idx < options.size()) {
return options.get(idx).equals(correctAnswer);
}
}
return input.equalsIgnoreCase(correctAnswer);
}
@Override
public String getDisplayString() {
return "[" + difficulty.getChinese() + "][单选] " + text + " " + options + " 正确答案:" + correctAnswer;
}
@Override
public String getBrief() { return text; }
@Override
public String getCorrectAnswerDisplay() { return correctAnswer; }
public void setOptions(List<String> opts) { this.options = opts; }
public void setCorrectAnswer(String ans) { this.correctAnswer = ans; }
public List<String> getOptionsForDisplay() { return options; }
}
// 多选题
class MultiChoice extends Question {
private List<String> options;
private List<String> correctAnswers;
public MultiChoice(String text, List<String> options, List<String> correctAnswers, int score, Difficulty difficulty) {
super(text, score, difficulty);
this.options = options;
this.correctAnswers = correctAnswers;
}
@Override
public boolean checkAnswer(String userInput) {
if (userInput.trim().isEmpty()) return false;
String input = userInput.trim().toUpperCase();
Set<String> userSet = new HashSet<>();
if (input.matches("[A-Za-z](,[A-Za-z])*")) {
String[] letters = input.split(",");
for (String letter : letters) {
int idx = letter.trim().charAt(0) - 'A';
if (idx >= 0 && idx < options.size()) {
userSet.add(options.get(idx));
}
}
} else {
userSet.addAll(Arrays.asList(userInput.split(",")));
}
Set<String> correctSet = new HashSet<>(correctAnswers);
return userSet.equals(correctSet);
}
@Override
public String getDisplayString() {
return "[" + difficulty.getChinese() + "][多选] " + text + " " + options + " 正确答案:" + correctAnswers;
}
@Override
public String getBrief() { return text; }
@Override
public String getCorrectAnswerDisplay() { return correctAnswers.toString(); }
public void setOptions(List<String> opts) { this.options = opts; }
public void setCorrectAnswers(List<String> ans) { this.correctAnswers = ans; }
public List<String> getOptionsForDisplay() { return options; }
}
// 判断题
class TrueFalse extends Question {
private boolean correct;
public TrueFalse(String text, boolean correct, int score, Difficulty difficulty) {
super(text, score, difficulty);
this.correct = correct;
}
@Override
public boolean checkAnswer(String userInput) {
if (userInput.equalsIgnoreCase("正确") || userInput.equalsIgnoreCase("true") || userInput.equals("对"))
return correct;
else if (userInput.equalsIgnoreCase("错误") || userInput.equalsIgnoreCase("false") || userInput.equals("错"))
return !correct;
return false;
}
@Override
public String getDisplayString() {
return "[" + difficulty.getChinese() + "][判断] " + text + " 正确答案:" + (correct?"正确":"错误");
}
@Override
public String getBrief() { return text; }
@Override
public String getCorrectAnswerDisplay() { return correct?"正确":"错误"; }
public void setCorrect(boolean c) { this.correct = c; }
}
// 成绩明细辅助类
class ResultDetail {
int id;
String questionText;
String userAnswer;
String correctAnswer;
boolean correct;
int score;
Difficulty difficulty;
ResultDetail(int id, String text, String userAns, String corrAns, boolean corr, int score, Difficulty difficulty) {
this.id = id;
this.questionText = text;
this.userAnswer = userAns;
this.correctAnswer = corrAns;
this.correct = corr;
this.score = score;
this.difficulty = difficulty;
}
}
// 公共主类
public class Main {
public static void main(String[] args) {
ExamSystem.main(args);
}
}
7.2运行演示
点击查看代码
╔════════════════════════════════╗
║ 简易在线考试系统 v2.0 ║
╚════════════════════════════════╝
1. 管理员登录(题目管理)
2. 考生登录(参加考试)
3. 查看成绩排名
4. 退出系统
请选择: 1
--- 题目管理 ---
1. 添加题目
2. 删除题目
3. 修改题目
4. 查看所有题目
5. 按难度查看题目
6. 返回主菜单
请选择: 1
选择题目类型: 1.单选题 2.多选题 3.判断题
1
请输入题干: 9+9=?
请输入分值: 2
请输入难度(1.简单 2.中等 3.困难): 1
请输入选项(用逗号分隔, 如: 1,2,3,4): 9,12,18,21
请输入正确答案(选项内容): 18
单选题添加成功!
--- 题目管理 ---
1. 添加题目
2. 删除题目
3. 修改题目
4. 查看所有题目
5. 按难度查看题目
6. 返回主菜单
请选择: 2
当前题库共 10 题:
1. [简单][单选] 1+1=? [1, 2, 3, 4] 正确答案:2
2. [简单][单选] 5-3=? [1, 2, 3, 4] 正确答案:2
3. [简单][判断] 3 + 5 = 8 正确答案:正确
4. [中等][多选] 下列哪些数字是偶数? [1, 2, 3, 4] 正确答案:[2, 4]
5. [中等][单选] 8÷2=? [2, 3, 4, 5] 正确答案:4
6. [中等][判断] 7 × 6 = 42 正确答案:正确
7. [困难][多选] 哪些是质数? [2, 4, 5, 6] 正确答案:[2, 5]
8. [困难][单选] 12 × 13 = ? [144, 156, 146, 166] 正确答案:156
9. [困难][判断] 11 × 11 = 121 正确答案:正确
10. [简单][单选] 9+9=? [9, 12, 18, 21] 正确答案:18
请输入要删除的题目编号: 1
删除成功
--- 题目管理 ---
1. 添加题目
2. 删除题目
3. 修改题目
4. 查看所有题目
5. 按难度查看题目
6. 返回主菜单
请选择: 3
当前题库共 9 题:
1. [简单][单选] 5-3=? [1, 2, 3, 4] 正确答案:2
2. [简单][判断] 3 + 5 = 8 正确答案:正确
3. [中等][多选] 下列哪些数字是偶数? [1, 2, 3, 4] 正确答案:[2, 4]
4. [中等][单选] 8÷2=? [2, 3, 4, 5] 正确答案:4
5. [中等][判断] 7 × 6 = 42 正确答案:正确
6. [困难][多选] 哪些是质数? [2, 4, 5, 6] 正确答案:[2, 5]
7. [困难][单选] 12 × 13 = ? [144, 156, 146, 166] 正确答案:156
8. [困难][判断] 11 × 11 = 121 正确答案:正确
9. [简单][单选] 9+9=? [9, 12, 18, 21] 正确答案:18
请输入要修改的题目编号: 9
当前题目: [简单][单选] 9+9=? [9, 12, 18, 21] 正确答案:18
输入新题干(直接回车保留原样):
输入新分值(直接回车保留原值):
新选项(逗号分隔, 回车跳过):
新正确答案(选项内容, 回车跳过):
修改完成
--- 题目管理 ---
1. 添加题目
2. 删除题目
3. 修改题目
4. 查看所有题目
5. 按难度查看题目
6. 返回主菜单
请选择: 4
当前题库共 9 题:
1. [简单][单选] 5-3=? [1, 2, 3, 4] 正确答案:2
2. [简单][判断] 3 + 5 = 8 正确答案:正确
3. [中等][多选] 下列哪些数字是偶数? [1, 2, 3, 4] 正确答案:[2, 4]
4. [中等][单选] 8÷2=? [2, 3, 4, 5] 正确答案:4
5. [中等][判断] 7 × 6 = 42 正确答案:正确
6. [困难][多选] 哪些是质数? [2, 4, 5, 6] 正确答案:[2, 5]
7. [困难][单选] 12 × 13 = ? [144, 156, 146, 166] 正确答案:156
8. [困难][判断] 11 × 11 = 121 正确答案:正确
9. [简单][单选] 9+9=? [9, 12, 18, 21] 正确答案:18
--- 题目管理 ---
1. 添加题目
2. 删除题目
3. 修改题目
4. 查看所有题目
5. 按难度查看题目
6. 返回主菜单
请选择: 5
选择难度(1.简单 2.中等 3.困难): 3
困难题目:
6. 哪些是质数?
7. 12 × 13 = ?
8. 11 × 11 = 121
--- 题目管理 ---
1. 添加题目
2. 删除题目
3. 修改题目
4. 查看所有题目
5. 按难度查看题目
6. 返回主菜单
请选择: 6
╔════════════════════════════════╗
║ 简易在线考试系统 v2.0 ║
╚════════════════════════════════╝
1. 管理员登录(题目管理)
2. 考生登录(参加考试)
3. 查看成绩排名
4. 退出系统
请选择: 2
请输入考生姓名: yinuo
欢迎 yinuo,考试即将开始!
本次考试共 5 题,限时 5 分钟。
难度分布:简单2题 中等1题 困难2题
输入 'save' 可实时保存当前答案,输入 'submit' 提前交卷。
╔════════ yinuo 的答题卡 ════════╗
║ 剩余时间:04:59 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 11 × 11 = 121 ❌未答
2. ⭐ 5-3=? ❌未答
3. ⭐ 3 + 5 = 8 ❌未答
4. ⭐⭐ 8÷2=? ❌未答
5. ⭐⭐⭐ 哪些是质数? ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: 1
┌─────────────────────────────────┐
│ 【判断题】11 × 11 = 121
├─────────────────────────────────┤
│ 请输入:正确 或 错误 │
└─────────────────────────────────┘
请输入你的答案: 正确
✓ 答案已实时保存。
╔════════ yinuo 的答题卡 ════════╗
║ 剩余时间:04:43 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 11 × 11 = 121 ✅已答
2. ⭐ 5-3=? ❌未答
3. ⭐ 3 + 5 = 8 ❌未答
4. ⭐⭐ 8÷2=? ❌未答
5. ⭐⭐⭐ 哪些是质数? ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: 2
┌─────────────────────────────────┐
│ 【单选题】5-3=?
├─────────────────────────────────┤
│ A. 1
│ B. 2
│ C. 3
│ D. 4
└─────────────────────────────────┘
请输入你的答案: B
✓ 答案已实时保存。
╔════════ yinuo 的答题卡 ════════╗
║ 剩余时间:04:37 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 11 × 11 = 121 ✅已答
2. ⭐ 5-3=? ✅已答
3. ⭐ 3 + 5 = 8 ❌未答
4. ⭐⭐ 8÷2=? ❌未答
5. ⭐⭐⭐ 哪些是质数? ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: submit
确认交卷?(y/n):
y
╔════════════════════════════════════════╗
║ 考试成绩报告 ║
╠════════════════════════════════════════╣
║ 考生: yinuo
║ 总分: 7 / 16
║ 正确率: 43%
║ 评级: 不及格 (D)
╠════════════════════════════════════════╣
║ 错题解析 ║
║ 3. 3 + 5 = 8
║ 你的答案: 未作答
║ 正确答案: 正确
║ 分值: 2分 难度: 简单
║ ─────────────────────────────
║ 4. 8÷2=?
║ 你的答案: 未作答
║ 正确答案: 4
║ 分值: 3分 难度: 中等
║ ─────────────────────────────
║ 5. 哪些是质数?
║ 你的答案: 未作答
║ 正确答案: [2, 5]
║ 分值: 4分 难度: 困难
║ ─────────────────────────────
╚════════════════════════════════════════╝
返回主菜单。
╔════════════════════════════════╗
║ 简易在线考试系统 v2.0 ║
╚════════════════════════════════╝
1. 管理员登录(题目管理)
2. 考生登录(参加考试)
3. 查看成绩排名
4. 退出系统
请选择: 2
请输入考生姓名: dashuai
欢迎 dashuai,考试即将开始!
本次考试共 5 题,限时 5 分钟。
难度分布:简单2题 中等2题 困难1题
输入 'save' 可实时保存当前答案,输入 'submit' 提前交卷。
╔════════ dashuai 的答题卡 ════════╗
║ 剩余时间:05:00 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 哪些是质数? ❌未答
2. ⭐⭐ 8÷2=? ❌未答
3. ⭐ 5-3=? ❌未答
4. ⭐⭐ 7 × 6 = 42 ❌未答
5. ⭐ 3 + 5 = 8 ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: 1
┌─────────────────────────────────┐
│ 【多选题】哪些是质数?
├─────────────────────────────────┤
│ A. 2
│ B. 4
│ C. 5
│ D. 6
├─────────────────────────────────┤
│ 提示:多选答案请用逗号分隔 │
│ 例如:A,B 或 A,B,C │
└─────────────────────────────────┘
请输入你的答案: A,C
✓ 答案已实时保存。
╔════════ dashuai 的答题卡 ════════╗
║ 剩余时间:04:53 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 哪些是质数? ✅已答
2. ⭐⭐ 8÷2=? ❌未答
3. ⭐ 5-3=? ❌未答
4. ⭐⭐ 7 × 6 = 42 ❌未答
5. ⭐ 3 + 5 = 8 ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: 2
┌─────────────────────────────────┐
│ 【单选题】8÷2=?
├─────────────────────────────────┤
│ A. 2
│ B. 3
│ C. 4
│ D. 5
└─────────────────────────────────┘
请输入你的答案: C
✓ 答案已实时保存。
╔════════ dashuai 的答题卡 ════════╗
║ 剩余时间:04:48 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 哪些是质数? ✅已答
2. ⭐⭐ 8÷2=? ✅已答
3. ⭐ 5-3=? ❌未答
4. ⭐⭐ 7 × 6 = 42 ❌未答
5. ⭐ 3 + 5 = 8 ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: 3
┌─────────────────────────────────┐
│ 【单选题】5-3=?
├─────────────────────────────────┤
│ A. 1
│ B. 2
│ C. 3
│ D. 4
└─────────────────────────────────┘
请输入你的答案: B
✓ 答案已实时保存。
╔════════ dashuai 的答题卡 ════════╗
║ 剩余时间:04:40 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 哪些是质数? ✅已答
2. ⭐⭐ 8÷2=? ✅已答
3. ⭐ 5-3=? ✅已答
4. ⭐⭐ 7 × 6 = 42 ❌未答
5. ⭐ 3 + 5 = 8 ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: 4
┌─────────────────────────────────┐
│ 【判断题】7 × 6 = 42
├─────────────────────────────────┤
│ 请输入:正确 或 错误 │
└─────────────────────────────────┘
请输入你的答案: 正确
✓ 答案已实时保存。
╔════════ dashuai 的答题卡 ════════╗
║ 剩余时间:04:27 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 哪些是质数? ✅已答
2. ⭐⭐ 8÷2=? ✅已答
3. ⭐ 5-3=? ✅已答
4. ⭐⭐ 7 × 6 = 42 ✅已答
5. ⭐ 3 + 5 = 8 ❌未答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: 5
┌─────────────────────────────────┐
│ 【判断题】3 + 5 = 8
├─────────────────────────────────┤
│ 请输入:正确 或 错误 │
└─────────────────────────────────┘
请输入你的答案: 正确
✓ 答案已实时保存。
╔════════ dashuai 的答题卡 ════════╗
║ 剩余时间:04:17 ║
╚════════════════════════════════╝
1. ⭐⭐⭐ 哪些是质数? ✅已答
2. ⭐⭐ 8÷2=? ✅已答
3. ⭐ 5-3=? ✅已答
4. ⭐⭐ 7 × 6 = 42 ✅已答
5. ⭐ 3 + 5 = 8 ✅已答
请输入题号进行作答(1-5),或输入:
'save' - 保存当前答案
'list' - 查看所有题目
'submit' - 交卷
命令: submit
确认交卷?(y/n):
y
╔════════════════════════════════════════╗
║ 考试成绩报告 ║
╠════════════════════════════════════════╣
║ 考生: dashuai
║ 总分: 14 / 14
║ 正确率: 100%
║ 评级: 优秀 (A)
╠════════════════════════════════════════╣
║ 错题解析 ║
║ 🎉 恭喜!全做对了!太棒了! 🎉
╚════════════════════════════════════════╝
返回主菜单。
╔════════════════════════════════╗
║ 简易在线考试系统 v2.0 ║
╚════════════════════════════════╝
1. 管理员登录(题目管理)
2. 考生登录(参加考试)
3. 查看成绩排名
4. 退出系统
请选择: 3
========== 考试成绩排名 ==========
排名 考生 总分 正确率 考试时间
----------------------------------------
1 dashuai 14 100% 2026-04-17 14:54:34
2 yinuo 7 43% 2026-04-17 14:53:33
========================================
╔════════════════════════════════╗
║ 简易在线考试系统 v2.0 ║
╚════════════════════════════════╝
1. 管理员登录(题目管理)
2. 考生登录(参加考试)
3. 查看成绩排名
4. 退出系统
请选择: 4
感谢使用!
八、总结
通过这次结对编程实践,我们不仅完成了一个功能完整的在线考试系统,更重要的是体验了敏捷协作的魅力。
代码统计:
- 总行数:约600行
- 开发时间:4小时
- Bug数量:12个(全部修复)
浙公网安备 33010602011771号