Overview: The Cost of Poor Design Writing code that works is only half the battle. In software development, the real challenge lies in writing code that is maintainable, readable, and resilient to change. A simplified Battleship game serves as a perfect laboratory for identifying common code smells—like magic numbers, unnecessary recursion, and bloated class states. Refactoring this project isn't just about making the game run; it is about decoupling logic from data and ensuring each function has a single, clear responsibility. By cleaning up these structural issues, we transform a fragile script into a robust application. Prerequisites To follow this tutorial, you should have a firm grasp of Python fundamentals, including classes, methods, and list indexing. You should also understand how to run scripts from the command line and be familiar with basic control flow structures like `if/else` statements and loops. Key Libraries & Tools * **Python 3.x**: The core programming language used for the implementation. * **Random Module**: Used for generating ship locations on the grid. * **OS Module**: Utilized for clearing the terminal screen to maintain a clean user interface during gameplay. * **Typing Module**: Specifically `Callable`, to define clear interfaces for callback functions during refactoring. Refactoring the User Input Mechanism One of the most frequent mistakes in beginner code is mixing input logic with game logic. The original code used recursion to handle invalid inputs, which can lead to stack overflow errors if a user is particularly stubborn. Replacing this with a `while True` loop and a `try-except` block makes the input much more robust. ```python def read_int(prompt: str, min_value: int = 1, max_value: int = 5) -> int: while True: try: line = input(prompt) value = int(line) if value < min_value: print(f"The minimum value is {min_value}. Try again.") continue if value > max_value: print(f"The maximum value is {max_value}. Try again.") continue return value except ValueError: print("That's not a number. Try again.") ``` This generic `read_int` function is a workhorse. It handles the conversion, validates ranges, and catches non-numeric strings without crashing the program. By defining parameters for `min_value` and `max_value`, we eliminate the need to hardcode board boundaries inside our input functions. Decoupling Logic from the Game Class The original `Game` class suffered from "state bloat," storing temporary variables like the current guess as instance variables. This is a mistake. If a value is only needed for a single calculation or turn, it should be a local variable. We also extracted the `read_guess` logic into a standalone function that takes a `Callable` as an argument. This allows the function to check if a coordinate was already guessed without needing to know the internal structure of the `Board` object. ```python def read_guess(already_guessed: Callable[[int, int], bool]) -> tuple[int, int]: while True: row = read_int("Guess the row: ") - 1 col = read_int("Guess the column: ") - 1 if not already_guessed(row, col): return row, col print("You already guessed that! Try again.") ``` Syntax Notes * **f-strings**: We use f-strings (e.g., `f"Value: {val}"`) for cleaner, more readable string interpolation compared to the older `%` or `.format()` methods. * **Type Hinting**: By using `Callable[[int, int], bool]`, we explicitly define that a function must accept two integers and return a boolean. This makes the code self-documenting. * **Removing `object` Inheritance**: In Python 3, classes inherit from `object` by default. Writing `class Game(object):` is redundant and should be simplified to `class Game:`. Practical Examples This refactoring pattern applies far beyond Battleship. Any CLI tool requiring user validation—such as a bank account manager or a configuration wizard—benefits from a centralized, validated input utility. Decoupling the "checking" logic (the callback) from the "fetching" logic (the input loop) allows you to reuse the same input UI for different validation rules across your entire codebase. Tips & Gotchas * **Avoid recursion for UI loops**: Use `while` loops for user input. Recursion is for recursive data structures, not for waiting on a human to type the right number. * **Handle Off-by-One Errors**: Remember that users think in 1-based indexing, while Python lists use 0-based indexing. Always subtract 1 from user input immediately after validation. * **Keep Comments Professional**: Avoid putting opinions or "easy to read" claims in comments. Code should be clear enough that the comments only explain the *why*, not the *what*.
Sags
People
Feb 2022 • 1 videos
High activity month for Sags. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Feb 2022
- Feb 25, 2022