Chapter 6: Quiz

Test your understanding of the concepts from this chapter. Each question has a hidden answer that you can reveal by expanding the details section.


Question 1: What is the recommended first prompt in a vibe coding session?

Show Answer A **planning prompt** that describes the overall project, its features, and asks the AI to help plan the structure. Starting with a planning prompt gives the AI context about the entire project, which improves the coherence and quality of all subsequent code generation. It also gives you a chance to evaluate the AI's understanding before committing to implementation.

Question 2: Why does the Task dataclass use field(default_factory=lambda: datetime.now().isoformat()) instead of created_at: str = datetime.now().isoformat() for the created_at field?

Show Answer If you used a plain default value like `datetime.now().isoformat()`, the timestamp would be evaluated **once** when the class is defined, and every task would share the same creation time. Using `default_factory` ensures the lambda is called **each time** a new `Task` instance is created, producing a fresh timestamp for each task. This is a requirement for mutable or dynamically computed defaults in Python dataclasses.

Question 3: What does the dest="command" parameter do in parser.add_subparsers(dest="command")?

Show Answer It stores the name of the chosen subcommand in the `args.command` attribute after parsing. For example, if the user runs `python task_manager.py add "Buy milk"`, then `args.command` will equal `"add"`. This allows the `main()` function to dispatch to the correct handler based on which subcommand was invoked.

Question 4: What is the purpose of the atomic write pattern in save_tasks()?

Show Answer The atomic write pattern writes data to a temporary file first (e.g., `tasks.tmp`), then renames it to the target file (`tasks.json`). This prevents data corruption if the program crashes during the write operation. File renaming is an atomic operation on most operating systems, meaning it either completes fully or does not happen at all. Without this pattern, a crash during writing could leave `tasks.json` in a partially written, unreadable state.

Question 5: Which prompting technique involves showing the AI a concrete example of the desired output format?

Show Answer **Example-driven prompting.** This technique is especially effective for formatting and display tasks. For instance, showing the AI that you want tasks displayed as `1. [x] Buy groceries` is more effective than describing the format in words. The AI can reverse-engineer the formatting rules from the example.

Question 6: In the search_tasks() function, why do we convert both the keyword and the description to lowercase before comparing?

Show Answer To make the search **case-insensitive**. Without converting both strings to the same case, searching for "groceries" would not match a task with the description "Buy GROCERIES". By calling `.lower()` on both strings, the comparison ignores case differences. This is a common pattern for user-facing search functionality.

Question 7: What happens if you add a new field with a default value to the Task dataclass, but the JSON file contains tasks saved before that field existed?

Show Answer The old tasks will load correctly. When `Task(**item)` is called to reconstruct a Task from a dictionary, the missing field will simply take its default value. This is because Python dataclass constructors only require arguments for fields that do not have defaults. This is why it is important to always add new fields **with defaults** when evolving a data model that has existing persisted data.

Question 8: What is the difference between return 0 and return 1 at the end of the main() function?

Show Answer In Unix/terminal conventions, an exit code of **0** indicates success, and a **non-zero** exit code (typically 1) indicates an error. Returning 0 from `main()` means the command completed successfully. Returning 1 means something went wrong (e.g., task not found, empty description, corrupted file). These exit codes can be checked by shell scripts and other tools that invoke the program.

Question 9: Why should you catch specific exception types (like json.JSONDecodeError) rather than using a bare except clause?

Show Answer Catching specific exceptions prevents you from accidentally hiding unrelated bugs. A bare `except` clause catches *everything*, including `KeyboardInterrupt` (Ctrl+C), `SystemExit`, and programming errors like `NameError` or `AttributeError`. By catching only `json.JSONDecodeError`, you handle the specific error you expect (corrupted JSON) while letting other errors propagate normally so you can find and fix them.

Question 10: What does pytest's tmp_path fixture provide, and why is it important for testing file-based operations?

Show Answer `tmp_path` provides a `pathlib.Path` object pointing to a temporary directory that is unique to each test function and is automatically cleaned up after the test completes. It is important for testing file-based operations because: (1) tests do not modify the real file system, (2) each test runs in isolation without interfering with others, and (3) there is no leftover test data to clean up manually.

Question 11: What design pattern is used when functions accept an optional data_path parameter instead of hardcoding the file location?

Show Answer **Dependency injection.** Instead of having each function internally determine the file path, the path is passed in as a parameter. This makes the functions testable (tests can provide a temporary path) and reusable (different parts of the application can use different file locations). The default value (`None`, which falls back to the standard path) preserves convenience for normal use.

Question 12: When narrowing the scope of a project, the chapter suggests deferring features to later. Why is this a better approach than trying to specify everything in the first prompt?

Show Answer Starting with a smaller scope has several advantages: (1) the AI produces simpler, more focused code that is easier to evaluate; (2) you can verify the foundation works before adding complexity; (3) each subsequent feature can build on tested, working code; (4) you maintain a clear mental model of the codebase; and (5) it is easier to debug problems when you know which prompt introduced them. Trying to specify everything at once often overwhelms both you and the AI.

Question 13: In the task manager, what does get_next_id() return when the task list is empty, and why?

Show Answer It returns **1**. When the task list is empty, there are no existing IDs, so the first task should get ID 1. For non-empty lists, it returns `max(task.id for task in tasks) + 1`, which is one more than the highest existing ID. This ensures IDs are always unique and increasing, even if tasks in the middle are deleted.

Question 14: The chapter shows that the AI did not spontaneously add atomic writes or handle corrupted files. What does this reveal about the AI's approach to code generation?

