This analysis examines three progressive implementations of an automatic quiz grading program. Each version introduces additional complexity—from basic answer matching to managing test papers, student records, and deleted questions—requiring increasingly refined object‑oriented design and robust input handling.
- Scope and Knowledge Progression
The assignments focus on:
- String parsing and pattern recognition: Extracting structured data from formatted text lines such as
#N:1 #Q:2+2= #A:4. - Data structure selection: Using
Map,List, andSortedMapto model entities and their relationships. - Conditional logic and fault tolerance: Handling missing questions, invalid paper IDs, absent answers, and score mismatches.
- Object‑oriented principles: Encapsulating behavior in dedicated classes (e.g.,
Problem,ExamPaper,Submission) to respect single responsibility and open‑closed principles. - Error reporting with priorities: Displaying appropriate prompts when a question is deleted, a student ID is invalid, or an answer is missing.
The three stages follow a logical ramp‑up: the first version handles static question‑answer pairs; the second layers paper templates with score validation; the third integrates student identity, question removal, and hierarchical error messages.
- Design and Implementation Details
2.1 Phase 1 – Core Parsing and Answer Verification
The initial solution revolves around a QuizItem class that binds together an identifier, a prompt, and a correct answer. A TreeMap keeps items sorted by ID, while an ArrayList preserves the submission order. The output routine iterates through the sorted map and prints each prompt alongside the user’s response and a correctness verdict.
class QuizItem {
private final int id;
private final String prompt;
private final String solution;
public QuizItem(int id, String prompt, String solution) {
this.id = id;
this.prompt = prompt;
this.solution = solution;
}
public String renderWithSubmission(String userReply) {
return prompt + "~" + userReply;
}
public boolean evaluate(String userReply) {
return solution.equals(userReply);
}
}
// Inside main logic
SortedMap<Integer, QuizItem> catalog = new TreeMap<>();
List<String> replies = new ArrayList<>();
// ... parsing omitted ...
int cursor = 0;
for (Map.Entry<Integer, QuizItem> entry : catalog.entrySet()) {
QuizItem item = entry.getValue();
String reply = replies.get(cursor);
System.out.println(item.renderWithSubmission(reply));
boolean correct = item.evaluate(reply);
// store or print result
cursor++;
}
| Metric | Value |
|---|---|
| Lines of Code | 70 |
| Number of Methods | 3 |
| Average Complexity | Low |
| Comment Ratio | 5% |
The design is deliberately simple; all orchestration resides in main. While straightforward, this structure limits extensibility—something addressed in later phases.
2.2 Phase 2 – Exam Papers, Point Allocation, and Orchestration
The second iteration introduces Problem, ExamPaper, Submission, and a coordinator class (here renamed GradingEngine).
-
Problem: holds id, content, and correct answer; accessible via getters.
-
ExamPaper: maintains a map of problem IDs to point values. It enforces that a valid paper must total exactly 100 points. ```
class ExamPaper { private final int paperId; private final Map<Integer, Integer> problemPoints = new LinkedHashMap<>();
public ExamPaper(int paperId) { this.paperId = paperId; } public void addProblem(int problemId, int points) { problemPoints.put(problemId, points); } public int totalPoints() { return problemPoints.values().stream().mapToInt(Integer::intValue).sum(); } public boolean isTotalValid() { return totalPoints() == 100; }}
-
Submission: stores the target paper ID and a list of user-provided answers, enabling later matching against the paper’s problem sequence.
-
GradingEngine: central registry holding maps of problem and exam papers, plus a list of submissions. Its
process()method validates paper total scores, compares answers, computes per‑problem results, and outputs the final grade breakdown.
| Metric | Value |
|---|---|
| Lines of Code | 132 |
| Number of Methods | 14 |
| Number of Classes | 4 |
| Average Complexity | Medium |
| Comment Ratio | 5% |
Decoupling the paper template from the submission logic improves modularity. However, certain assumptions (e.g., strict ordering of answers) later require guard clauses to prevent IndexOutOfBounds errors.
2.3 Phase 3 – Students, Deleted Problems, and Multi‑Priority Errors
The final version adds Student records, a soft‑delete mechanism for problems, and layered error feedback. The class set now includes:
-
Problem – augmented with a
boolean deletedfield. When a problem is removed, any reference should yield a dedicated warning instead of a normal answer check. ```class Problem { private final int id; private final String description; private final String answer; private boolean removed;
// constructor, getters... public void markAsRemoved() { this.removed = true; } public boolean isRemoved() { return removed; }}
-
Student – holds student ID and name; used to verify that each submission is associated with a known participant.
-
ExamPaper – largely unchanged but now accessed through a
HashMap. -
Submission – stores student ID, paper ID, and a map of question numbers to answers.
The GradingEngine grows several parsing methods (parseQuestion, parsePaper, parseStudent, parseSubmission, parseDeletion). Error generation follows a priority chain: if a question is deleted, output "the question is deleted"; if a question does not exist, output "non‑existent question"; if an answer is missing, output "answer is null"; otherwise, show the actual correctness. Student ID validation adds a "student not found" notice when appropriate.
| Metric | Value |
|---|---|
| Lines of Code | 150 |
| Number of Methods | 10 |
| Number of Classes | 5 (including main) |
| Average Complexity | High |
| Comment Ratio | 10% |
The added features significantly raise the implementation difficulty. The removal flag elegantly avoids physical deletion, but its boolean nature could be upgraded to an enum for richer states (e.g., DRAFT, ACTIVE, RETIRED).
- Common Pitfalls and Debugging Insights
3.1 String Splitting with Symbols and Escape Sequences
Using String.split("#N:") without considering that the argument is a regex can lead to unexpected splits because characters like # and : have special meaning in certain contexts. A safer approach is to match literal patterns with Pattern.compile("#N:|#Q:|#A:") and split accordingly, or to escape the string properly. For whitespace-normalised splitting, split("\\s+") is preferable to split(" ") to handle multiple spaces.
3.2 Boundary Checks on Collections
When the number of user answers is smaller than the expected questions, direct list indexing throws ArrayIndexOutOfBoundsException. Adding an explicit check—such as if (index < answers.size()) before retrieval—and printing a dedicated "missing answer" notice prevents crashes and clarifies the output.
3.3 Deletion Flag Propagation
While marking a problem as removed works, downstream code must consistently check the flag before evaluating an answer. If the check is omitted, deleted problems might still be graded. Centralising the validation inside a GradingEngine helper method reduces duplication and ensures uniform behaviour.
3.4 Format Assumptions and Faulty Inputs
The early prototypes assume perfectly formatted input. Real‑world data often contains extra spaces, missing fields, or swapped tags. Introducing regex‑based validation for each input line and catching mismatches early (with a generic or specific "wrong format" error) greatly improves robustness.
- Refactoring Strategies and Future Directions
Single Responsibility: The main orchestrator in Phase 3 still contains parsing logic mixed with grading logic. Extracting a dedicated InputParser class that returns structured domain objects keeps the main flow focused on coordination.
Encapsulation: Fields like answers in Submission should be private with accessors. Methods that modify problem state (e.g., processDeleteQuestion) belong in the corresponding entity or a repository, not in the top‑level engine.
Strategy for Answer Evaluation: Instead of a cascade of if-else, a lightweight chain‑of‑responsibility or a priority queue of validation rules (problem exists, not deleted, answer present) can clarify the decision tree and make it easier to add new rule types.
Enhanced Error Markup: Rather than concatenating textual warnings directly during output, building result objects with numeric status codes and descriptive messages allows the presentation layer to format the final report consistently.
Configurable State Models: Replacing the boolean removed flag with an enum (e.g., ProblemStatus.ACTIVE, ProblemStatus.ARCHIVED) accommodates future features like temporary deactivation or audit logging without invasive changes.
Adopting these adjustments transforms a fragile, single‑loop grader into a resilient, layered system. The incremental assignment structure effectively highlights how growing feature lists demand deliberate architectural separation, careful input validation, and systematic error handling—skills directly transferable to larger software projects.