22207321-王郅坚-BLOG
前言
这三次题目集涉及了不同的知识点、编程技巧及而算法逻辑,从简单的基础题目逐步过渡到复杂的业务逻辑模拟。总体而言,题目集1比较基础,其内容往往涉及数据结构的使用及算法实现的逻辑结构,比如链表、数组、排序、查找等等。题目集2开始引入更复杂的业务场景,与题目之间的关联性大大增强,同时也引入了面向对象的设计思想。而题目集3的难度则进一步提升,涉及的逻辑任务复杂度较高,要求对设计模式、模块组织及代码复用有更深的体会。三次题目集不仅是单单考核独立的编程任务,其实它们有明确的迭代关系,逐步递进并且不断添加复杂度。题目集1注重基础的数据结构和算法操作,题目集2开始引入面向对象(OOP)的设计思想,而题目集3则注重更为复杂的业务处理,以及系统的各类状态管理。通过这三次作业,编程能力从基础的算法实现提升到具备一定的系统化设计能力,同时也逐步强化代码的复用性、健壮性和扩展性。在题目集1到题目集3的迭代过程中,我体会到了代码结构设计的重要性。这不仅仅是从各个模块功能的角度来实现,还要考虑代码的可读性、可维护性,甚至整个系统的扩展性。在每一次的迭代中,代码的模块化设计增强了系统的灵活性,而面向对象的设计思想让不同的模块解耦,使复杂的逻辑变得更容易应对。
三次题目集的迭代思路简述
题目集1到题目集3之间的关系是明显的迭代和层层递进:
题目集1:基础数据结构与算法的练习。主要考察基本操作,比如数组、链表等数据结构,以及排序、查找等常用算法。它为后续的项目编码夯实了基础知识。
题目集2:OOP思想的引入,从基础的面向过程逐渐转向面向对象。题目通过类与类之间的交互,强调代码的模块化与复用解决复杂问题。
题目集3:多状态、多层次业务逻辑模拟。最后的题目集要求我们不仅仅搭建单一功能模块,而是通过组合多个类,解决现实业务场景中的错综复杂的题目管理、状态运作问题,同时加入对错题的处理、答案校验、状态反馈及异常处理等。
接下来,我将依次分析三次题目集的设计思路及其迭代关系,并且延伸探讨通过这三次作业的具体编程收获,重点剖析题目集3的最后一题 的设计与实现。
迭代一:题目集1 - 基本的答题系统设计
功能设计
第一版的代码实现了一个基础的答题系统 。主要包含:
题目的输入与题库的建立 :每个题目由编号、题目内容、标准答案构成。通过解析输入,建立一个 TestPaper 对象来管理题库。
用户答案的输入与检查 :通过 AnswerSheet 对象存储用户的答案,并且根据 Question 的标准答案与用户输入做比较,给出回答是否正确的判定。
结果的输出 :输出每道题的内容、用户的答案以及是否回答正确。
代码结构分析
Question 类 :此类代表每一道题目,包含属性如题目编号、题目内容、标准答案以及用户的答案。此外提供 compareAnswer() 方法用于判断用户答案是否正确。
TestPaper 类 :用于管理多个题目。它存储了多个 Question 对象,并提供了添加题目 addQuestion()、答案检查 checkAnswer() 以及对题目进行排序的功能。
AnswerSheet 类 :负责记录用户的答案,同时通过关联的 TestPaper 来对照题目和答案输出结果。
Main 类 :这是程序的核心入口,负责解析用户的输入,包括题目的解析和答案的解析,以及调用上述类的方法来进行整体的答题检查与结果输出。
时间复杂度分析
dealInput() :处理输入题目及其属性。每行输入的解析大约需要花费 O(L),而总共有 numQuestions 行,因此总处理复杂度为 O(N * L),其中 N 是题目数量,L 是每行输入长度。
readAnswers() :处理用户的答案。通过无限循环 (while) 等待用户输入的答案,每个答案的处理时间大致是 O(L)(取决于答案的长度),所以总复杂度为 O(M * L),其中 M 是用户输入的次数。
TestPaper.sortQuestions() :题目排序使用了 Timsort,时间复杂度为 O(N log N),其中 N 是题目数量。
空间复杂度分析
dealInput() 和 readAnswers() 的空间复杂度与输入的数量和长度有关,较为线性,都是 O(N) 或 O(M)。
TestPaper 和 AnswerSheet 中的数据结构(如 LinkedList)用来存储题目和答案,空间复杂度依赖于题目数量,略高为 O(N)。
圈复杂度分析
圈复杂度表示代码逻辑的复杂性。根据控制结构的数量,我们可以估算出圈复杂度:
dealInput() : 包含一个简单的 for 循环,没有额外的条件分支或复杂控制流,因此圈复杂度为 1。
readAnswers() :有 while 和两个 if 条件判断,圈复杂度为 3(三种控制路径)。
TestPaper.sortQuestions() :无条件控制,圈复杂度为 1。
总体来看,这一版的代码有较低的圈复杂度,表明代码的逻辑相对比较简单,容易测试和维护。
代码分析
数据与类结构 :
Question 类 :用于表示每道题目的核心数据结构,包含题目编号(numOfQuestion)、题目内容(contentQuestion)、标准答案(standardAnswer)和用户输入的答案(answer)。该类还提供了两个关键方法:
compareAnswer() 方法用于检查用户答案是否与标准答案一致。
setAnswer() 方法用于存储用户输入的答案。``
TestPaper 类 :负责对多道题目进行管理。主要功能:
添加题目到试卷(addQuestion())。
检查用户作答是否正确(checkAnswer())。
将题目按编号排序(sortQuestions()),并在标准格式下输出题目及答案。
例子:
public boolean compareAnswer() {
return this.answer != null && this.answer.equals(this.standardAnswer);
}
Main 类中的输入处理 :
在主程序中,我们通过两个函数来处理输入:
dealInput() :解析题目输入的所有信息,它根据题目格式的 "#N:num #Q:content #A:standardAnswer" 进行拆解,并将解析得来的题目添加到 TestPaper 对象中。
public static void dealInput(int numQuestions, TestPaper testPaper, Scanner scanner) {
for (int i = 0; i < numQuestions; i++) {
String line = scanner.nextLine();
String[] parts = line.split("#");
int num = Integer.parseInt(parts[1].split(":")[1].trim());
String content = parts[2].split(":")[1].trim();
String standardAnswer = parts[3].split(":")[1].trim();
testPaper.addQuestion(new Question(num, content, standardAnswer)); }
这个方法中,通过 split("#") 将输入的题目按 #N, #Q, #A 的格式拆分。而后进一步将其分解成题目编号、题目内容和标准答案,最后使用 addQuestion() 方法将其添加到 TestPaper 中。
readAnswers() :处理用户输入的答案部分,它通过 #A: 标识符截取用户的答案,并依次将解析后的答案传递给 AnswerSheet,与每道题目进行匹配。
public static void readAnswers(int numQuestions, Scanner input, AnswerSheet answerSheet) {
while (true) {
String line = input.nextLine().trim();
if (line.equals("end")) {
break;
}
if (line.startsWith("#A:")) {
String[] answers = line.split(" ");
for (int i = 0; i < answers.length; i++) {
String answer = answers[i].substring(3).trim();
answerSheet.addAnswer(i + 1, answer);
}
此方法通过循环处理所有输入内容,直到遇到 end 标识结束。它先通过 #A: 拆分答案输入数据,然后提取答案部分,并将其加入 AnswerSheet 对象。
AnswerSheet 类功能 :
AnswerSheet 类通过 addAnswer() 方法接收解析后的答案,并将它们与对应的题目进行匹配,最后可以通过 checkAndRecordResults() 方法来对整个答卷进行判分,并生成具体的结果输出。
以下是题目一的类图,展示了我的代码是如何设计类以及方法的使用:
再是我的顺序图在这个顺序图中,我们展示了以下步骤:

Main类创建了一个Scanner对象来读取输入。
Main类创建了一个新的TestPaper实例和一个AnswerSheet实例。
Main类调用dealInput方法来处理题目输入,将题目添加到TestPaper实例中。
Main类调用readAnswers方法来处理答案输入,将答案添加到AnswerSheet实例中。
AnswerSheet实例调用checkAndRecordResults方法来检查答案并记录结果。
AnswerSheet实例调用display方法来展示试卷和答题结果。
Main类关闭Scanner对象。
踩坑心得
- 用户输入错误格式的处理:
在第一版代码中,dealInput() 和 readAnswers() 没有做太多输入格式的校验,导致当输入格式不符合预期时,程序会运行时崩溃。用户输入的数据在结构上具有严格的格式要求(如 #N:, #A:),如果误输入或丢失某个字段,或者某些特殊情况(如空格位置不对),ArrayIndexOutOfBoundsException 就会被抛出。这是由于我们在直接操作数组的分割部分时,没有做充分防御性编程,缺少边界值的检查。
改进措施 :
为了解决这种问题,我加入了输入格式检查并使用了有效的错误提示。具体来说,可以在 dealInput() 中引入一个输入格式合法性的判断,比如提前检查有效字段是否存在:
public static boolean isValidInput(String[] parts) {
return parts.length == 4 &&
parts[1].startsWith("N:") &&
parts[2].startsWith("Q:") &&
parts[3].startsWith("A:");}
如果 isValidInput() 返回 false,则输出错误提示并提前退出,避免程序崩溃。
- 用户答案输入的匹配不准确:
原代码中,在 readAnswers() 方法里,默认用户的答案和题目一一对应,假设用户不会缺少题目编号以及相关标示符,如 #A:。这种假设在实际场景中不一定合理,且容易导致误匹配问题。遇到这种情况,代码会陷入匹配错误的情况,导致后续操作中的结果严重不对(如答案错位、题库丢失等)。
改进措施 :
同样,我通过对答案输入做了更严格的检查,假设每个答案都缺少 #A: 非常容易出错。因此可以:
在每一行进行严格的输入前缀和格式解析,并在检查是否缺失时给出警示。
例如,当答案格式不符时,可以直接跳过并输出相应提示。
if (!line.startsWith("#A:")) {
System.out.println("Invalid answer format: " + line);
continue;} // 跳过错误答案
收获与不足
优势 :
实现了题目管理和答案对比的基本功能,代码清晰且模块化。
系统能够处理与输入对应的题目和答案,实现了基本的答题系统框架。
不足 :
缺乏输入校验 :当输入格式不符合预期时(如缺少 #A: 标识符),程序会崩溃,并未提供有效的错误处理机制。
线性流程 :此版本的代码流程较为线性,一旦流程复杂化,可能引起维护难度的增加。
迭代二:题目集2 – 多试卷与分数管理
功能设计
第二次代码迭代引入了更复杂的需求,需支持多份试卷以及题目分数的管理。此外,系统还要进行答案匹配,计算最终得分,并能够展示题目、答案以及得分情况。
多试卷支持 :系统增加了 TestPaper 的管理,通过 TestPaper 对象,我们可以管理不同试卷中各道题的分数。
分数计算 :系统根据每题的分数将总得分计算出来。
软性报警 :如果试卷总分不满或超过 100 分,系统会自动报警提示。
代码结构分析
TestPaper 类扩展 :引入了 scores 列表来保存每道题目的分数,增加了 addQuestion(Question, int) 方法,以便将题目和分数绑定。还添加了 getTotalScore() 方法来计算试卷的总分,并检查是否符合标准的 100 分满分。
答案检查 :使用 AnswerSheet.displayResults() 将答案匹配的结果展示出来,并计算整体得分。
时间复杂度分析
processInput()(处理输入部分) :与 dealInput() 类似,每条输入解析和分数绑定时间为 O(N * L),即题目数量乘以每行字符串处理时间。
分数计算 :对于给定题目数量 N,对 TestPaper 中的 scores 进行一次遍历(还包括用户答案的比对过程),它的时间复杂度与题目数量呈线性关系,复杂度大致为 O(N)。
排序题目 :相同于迭代一,排序依然是 Timsort 算法,复杂度为 O(N log N)。
空间复杂度分析
除了 TestPaper 需要储存题目列表和分数列表,其他部分空间开销未显著变化。总体空间复杂度仍为 O(N)。
圈复杂度分析
processInput() :类似 dealInput(),解析操作大致相同,圈复杂度为1。
AnswerSheet.displayResults() :此方法中额外加入了 for 循环和判分逻辑,对每道题核对答案并叠加得分。这里包含多个条件和判断,圈复杂度大幅上升,约为3-4(取决于条件数和循环层次)。
加总与警告检查 :在对试卷进行分数加总时,为了确保总分等于100,系统增加了判断和报警逻辑,圈复杂度可能略微提高,但总体仍维持在合理的区间 1-2。
功能分析
多试卷支持及题目与分数绑定 :
在第二次迭代中引入了 TestPaper 类的扩展,它不仅管理每一张试卷的题目,同时每道题目绑定一个分数。新增了 scores 列表用于保存各道题目的分值情况。
public void addQuestion(Question question, int score) {
questions.add(question);
scores.add(score);
totalScore += score;}
通过 addQuestion() 方法,将每道题目和其对应分值保存下来,并通过调用 getTotalScore() 方法来获取试卷的总体分数,整个系统在对题目进行对比时会基于这些分值进行打分处理。
答案核对与分数统计 :
核心的 compareAnswer() 方法依然负责比较用户输入和标准答案,得到的是布尔值来表示答案是否正确。接下来,系统引入了分数计算,通过对判断是否互相匹配来决定得分。 每份答卷最终的总得分是对每个题目的得分情况进行累积得到的,以下是结果展示部分的相关代码:
public void displayResults() {
.........
for (int i = 0; i < testPaper.getQuestions().size(); i++) {
Question question = testPaper.getQuestions().get(i);
String answer = i < answers.size() ? answers.get(i) : null;
boolean isCorrect = question.compareAnswer(answer);
if (isCorrect) {
totalScore += testPaper.getScores().get(i);}}
// 构建对应的结果字符串显示
System.out.println(resultBuilder.toString());
System.out.println(scoreBuilder.toString() + "~" + totalScore);}
错误处理与提示增强 :
系统中增加了对非法输入的处理,例如试卷可能存在的问题就是总分不等于100。系统通过检测总分来给出警告信息,如果有异常数据,系统会通过 alerts 提示进行警告输出:
if (testPaper.getTotalScore() != 100) {
alerts.add("alert: full score of test paper" + testPaper.getTestPaperId() + " is not 100 points");}
试卷和答卷信息解析 :
在迭代二中,题目输入、试卷解析逻辑更加复杂。除了通常的题目序号和题目内容外,还要处理对应的分值。系统通过标识符 #T: 来解析试卷的配置,并绑定题号和其分值。这使得系统变得更加灵活和贴近实际业务需求。
以下是题目二的类图,展示了我的代码是如何设计类以及方法的使用:
再是我的顺序图在这个顺序图中,我展示了以下步骤:

Main类创建了一个Scanner对象来读取输入。
Main类调用processInput方法来处理输入,并将读取到的行传递给相应的处理方法。
processInput方法根据行的前缀调用processQuestion、processTestPaper或processAnswerSheet方法。
processQuestion方法将新创建的Question对象添加到questions列表中。
processTestPaper方法创建新的TestPaper对象,并将题目和分数添加到testPapers列表中。
processAnswerSheet方法创建新的AnswerSheet对象,并将其添加到answerSheets列表中。如果找不到对应的试卷编号,它会向errors列表中添加一个错误消息。
输入处理完成后,Main类调用displayResults方法来显示结果。
displayResults方法首先打印所有警告和错误消息,然后对每个AnswerSheet对象调用displayResults方法来显示每个答卷的结果。
踩坑心得
- 判分系统中的总分处理:
在实现中,系统被要求检查试卷总分是否为100,但最初的设计中忽略了这一点,没有检查题目分数是否合理。这导致当用户输入加总错误的题目分数时,系统无法发出正确信号以提醒用户。
改进措施 :
我在 TestPaper 中增加了总分检测功能,并对总分做了严格的限制,当检测到总分数小于或大于100时,系统通过 alerts 给出警告提示。
if (testPaper.getTotalScore() != 100) {
alerts.add("alert: full score of test paper " + testPaper.getTestPaperId() + " is not 100 points");}
此函数有效增强了系统的健壮性,可以始终确保考试试卷设计的正确性和合理性。
- 题目答案与题目编号的错位问题:
在第二次迭代中,由于需要对多试卷、多题目的分数管理,这给解析答案和题目编号的顺序带来了问题。例如,在 addQuestion() 中将题目编号和分数一一对应时,存在错位问题。最初假设分数按照特定模式输入,但实际上,用户输入顺序可能不符合预期。
改进措施 :
为了解决这一问题,我通过为每一个题目绑定唯一的题目编号和相应的分数字段,确保分数和题号一一对应,防止答案错位。同时增强代码中的容错机制,在无法匹配题号时返回错误提示。
for (TestPaper tp : testPapers) {
if (tp.getTestPaperId() == testPaperId) {
answerSheet = new AnswerSheet(tp);}} // 确保答案和试卷匹配
收获与不足
优势 :
增加了对分数体系的支持,使系统更贴合真实应用场景,每个题目都可以分配一个独立的分数。
错误处理略有增强,通过使用警告和提示,可以提醒用户试卷分数是否达到标准上限。
不足 :
题目管理仍然较为固定 :多试卷虽然引入了分数管理,但题目类型和结构仍然较为单一,扩展性有限。
缺乏答案格式的严格校验 :如果答案输入错误,系统仍然缺乏足够的健壮性来处理非预期格式的输入。错误输入可能导致分数计算中的逻辑错误。
迭代三:题目集3 - 题目动态管理与状态处理
功能设计
在第三次迭代中,系统要求具备更为复杂的状态管理 ,例如题目的删除、无效题检测以及错误输入处理。通过这些增强,系统变得更加健壮并具有更强的功能:
题目删除功能 :能够通过 #D 指令标记题目为删除状态,后续的答案核对和分数计算要跳过这些被删除的题目。
非法输入的检查与提示 :系统进一步增强了对输入的校验和处理,能够在输入有误的情况下自动给出提示,避免用户陷入死循环。
更细致的反馈 :当用户答题时,无论题目是否被删除、答案是否为空或是否为非法格式,系统都会给出合理的反馈,提高用户体验。
代码结构分析
题目状态控制 :每个 Question 对象增加了 isDeleted 属性,用于标识题目是否被删除。如果题目被删除,在答案检查和分数计算时跳过该题。这个实现通过增加题目状态来避免误用无效数据。
增加用户输入校验与反馈 :对于非法输入增加了提示,通过 errors 记录下错误并友好输出。进一步封装使得用户输入的处理更加鲁棒。
时间复杂度分析
依旧主要基于线性扫描题目与答案,并且在答案校验的基础上增加了对状态的检测:
题目删除 :删除标记的复杂度为 O(1) ,直接设置标记。
答案检查 :与上一次迭代相比,仅增加了对题目状态的筛选,整体仍为 O(N) 。
非法输入处理 :对于每次输入,存在合法性检查,时间复杂度为 O(L) (逐行检测行长) ,复杂度与输入行长成正比。
空间复杂度分析
增加题目状态标记后,消耗空间没有显著增加。总体来说,系统的空间复杂度依然与题目数量呈线性关系,为 O(N)。
圈复杂度分析
题目删除功能 :相比于单纯的题目状态处理,题目删除后的高效判定只需检查 isDeleted 状态,但在所有逻辑核对中,系统增加了较多的 if-else 判断。圈复杂度增加了1-2。
错误处理与报警系统 :增加了必要的错误检测和错误处理逻辑,特别是多个 if 分支和异常路径,进一步增加了代码复杂度,圈复杂度大致为 4-5。
功能解析
题目删除功能 :
在程序中,我们通过 isDeleted 属性来标识题目的状态。如果某道题目被删除,则消息标记为删除,系统在核对答案和计算分数时会跳过被删除的题目。 新增的 processDeleteQuestion() 用于处理题目删除逻辑,当输入 #D: 指令并获取题目编号时,特定题目会加入删除状态。
if (question.isDeleted()) {
resultBuilder.append("the question ").append(question.getNumOfQuestion()).append(" invalid~0\n");
scoreBuilder.append("0 ");}
输入处理与容错增强 :
系统的另一个提升是输入和错误处理的容错性增强。例如,在处理题目时,系统会先验证输入是否合法并进行相应的判定和报错信息:
if (parts.length < 3 || content.isEmpty() || standardAnswer.isEmpty()) {
errors.add("wrong format:" + line);
return;}
这类判断能确保程序不会因为不合规的输入而崩溃,并对用户给出明确提示。
增强的答卷展示与分析 :
新版本中,答卷结果展示进一步增强,不仅展示学生答案,还会针对各类问题给出详细的答案解释。如果题目已经被删除,则会单独输出“已删除”的提示。此外,系统还能根据学生信息给出成绩结果,更符合现实中的场景需求。
以下是题目三的类图,展示了我的代码是如何设计类以及方法的使用:
再是我的顺序图在这个顺序图中,我们展示了Main类如何使用Scanner来读取输入,并根据输入的前缀调用不同的处理方法。然后,Main类调用displayResults方法来显示所有答卷的结果。每个处理方法(如processQuestion、processTestPaper等)都是根据输入行的前缀来决定是否被调用。
踩坑心得
- 题目删除功能的实现问题:
在最初设计题目删除功能时,简单地把删除处理成了通过链表中删除元素。但这样有个问题——直接从 LinkedList 中删除元素可能会导致引用丢失,并影响后续的题目编号查找顺序。
改进措施 :
改进后的思路是,引入 isDeleted 标志位,而不是从列表中直接删除元素,而只是标记题目为已删除。这样,题目依然在系统中保存,但在判卷时会根据状态判断是否跳过该题目。
public void delete() {
this.isDeleted = true;}
通过这种标记删除,避免题目丢失的错误情况。
- 输入格式出错导致系统崩溃 :
在第三次迭代期间,最初错误处理逻辑很简单,直接抛出异常,导致程序崩溃。这种处理方式不利于用户体验,尤其是在答案格式不符合预期时,用户难以知晓问题出在哪里。
改进措施 :
为了解决这种问题,我设计了一套更加清晰的错误处理机制。每次输入数据时,我会在输入不当时将错误信息记录到 errors 列表中,最后通过 Main 类中的 displayResults() 将这些错误提示展示出来,而不会中断整个程序。错误提示友好而清晰,避免了用户不知道和无法处理的情况。
errors.add("wrong format: " + line);
收获与不足
优势 :
状态管理增强 :系统可以动态管理题目,处理删除和无效题,提升了真实场景中的可操作性。
错误处理更强大 :通过增加校验,可以避免程序直接崩溃,而是通过输出明确的错误提示,提升了容错性和用户体验。
不足 :
尽管系统逐渐坚固,但仍然是基于更为静态的题目模型,未来可以通过引入数据库或文件持久化方式扩展规模。
遗留的多题型兼容问题仍然未解决,目前支持单一的标准答案判断,未来可以通过引入面向接口的设计方式,来支持多类型题目。
代码总体质量分析
在三次迭代中,逻辑逐渐清晰,功能扩展但也变得更加复杂。我们可以根据各个指标来总结代码的总体质量:
-
时间复杂度和空间复杂度总结
在每次的代码设计中,所有操作基本都保持在合理的时间复杂度水平。常见的操作如对题目的录入和计算保持在 O(N) 或 O(N log N) 的复杂度,显示出较好的可扩展性。当然在数据规模极为庞大的情况下,仍需要考虑增加性能优化的措施,例如缓存机制。 -
圈复杂度与代码复杂性
从圈复杂度的分析可见,代码的复杂性确实随着功能增加而上升,但没有出现过高的圈复杂度问题(大于10的危险区间)。因此,在可维护性、可读性上,这段代码依然表现优秀。通过必要的重构(例如模块化和记者架构),未来可以进一步减少圈复杂度,增进代码清洁度。 -
代码可维护性
这些代码中最为突出的设计点是,将功能逐渐模块化拆分。Question、TestPaper、AnswerSheet 各自承担单一的功能职责,避免了代码耦合度过高的问题。然而,仍然存在拓展空间,例如扩展多题型或持久化系统设计。
总结:迭代演进与架构优化的最终反思
经过三次迭代的答题系统开发,我对编程的系统性设计、代码架构优化、输入错误处理以及复杂功能的迭代实现有了更深刻的认识。这次作业不仅是一次代码编写的练习,更是一次对软件开发流程、代码结构优化、边界条件处理等多方面的综合考验。
以下是对每次迭代的经验、问题、和最后的反思与总结:
- 从基础系统开始:第一次迭代的思考
第一次迭代实现了代码的自动化和模块化。这次迭代的设计清晰:根据用户提供的输入,系统能够将题目录入到一个题库中,并管理这些题目,最终根据用户输入的答案给予相应的判断。第一次迭代的代码逻辑非常线性,功能紧凑,没有复杂的逻辑分支和异常处理。通过Question、TestPaper和AnswerSheet三个核心类的构造,系统初步有了合理的模块划分。
但同时,第一次代码有许多明显的不足之处,在实际开发中也踩了一些坑。最典型的问题是缺少健全的输入校验与处理机制。例如,当输入格式错误,或者用户在输入中漏掉了关键字段时,系统直接挂掉,甚至没有给用户任何提示。这个问题反映了程序对不同输入情况下的鲁棒性不足。虽然代码运行时逻辑清晰,但没有做好足够的“防御性编程”,导致用户不按照正确格式输入时,系统崩溃的现象频繁发生。
从第一次迭代的经验中,我认识到,代码不仅要完成功能,更要应对不同情况下的异常输入。用户的输入不仅限于我们预设的理想场景,可能会是错误或者模糊的输入,这也是第一次迭代系统最弱的地方。
- 功能扩展与复杂性增加:第二次迭代的提升与思考
第二次迭代是在第一次基础上的功能增强。除了支持单一的试卷和题目,这一部分我们增加了多试卷管理,引入了分数管理以及最终评分的判断。这一迭代的目的是使系统更加接近实际应用场景,能够处理更复杂的业务逻辑,如成绩计算和多个题目分数合并等。
针对第一次的不足,我加入了对输入格式的初步检查,如确保输入的题目格式正确,题目与分数匹配,答案能够合理一致等等。尤其在这次迭代中,系统增加了评分标准,更贴合实际的考试场景。通过在 TestPaper 类中加入分数的管理,系统可以为每一题目分配各自的分数,并且最后通过 getTotalScore() 方法计算总分。同时,还为系统增加了分数校验与警告提示,当总分不等于100时,系统会提醒设计者总分超标的风险。
然而,这次迭代中输入错位与答案匹配的问题依然存在。在项目实施过程中,假设用户总是按照我们所设定的规则输入题目编号(例如 #N, #Q, #A),若丢失某一个编号时,将导致后续操作步骤紊乱。同时,判题过程中的输入错位也会引发严重的后果。例如,用户在输入答案时输入顺序错误,这将导致比对答案时错位,导致结果完全错误。
这一迭代的踩坑心得就是,即便系统功能越来越复杂,也必须确保处理输入和业务逻辑时具备灵活性和准确性。为了解决输入错位问题,我在这版里增加了逐行输入格式的合法性检查,确保每一个输入环节都得到约束。具体的解决方案是引入一个 isValidInput() 函数,它可以检测题目和答案字段是否存在,输入是否合法等,避免程序因为输入失误而卡死在某一步。
- 动态管理与健壮性的提升:第三次迭代的扩展与完善
第三次迭代是在前两次基础上的再次提升,引入了对于复杂场景的处理,例如题目的删除以及状态管理。事实上,系统从最开始加入简单的多题型支持,到最终能够动态删除某些题目并保持系统健壮性,经历了较为明显的完善过程。
题目删除功能是这一迭代里最重要的功能优化。早期的简单删除操作会导致系统内存溢出甚至逻辑紊乱,原因在于题目删除后,链表的项如果真的删除,会影响到后续逻辑查询。而在这次迭代中,标记删除而非实际删除成为我们更好的解决方案。通过在 Question 类中引入 isDeleted 标志位,我们可以清楚地标识哪些题目被删除或禁用,而不会影响题目的存在与否,在所有后续查询中忽略这些已标记的题目即可。标记删除有效避免了潜在的数据流问题,并与评分系统良好兼容。
与此同时,错误处理与日志管理也成为系统的关键组件。通过记录 errors,我们可以在允许输入不当的情况下输出明确信息,并提示用户如何纠正错误。这大大增强了系统的健壮性和用户体验。在以往的 dealInput() 和 readAnswers() 中,用户输入错误很容易导致整个系统崩溃,但本次新增的错误提示机制不仅提升了系统的健壮性,还为用户提供了交互反馈。
这部分的踩坑经历主要集中在,系统必须处理好边界情况。如用户随机删除题目后,后续的输入、输出逻辑能否保证一致;题目的状态管理和输出是否实时更新等。我通过这次迭代,意识到系统的功能虽然得到了显著扩展,但背后带来了更多的复杂性,这也是需要重点解决的问题。
综合反思
通过三次迭代的逐步优化,本次开发任务让我深刻认识到了系统开发中的几个关键元素:
-
健壮性:系统的健壮性不仅表现在它是否完成规定的任务,更重要的是它能否合理应对各种异常输入和边界条件。在三次开发中,用户输入格式的验证和对用户的反馈处理一直是难点;从最初完全脆弱的输入处理,到最终引入更加清晰的错误提示和返回机制,极大提升了用户体验与系统稳定性。
-
模块化与扩展性设计:一个可持续发展的系统必须具备良好的模块化设计。
Question、TestPaper和AnswerSheet三个类虽然从一开始就存在,但在迭代过程中,它们的职责逐渐明确,并相互分离,避免了耦合过高的风险。同时,为了便于系统后续扩展,如加入不同题型(选择题、填空题)等,我们还可以通过增加接口或抽象类来编排不同题目类型的实现。 -
错误与异常处理:从第一次代码的粗糙输入逻辑,到第三次迭代中加入的全面输入检查机制,这一思路在整个系统开发中不断演进。确保系统面对输入错误时不会崩溃,并为用户提供弹性反馈,是软件开发中的重要一环。
-
动态管理的实现:随着系统的功能越来越复杂,处理动态题目管理,删除以及状态查询显得尤为重要。从最开始的暴力删除题目,到通过状态标记实现题目禁用,整个解决方案的演进过程告诉我,即使是工程实现中的某些简单功能点,背后却可能隐藏许多意想不到的复杂性。需要考虑到所有可能产生的错误模块。
后续需要优化与提高的点
-
实现更复杂的题型扩展:目前整个系统支持单一的题型,未来可以通过设计接口或抽象类来为其扩展更多题型。例如位于
Question类中的compareAnswer()方法,可以通过策略模式根据不同题目类型采取不同的对比方式。 -
性能和数据管理:随着题库的增大,系统的性能将不可避免地成为问题。未来可以通过将题目和答案持久化到数据库中,以提升系统性能和数据查询速度。此外,支持文件系统或数据库来存储大量题目也将是重要优化步骤。
最终总结
这次三次迭代的作业让我打下了坚实的编程基础,在系统架构设计、代码优化、鲁棒性提升等方面收获颇丰。通过对代码模块化设计、输入校验、性能优化和错误提示的深入理解和不断优化,我认识到了编程不仅仅是围绕正确的功能实现。系统健壮性、可维护性、可扩展性才是程序长久应用中的核心问题。
从最简单的题目解析系统到支持数值判断、分数校验、题目删除和异常处理的复杂系统,这个过程让我对编程的面向对象思维、程序设计思路有了更深刻的感悟。在接下来的学习中,我将进一步优化代码逻辑,强化数据管理,完善扩展性,并提高大规模数据处理性能!

浙公网安备 33010602011771号