Show Answer AI coding assistants tend to generate **"happy path" code** --- code that works correctly when all inputs are valid and nothing goes wrong. They generally do not add defensive programming measures, safety features, or edge case handling unless explicitly asked. This means the vibe coder must take responsibility for thinking about robustness, security, and failure scenarios, and must prompt the AI to address them.

Question 15: What is the purpose of the if __name__ == "__main__": guard at the end of the task manager script?

Show Answer This guard ensures that `main()` is only called when the script is run directly from the command line (e.g., `python task_manager.py add "test"`). When the file is imported as a module (e.g., in test files with `from task_manager import add_task`), the guard prevents `main()` from executing. This allows the functions to be imported and tested independently without triggering the CLI.

Question 16: Why does the chapter recommend listing specific test scenarios in the testing prompt rather than simply saying "write tests"?

Show Answer Listing specific scenarios ensures the AI writes **meaningful, targeted tests** rather than trivial ones. A vague prompt like "write tests" often produces tests that only cover the happy path or test implementation details rather than behavior. By specifying scenarios like "searching is case-insensitive" or "loading from a non-existent file returns empty list," you direct the AI to test the behaviors that actually matter for correctness.

Question 17: What would happen if you removed the indent=2 parameter from json.dump() in save_tasks()?

Show Answer The JSON output would be written on a single line with no formatting or indentation. Functionally, the program would still work correctly --- `json.load()` can parse both formatted and unformatted JSON. However, the file would be very difficult for humans to read or debug. The `indent=2` parameter formats the JSON with two-space indentation, making it human-readable, which is valuable when debugging or manually inspecting the data file.

Question 18: In the prompt sequence table, what distinguishes a "specification" prompt from a "direct request" prompt?

Show Answer A **specification prompt** provides detailed requirements including specific names, types, behaviors, and constraints (e.g., "Create a function called `search_tasks` that takes a `keyword: str` and returns `list[Task]`"). A **direct request** is simpler and leaves more decisions to the AI (e.g., "Add a search command"). Specification prompts produce more predictable output because they constrain the AI's choices, while direct requests give the AI more freedom, which can be appropriate when you trust the AI's judgment on implementation details.

Question 19: Why does the list_tasks() function accept status_filter as a string ("pending" or "completed") rather than a boolean?

Show Answer Using a string allows for **three states**: "pending" (show only incomplete tasks), "completed" (show only finished tasks), and `None` (show all tasks). A boolean could only represent two of these states. Additionally, the string values map directly to the `--status` CLI argument choices, making the interface between the CLI layer and the logic layer clean and intuitive. This is sometimes called a "tri-state" or "optional filter" pattern.

Question 20: What Python feature does [t for t in tasks if not t.completed] demonstrate, and what is the equivalent written as a traditional loop?

Show Answer This is a **list comprehension** (covered in Section 5.12). The equivalent traditional loop would be:
result = []
for t in tasks:
    if not t.completed:
        result.append(t)
The list comprehension is more concise and considered more Pythonic. It also tends to be slightly faster because the append operation is optimized internally. Both versions produce identical results.

Question 21: Why did the chapter add a data_path parameter to functions like add_task() and load_tasks() rather than using a global variable?

Show Answer Adding a `data_path` parameter provides **testability** and **flexibility**. Tests can pass a temporary path to avoid modifying real data. Different parts of the application could use different files. The parameter has a default value of `None` (which falls back to the standard location), so normal usage is unaffected. Using a global variable would make testing harder (you'd need to modify global state) and would prevent using different file locations simultaneously. This is the dependency injection pattern.

Question 22: The chapter uses Path.replace() for the atomic rename. What is the difference between Path.replace() and Path.rename()?

Show Answer `Path.replace()` overwrites the destination file if it already exists (which is what we want --- we are replacing the old `tasks.json` with the new one). `Path.rename()` may raise an error on Windows if the destination already exists. Using `replace()` ensures cross-platform compatibility and the expected behavior of atomically replacing the data file.

Question 23: If a user runs python task_manager.py with no arguments, what happens and why?

Show Answer The `main()` function checks `if args.command is None` (which is the case when no subcommand is provided) and calls `parser.print_help()`, which displays the usage information showing available commands. Then it returns 0 (success). This is a user-friendly behavior --- rather than crashing or printing an error, the program shows how to use it correctly.

Question 24: The enhanced version uses an Enum for priority levels. What advantage does this have over using plain strings?

Show Answer An `Enum` provides several advantages: (1) **type safety** --- you cannot accidentally assign an invalid priority like "urgent" or "hi"; (2) **discoverability** --- `Priority.HIGH`, `Priority.MEDIUM`, `Priority.LOW` are self-documenting; (3) **associated data** --- the `sort_value` property attaches sorting logic directly to the enum; (4) **IDE support** --- editors can autocomplete enum members. Plain strings offer none of these protections and can silently accept typos.

Question 25: Describe the complete vibe coding workflow demonstrated in this chapter in five steps.

Show Answer The five-step workflow is: 1. **Plan** --- Start with a planning prompt that describes the project goals and asks the AI for an architecture recommendation. Narrow the scope to what you need now. 2. **Generate** --- Send specific prompts to generate code for each component (data model, persistence, CLI, features). Use specification and example-driven techniques. 3. **Evaluate** --- Review the AI's output. Check for correctness, completeness, style adherence, and potential issues. Identify what needs improvement. 4. **Iterate** --- Send follow-up prompts to refine, fix, and extend the code. This includes adding error handling, improving formatting, and adding missing features. 5. **Test and Reflect** --- Write tests to verify the code works correctly. Then reflect on the process: what went well, what could be improved, and what are the next steps. This cycle (plan, generate, evaluate, iterate, test/reflect) is the core loop of vibe coding and applies to projects of any size.