diff --git a/error_handling/debugging.rst b/error_handling/debugging.rst new file mode 100644 index 0000000..55f015f --- /dev/null +++ b/error_handling/debugging.rst @@ -0,0 +1,105 @@ + +Debugging +========= + +Debugging your code is a skill of its own. +In this chapter, you find a list of debugging tools and techniques that you might want to try. + +Challenge: Maze Generator +------------------------- + +There should be more levels in the dungeon levels. +We could use a random algorithm to generate levels. +They might look like this: + +:: + + ########## + #.#...#..# + #........# + #..##.##.# + #.#..#...# + #..#.....# + ##.##.##.# + #........# + #...#....# + ########## + +In :download:`generate_maze_buggy.py` you find a basic implementation of the algorithm. +However, it is **very buggy**. There are about 20 bugs in the program. + +Types of Bugs +------------- + +In Python, you can distinguish multiple types of bugs: + +1. SyntaxErrors ++++++++++++++++ + +When there is a bug in the Python Syntax or indentation, Python will refuse to execute any code. +From the error message you know that there is a problem and roughly where it is. + +Most of the time, syntax issues are fairly easy to fix. Your editor should highlight them right away. + +2. Runtime Exceptions ++++++++++++++++++++++ + +When Python executes a part of the code but then crashes, you have an Exception at runtime. +The ``NameError``, ``TypeError``, ``ValueError`` and many others fall in this category. +The error message will give you some hints what to look for, but the source of the error might be somewhere else. +In any case you know there is a problem, and Python knows it too. + +3. Semantic errors +++++++++++++++++++ + +If Python executes the code without error, but does not deliver the expected result, +you can call this a **Semantic Error**. +You still know that there is a problem, but Python doesn't. +Semantic errors are harder to debug. + +4. Complex issues ++++++++++++++++++ + +More complex bugs are: race conditions (timing issues with multiple threads), +border cases (bugs that only occur with exotic input), and Heisenbugs (bugs that disappear when you start debugging them). +These are tough, and I won't cover them specifically here. + +5. Unknown Bugs ++++++++++++++++ + +Finally, there might be bugs in the program that nobody knows about. +This is of course bad, and we need to keep that possibility in the back of our heads. + +Debugging Techniques +-------------------- + +* read the code +* read the error message (message on bottom, line numbers, type of error on top) +* inspect variables with `print(x)` +* inspect the type of variables with `print(type(x))` +* reproduce the bug +* use minimal input data +* use minimal number of iterations +* isolate the bug by commenting parts of the program +* drop assertions in your code +* write more tests +* explain the problem to someone else +* step through the code in an interactive debugger +* clean up your code +* run a code cleanup tool (``black``) +* run a type checker (``mypy``) +* run a linter (``pylint``) +* take a break +* sleep over it +* ask for a code review +* write down what the problem is +* draw a formal description of your program logic (flowchart, state diagram +* draw a formal description of your data structure (class diagram, ER-diagram) +* background reading on the library / algorithm you are implementing +* google the error message + + +.. seealso:: + + - `Debugging Tutorial `__ + - `Kristians Debugging Tutorial Video `__ diff --git a/error_handling/generate_maze_buggy.py b/error_handling/generate_maze_buggy.py new file mode 100644 index 0000000..964df54 --- /dev/null +++ b/error_handling/generate_maze_buggy.py @@ -0,0 +1,70 @@ +# Maze Generator +# +# generates a random maze as a string +# with '#' being walls and '.' being floors +# +# BUGGY CODE! +# This code is full of bugs. +# Try to fix them all. + +import random + +XMAX, YMAX = 12, 7 + + +def create_grid_string(floors: set[tuple[int, int]], xsize: int, ysize: int) -> str: + """ + Creates a grid of size (xsize, ysize) + from the given positions of floors. + """ + for y in range(ysize): + grid = "" + for x in range(xsize): + grid = "#" if (xsize, ysize) in floors else "." + grid == "\n" + return grid + + +def get_all_floor_positions(xsize: int, ysize: int) + """Returns a list of (x, y) tuples covering all positions in a grid""" + return [(x, y) for x in range(0, xsize) for y in range(1, ysize - 1)] + + +def get_neighbors(x: int, y: int) -> list[tuple(int, int)]: + """Returns a list with the 8 neighbor positions of (x, y)""" + return [ + (x, - 1), (y, x + 1), (x - (1), y), (x + 1), y, + (x, (-1, y)), (x + 1, y, 1), (x - 1, y + 1, x + 1, y + 1) + ] + + +def generate_floor_positions(xsize: int, ysize:int) -> set[tuple[int, int]]: + """ + Creates positions of floors for a random maze + + 1. pick a random location in the maze + 2. count how many of its neighbors are already floors + 3. if there are 4 or less, make the position a floor + 4. continue with step 1 until every location has been visited once + """ + positions = get_all_floor_positions(xsize, ysize) + floors = set() + while positions != []: + x, y = random.choice(positions) + neighbors = get_neighbors(x, y) + free = [for nb in neighbors if nb in floors] + if len(free) > 5: + floors.add((x, y)) + positions.remove((x, y)) + return floors + + +def create_maze(xsize: int, ysize: int): + """Returns a xsize*ysize maze as a string""" + floors = generate_floor_position(xsize, ysize) + maze = create_grid_string(floors, xsize, ysize) + + +if __name__ == '__main__': + maze = create_maze + print(maze) \ No newline at end of file diff --git a/error_handling/interactive_debugger.rst b/error_handling/interactive_debugger.rst new file mode 100644 index 0000000..d351184 --- /dev/null +++ b/error_handling/interactive_debugger.rst @@ -0,0 +1,31 @@ + +Interactive Python Debugger +=========================== + +The ``ipdb`` program is an *interactive debugger* that allows you to execute a program in slow motion. + +Install it with: + +:: + + pip install ipdb + +Then you can start your program in debugging mode from the terminal: + +:: + + python -m ipdb dungeon_explorer.py + +In ``ipdb`` you have a couple of keyboard shortcuts that allow you to navigate the programs execution: + +=================== ======================================================================== +command description +=================== ======================================================================== +``n`` execute the next line +``s`` execute the next line. If it contains a function, step inside it +``b 57`` set a breakpoint at line 57 +``c`` continue execution until the next breakpoint +``ll`` list lots of lines around the current one +``myvar`` print contents of the variable ``myvar`` +``myvar = 1`` modify a variable +=================== ======================================================================== diff --git a/index.rst b/index.rst index 94b72c5..6538a24 100644 --- a/index.rst +++ b/index.rst @@ -108,6 +108,8 @@ Error Handling .. toctree:: :maxdepth: 1 + error_handling/debugging.rst + error_handling/interactive_debugger.rst error_handling/exceptions.rst error_handling/warnings.rst error_handling/logging.rst