From 00979b2e815c047970c251bf769f1aa9fd936160 Mon Sep 17 00:00:00 2001 From: trigg770 Date: Thu, 10 Sep 2026 20:20:45 +0800 Subject: [PATCH] Assert the assumptions the code relies on Several classes rely on a guarantee made elsewhere without saying so. If a later change breaks one of these guarantees, the failure appears far from its cause, as a confusing reply or a NullPointerException. Let's state each assumption with an assert whose message names what guarantees it: * Parser.parse never sees blank input, as Ted skips it * Storage.parseLine never sees a blank line, as load() skips them, and never reaches its default branch, as fieldCountFor accepts only T, D and E * TaskList is never given null, as Storage.load() skips unreadable lines instead of adding null * Ted always has a task list, as the constructor makes an empty one when loading fails * MainWindow always has a Ted, as Main calls setTed before showing the window Let's also enable assertions for gradle run, as Java ignores them by default. Tests already run with assertions on. Checks on user input stay as exceptions, because bad input is expected and must be reported even with assertions off. For the same reason there is no assert that an event ends after it starts: the parser rejects such an event, but a hand-edited save file can still hold one. Co-Authored-By: Claude Opus 5 --- build.gradle | 3 +++ src/main/java/ted/MainWindow.java | 1 + src/main/java/ted/Parser.java | 2 ++ src/main/java/ted/Storage.java | 4 +++- src/main/java/ted/Ted.java | 1 + src/main/java/ted/task/TaskList.java | 1 + 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index da67b39dae..81332a0b83 100644 --- a/build.gradle +++ b/build.gradle @@ -73,4 +73,7 @@ shadowJar { run { standardInput = System.in + // Java ignores assert statements unless told otherwise, so turn them on when + // running the app from Gradle. Tests already run with assertions on. + enableAssertions = true } diff --git a/src/main/java/ted/MainWindow.java b/src/main/java/ted/MainWindow.java index 3ded5d5653..8658e605ba 100644 --- a/src/main/java/ted/MainWindow.java +++ b/src/main/java/ted/MainWindow.java @@ -59,6 +59,7 @@ public void setTed(Ted ted) { */ @FXML private void handleUserInput() { + assert ted != null : "Main calls setTed before showing the window, so input always has a Ted"; String input = userInput.getText(); String response = ted.getResponse(input); if (response.isEmpty()) { diff --git a/src/main/java/ted/Parser.java b/src/main/java/ted/Parser.java index 6e2ed48bde..400c1704f8 100644 --- a/src/main/java/ted/Parser.java +++ b/src/main/java/ted/Parser.java @@ -57,6 +57,8 @@ private Parser() { * @throws TedException if the command is unknown or its details are unusable. */ public static Command parse(String input) throws TedException { + assert !input.isBlank() : "Ted skips blank input, so it never reaches the parser"; + // Splitting into at most two parts keeps the command word exact, so that // "todos" is not mistaken for "todo", while leaving the rest untouched. String[] parts = input.split(" ", 2); diff --git a/src/main/java/ted/Storage.java b/src/main/java/ted/Storage.java index b7628abe5d..353adb67d5 100644 --- a/src/main/java/ted/Storage.java +++ b/src/main/java/ted/Storage.java @@ -140,6 +140,8 @@ public int getSkippedLineCount() { * @return the rebuilt task, or {@code null} if the line is not in the expected format. */ private static Task parseLine(String line) { + assert !line.isBlank() : "load() skips blank lines, so they are never parsed"; + // A first, unlimited split just to read the type icon safely: even a // corrupted line must have at least its icon before anything can be parsed. String icon = line.split(FIELD_SEPARATOR_REGEX)[0]; @@ -173,7 +175,7 @@ private static Task parseLine(String line) { parseSavedDateTime(fields[3])), isDone); default: - // Unreachable: fieldCountFor accepts only T, D and E. + assert false : "unreachable, as fieldCountFor accepts only T, D and E"; return null; } } catch (DateTimeParseException e) { diff --git a/src/main/java/ted/Ted.java b/src/main/java/ted/Ted.java index 5bb77606a2..86d7f11717 100644 --- a/src/main/java/ted/Ted.java +++ b/src/main/java/ted/Ted.java @@ -117,6 +117,7 @@ public String getResponse(String input) { return ui.flush(); } + assert tasks != null : "the constructor sets up a task list even when loading fails"; try { command.execute(tasks, ui, storage); } catch (TedException e) { diff --git a/src/main/java/ted/task/TaskList.java b/src/main/java/ted/task/TaskList.java index d9f3a17753..32cb41d8ac 100644 --- a/src/main/java/ted/task/TaskList.java +++ b/src/main/java/ted/task/TaskList.java @@ -28,6 +28,7 @@ public TaskList() { * @param tasks the tasks to start with. */ public TaskList(List tasks) { + assert tasks.stream().allMatch(task -> task != null) : "Storage.load() never adds null"; this.tasks = new ArrayList<>(tasks); }