-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdebugger.qmd
615 lines (456 loc) · 23 KB
/
debugger.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
# Debuggers {#sec-interactive-debugging}
```{r}
#| label: _common
#| eval: true
#| echo: false
#| include: false
source("_common.R")
library(lobstr)
```
```{r}
#| label: co_box_dev
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "r",
header = "Warning",
contents = "The contents for this section are under development. Thank you for your patience."
)
```
Bugs can cause our app to crash, produce incorrect results or displays, and result in other unexpected behaviors. Fixing bugs in our app-package is an important step to ensure our application continues behaving as expected. This chapter will cover how to use an interactive debugger to help us find the root cause of bugs and errors.
:::{.bugbox}
::::{.bugbox-header}
TLDR: Interactive Debugger & Shiny Apps
::::
::::{.bugbox-container}
Placing a call to `browser()` inside `observe()` will trigger the interactive debugger when the observer is invalidated.
```r
observe({
browser()
})
```
This allows us to interactively examine variables and reactive expressions (within the scope of the `observe()` function).
::::
:::
Debugging in RStudio ![](images/rstudio-icon.png){height=20} is covered elsewhere,[^debug-other-resources] so we'll focus on debugging our Shiny app code using Positron's ![](images/positron.png){height=20} interactive debugger.
```{r}
#| label: shinypak_apps
#| echo: false
#| results: asis
#| eval: true
shinypak_apps(regex = "10", branch = "10_debugger")
```
```{r}
#| label: co_box_positron_version
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "b",
look = "default",
hsize = "1.10",
size = "1.05",
header = "![](images/positron.png){height=20} Positron Version",
fold = FALSE,
contents = "At the time of this writing, the [2025.01.0-39](https://github.com/posit-dev/positron/releases/tag/2025.01.0-39) pre-release of Positron was available for testing."
)
```
[^debug-other-resources]: For an introduction to the IDE's debugging tools, see [Debugging with the RStudio IDE](https://support.posit.co/hc/en-us/articles/205612627-Debugging-with-the-RStudio-IDE). Debugging is also covered in [Advanced R, 2ed](https://adv-r.hadley.nz/debugging.html) and [Mastering Shiny.](https://mastering-shiny.org/action-workflow.html#debugging)
## Interactive debugging {#sec-debug-browser}
Interactive debugging (e.g., using `browser()` or setting a breakpoint) allows us to ‘peek inside’ a function's scope to view intermediate values/variables and break down the execution line by line.
```{=html}
<style>
.codeStyle span:not(.nodeLabel) {
font-family: monospace;
font-size: 1.5em;
font-weight: bold;
color: #9753b8 !important;
background-color: #f6f6f6;
padding: 0.2em;
}
</style>
```
```{mermaid}
%%| fig-cap: 'Interactive debugger with `browser()` or breakpoint'
%%| fig-align: center
%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"16px"}}}%%
flowchart LR
Start(["Begin<br>Execution"])
Browser{{"Breakpoint<br>or <code>browser</code>?"}}
subgraph Debugger ["<strong>Debugger</strong>"]
Step("Enter<br>debugger")
Step --> |"Step Through<br>Code"|Debug{{"Done<br>debugging?"}}
Debug -->|"No"| Step
Debug -->|"Yes"| Exit("Exit<br>debugger")
end
Resume(["Resume<br>Execution"])
Start --> Browser
Browser -->|"Yes"| Debugger
Exit --> Resume
Browser -->|"No"| Resume
%% style FunctionScope fill:#FFF,color:#000,stroke:#333,font-size:14px
%% style Start fill:#FFF,stroke:#333,stroke-width:1px,color:#000,font-size:14px
%% style Browser fill:#ffcccb,stroke:#e60000,stroke-width:3px,color:#000,font-size:14px
%% style Debugger fill:#F8F8FA,stroke:#4CB7DB,stroke-width:3px,rx:10,ry:10,font-size:14px
%% style Step1 fill:#4CB7DB,color:#000,stroke:none,rx:15,ry:15,font-size:14px
%% style Step2 fill:#4CB7DB,color:#000,stroke:none,rx:15,ry:15,font-size:14px
%% style Debug fill:#ffcccb,stroke:#e60000,stroke-width:3px,color:#000,font-size:14px
%% style Exit fill:#4CB7DB,color:#000,stroke:none,rx:15,ry:15,font-size:14px
%% style Resume fill:#FFF,stroke:#333,stroke-width:1px,color:#000,font-size:14px
```
Unfortunately, using `browser()` and breakpoints are not as straightforward within reactive contexts. `browser()` interacts with the R interpreter by temporarily suspending execution and redirecting the input to evaluate R expressions in the context of the function or script that called it.
```{=html}
<style>
.codeStyle span:not(.nodeLabel) {
font-family: monospace;
font-size: 1.5em;
font-weight: bold;
color: #9753b8 !important;
background-color: #f6f6f6;
padding: 0.2em;
}
</style>
```
```{mermaid}
%%| fig-cap: 'How `browser()` works in regular R function'
%%| fig-align: center
%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"14px"}}}%%
flowchart TD
Running(["Being<br>execution"])
subgraph Browse["<strong>browser() Scope</strong>"]
Browser("<code>browser()</code>")
subgraph Debugger["<strong>Interactive Debugger</strong>"]
Step[\"Inspect variables<br>and values"/]
end
end
Resume(["Resume<br>execution"])
Running -->|"Encounter"| Browser --> |"Enter"|Step -->|"Exit"|Resume
%% subgraphs
%% style Function fill:#FFF,color:#000,stroke:#333,font-size:14px %%
%% style BrowserStep stroke:#4CBB9D,color:#000,stroke-width:3px,rx:10,ry:10,font-size:14px %%
%% style Debugger fill:#F8F8FA,color:#000,stroke:#4CB7DB,stroke-width:3px,rx:10,ry:10,font-size:14px %%
%% nodes
%% style Running fill:#FFF,color:#000,stroke:#333,rx:10,ry:10,font-size:14px %%
%% style Encounter fill:#4CBB9D,color:#FFFFFF,stroke:none,rx:10,ry:10,font-size:14px %%
%% style Step1 fill:#4CB7DB,color:#000,stroke:none,rx:15,ry:15,font-size:14px %%
%% style Step2 fill:#4CB7DB,color:#000,stroke:none,rx:10,ry:10,font-size:14px %%
%% style ExitBrowser fill:#4CBB9D,color:#FFFFFF,stroke:none,rx:20,ry:10,font-size:14px %%
%% style Resume fill:#FFF,color:#000,stroke:#333,rx:10,ry:10,font-size:14px %%
```
<br>
Shiny's asynchronous execution makes it difficult for `browser()` to pause the flow of execution, making maintaining an interactive debugging session challenging.
### Reactive browsing
In a Shiny context, code inside the server function is executed asynchronously in response to user inputs or reactive dependencies. Shiny does not directly expose the control flow to the user, but we want to pause this execution without altering or stopping the reactive flow.
Fortunately, Shiny already has a function that performs this: `observe()`. Within a Shiny `server()` function, any call to `observe()` creates a reactive observer that 'listens' for changes to reactive dependencies (and runs any enclosed code whenever those dependencies change).
```{=html}
<style>
.codeStyle span:not(.nodeLabel) {
font-family: monospace;
font-size: 1.5em;
font-weight: bold;
color: #9753b8 !important;
background-color: #f6f6f6;
padding: 0.2em;
}
</style>
```
```{mermaid}
%%| fig-cap: 'Reactive `browser()` in an `observe()`er'
%%| fig-align: center
%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"14px"}}}%%
flowchart TD
Running(["Being<br>execution"])
subgraph Observe["<strong>observe() Scope</strong>"]
Observer("<code>observe({<br><br>})</code>")
subgraph Browse["<strong>browser() Scope</strong>"]
Browser("<code>browser()</code>")
subgraph Debugger["<strong>Interactive Debugger</strong>"]
Step[\"Inspect variables<br>and reactives"/]
end
end
end
Resume(["Resume<br>execution"])
Running -->|"Encounter"| Observer --> Browser --> |"Enter"|Step -->|"Exit"|Resume
%% style Server fill:#FFF,color:#000,stroke:#333,font-size:14px
%% style StartReact fill:#FFF,color:#000,stroke:#333,font-size:14px
%% style EndReact fill:#FFF,color:#000,stroke:#333,font-size:14px
%% style Observer fill:#FFF,stroke:#FEDBC1,stroke-width:3px,font-size:14px
%% style ObserveExecution fill:#FEDBC1,stroke:none,rx:10,ry:10,font-size:14px
%% style Browser fill:#4CBB9D,color:#000,stroke:none,rx:20,ry:10,font-size:14px
%% style Browse stroke:#4CBB9D,color:#000,fill:#FFF,stroke-width:3px,rx:10,ry:10,font-size:14px
%% style Debugger fill:#F8F8FA,stroke:#4CB7DB,stroke-width:3px,rx:10,ry:10,font-size:14px
%% style Step fill:#4CB7DB,stroke:none,rx:10,ry:10
%% style ExitBrowser fill:#4CBB9D,color:#000,stroke:none,rx:20,ry:10,font-size:14px
%% style ExitObserver fill:#FEDBC1,color:#000,stroke:none,rx:20,ry:10,font-size:14px
```
When `browser()` is called from ***within the `observe()` scope***, the code execution pauses and temporarily suspends the reactive flow so we can inspect the environment (without altering or stopping the reactive flow).
## Example: `ggplot2movies` app
In the [Resources](resources.qmd) chapter we developed a slight variation of our app with the `ggplot2movies` data.[^ggp2-callback] In the following sections we're going to use the interactive debugger to see the inner workings of the 'Remove missing' checkbox.
[^ggp2-callback]: You can refresh your memory on the `ggplot2movies` application in @sec-resources-inst-tidy-movies-app.
![Remove missing checkbox in `ggplot2movies` development application (click to enlarge)](images/debug_remove_missing_checkbox.png){width='100%' fig-align='center'}
We'll begin by placing the `observe()` and `browser()` breakpoint *inside* the module server function containing the 'Remove missing' input (right after the `moduleServer()` function). We'll close the `observe()` scope after the reactive `inputs()` are created:
```{r}
#| eval: false
#| code-fold: show
#| code-summary: 'show/hide debugging functions in the module server function'
dev_mod_scatter_server <- function(id, var_inputs) {
moduleServer(id, function(input, output, session) {
observe({ # <1>
browser() # <2>
all_data <- fst::read_fst("tidy_movies.fst") # <3>
graph_data <- reactive({ # <4>
if (input$missing) {
tidyr::drop_na(data = all_data)
} else {
all_data
}
}) |>
bindEvent(input$missing) # <4>
inputs <- reactive({ # <5>
plot_title <- tools::toTitleCase(var_inputs()$plot_title)
list(
x = var_inputs()$x,
y = var_inputs()$y,
z = var_inputs()$z,
alpha = var_inputs()$alpha,
size = var_inputs()$size,
plot_title = plot_title
)
}) # <5>
}) # <1>
observe({ # <6>
output$scatterplot <- renderPlot({
plot <- sap::scatter_plot(
df = graph_data(),
x_var = inputs()$x,
y_var = inputs()$y,
col_var = inputs()$z,
alpha_var = inputs()$alpha,
size_var = inputs()$size
)
plot +
ggplot2::labs(
title = inputs()$plot_title,
x = stringr::str_replace_all(
tools::toTitleCase(inputs()$x), "_", " "),
y = stringr::str_replace_all(
tools::toTitleCase(inputs()$y), "_", " ")
) +
ggplot2::theme_minimal() +
ggplot2::theme(legend.position = "bottom")
})
}) |>
bindEvent(graph_data(), inputs()) # <6>
})
}
```
1. Observer scope
2. Call to `browser()` (execution paused)
3. Read `tidy_movies.fst` data
4. Missing data checkbox logic
5. Reactive values from user inputs
6. Module graph code (outside of `observe()` scope)
Then we'll load the changes:
```{r}
#| label: hot_key_ggp2_movies_app
#| echo: false
#| results: asis
#| eval: true
hot_key(fun = "L")
```
And launch the app using:
```{r}
#| eval: false
#| code-fold: false
launch_app(app = "ggp2")
```
```{r}
#| label: git_box_10_debugger
#| echo: false
#| results: asis
#| eval: true
git_margin_box(
contents = "launch",
fig_pw = '65%',
branch = "10_debugger",
repo = 'sap')
```
```{r}
#| label: remote_inst
#| echo: false
#| eval: false
remotes::install_github("mjfrigaard/sap",
ref = "10_debugger")
```
```{r}
#| label: co_box_dbug_modules
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "r",
size = '1.05',
header = "Loading code changes",
hsize = '1.15',
fold = TRUE,
look = 'default',
contents = "
<br>
Don't forget to load any debugging calls with `devtools::load_all()` _before_ re-launching the app!
\`\`\`r
devtools::load_all('.')
`\`\`\
***Or***
<kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd>
"
)
```
As noted above, `browser()` pauses code execution and activates the interactive debugger mode, allowing us to view objects, execute code, and ‘step through’ the function line-by-line.
### IDE Changes
When the app is launched, Positron ![](images/positron.png){height=20} alerts us that we're in debugging mode by making a few changes to the IDE:[^positron-video]
[^positron-video]: Watch [this video](https://youtu.be/8uRcB34Hhsw?si=OMJz4_043sCPnx8x&t=2452) to learn more about Positron.
#### Sidebar {.unnumbered}
1. The **Run and Debug** sidebar menu item is displayed and the footer is [highlighted in blue]{style="background-color: #6CAAD9; font-weight: bold;"}[^highlight-footer]
![Positron IDE in debugger mode (click to enlarge)](images/debug_positron_all.png){width='100%' fig-align='center'}
[^highlight-footer]: Previous versions of Positron highlighted the footer in red.
#### Editor {.unnumbered}
The `dev_mod_scatter.R` file in the **Editor** highlights the `browser()` function [in yellow]{style="background-color: #FAFFC1; font-weight: bold;"}
![Debugger in file **Editor** (click to enlarge)](images/debugger_positron_editor.png){width='100%' fig-align='center'}
#### Console {.unnumbered}
The **Console** tells us `browser()` was '`Called from: observe()`' and displays the interactive debugger prompt:
![Debugger in file **Editor** (click to enlarge)](images/debugger_positron_initial_console.png){width='75%' fig-align='center'}
`observe()` does not inherently pause or interrupt other reactive processes—it just triggers when changes occur within its scope. So when we're using `observe()` for debugging, we need to define the context (or scope) for its behavior.
The output in the **Console** tells us the `tidy_movies.fst` data are downloaded, but our placement suspends the execution of the application *before* these data are loaded and the graph is rendered in the UI.
::: {layout-ncol=2 layout-valign="top"}
![Debugger in **Console** (click to enlarge)](images/debugger_positron_console.png){width='100%'}
![Suspended `ggplot2movies` data app (click to enlarge)](images/debug_browser_suspend_app.png){width='100%'}
:::
The interactive debugger can only access variables and values inside the `observe()` scope, but this process can be incredibly useful for addressing bugs (and for exploring how an application works). In the next sections, we'll 'step through' the module function to explore how the missing values are removed from the graph.
### Variables and values
We want to use the interactive debugger to proceed through the module function until the data object enters the logic for the missing checkbox, and then we can confirm its structure.
:::{.column-margin}
Step through/execute each line of code by entering **`n`** in the **Console**.
:::{style="font-weight: bold;"}
```
Browse[1]> n
```
:::
:::
As we 'step through' the function, Positron's ![](images/positron.png){height=20} **Console** displays the `debug at` location, followed by the code line number:
![Full path and line number to the file containing our call to `browser()` (click to enlarge)](images/debug_positron_step.png){fig-align='center'}
In the **Editor**, the same line of code is highlighted [in yellow]{style="background-color: #FAFFC1; font-weight: bold;"}:
![Corresponding line number in **Editor** (click to enlarge)](images/debug_positron_highlight_next_exec.png){fig-align='center'}
The line creating `graph_data` gives us a hint for how the missing data are removed (i.e., with `bindEvent()`), but we'll explore this more in [Print debugging](debug_print.qmd).
![`graph_data()` reactive creation (click to enlarge)](images/debugger_positron_graph_data.png){fig-align='center'}
Under **Locals** in the **DEBUG VARIABLES** sidebar, we can see `all_data` is listed as a `<data.frame>`, and `graph_data` are listed as a `<reactive.event>`:
![Click to enlarge **DEBUG VARIABLES** in sidebar](images/debug_positron_reactive_event.png){fig-align='center'}
In the next section, we'll explore these variables (and the reactive inputs).
### Inspecting variables
We can use the **Console** to evaluate code while the interactive debugger is running. This comes in handy if we want to check the structure of an object inside a module (like `all_data`).
::: {layout="[35, 65]" layout-valign="top"}
```r
Browse[1]> str(all_data)
```
```sh
'data.frame': 58788 obs. of 10 variables:
$ title : chr "$" "$1000 a Touchdown" ...
$ year : int 1971 1939 1941 1996 1975 ...
$ length : int 121 71 7 70 71 91 93 25 97 ...
$ budget : int NA NA NA NA NA NA NA NA NA ...
$ rating : num 6.4 6 8.2 8.2 3.4 4.3 5.3 ...
$ votes : int 348 20 5 6 17 45 200 24 18 51 ...
$ mpaa : Factor w/ 5 levels "G","PG","PG-13" ...
$ genre_count: int 2 1 2 1 0 1 2 2 1 0 ...
$ genres : chr "Comedy, Drama" "Comedy" ...
$ genre : Factor w/ 8 levels "Action": 6 3 6 ...
```
:::
This gives us an idea of the total rows before missing are removed.
### Inspecting values
The reactive values and inputs can also be viewed in the **Console**, and we can see `graph_data()` is 'bound' to `input$missing` with `bindEvent()`. We can confirm the `input$missing` value in the **Console**:
::: {layout="[35, 65]" layout-valign="top"}
```r
Browse[1]> input$missing
```
```sh
[1] TRUE
```
:::
This tells us the '*Remove missing*' checkbox has been selected, and we can verify the missing values have been removed from `graph_data()`:
::: {layout="[35, 65]" layout-valign="top"}
```r
Browse[1]> identical(
nrow(tidyr::drop_na(all_data)),
nrow(graph_data())
)
```
```sh
[1] TRUE
```
:::
Using `browser()` to 'step through' an application gives us a better understanding of the 'order of execution', and it lets us see how `input$missing` and `bindEvent()` work together to remove the missing values with the checkbox.
## Recap {.unnumbered}
```{r}
#| label: co_box_recap
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "g",
fold = FALSE,
look = "default", hsize = "1.10", size = "1.05",
header = "Recap: Interactive Debuggers",
contents = "
During regular development, an interactive debugger can let us inspect variables and execute the code line-by-line. In Shiny functions, the debugger lets us track the execution of reactive expressions and observers, which allows us to unravel reactivity-related issues that are often difficult to diagnose.
`browser()` and `observe()` are powerful tools for debugging our application. Navigating a function using the interactive debugger gives us control over the execution of each line.")
```
```{r}
#| label: git_contrib_box
#| echo: false
#| results: asis
#| eval: true
git_contrib_box()
```
<!--
---
### Interactive Debugging
Description:
Interactive debugging uses specialized tools like `gdb`, `pdb` (for Python), or IDE-integrated debuggers (e.g., Visual Studio Code, PyCharm) to allow developers to pause execution, inspect variables, step through code line by line, and modify values at runtime.
Workflow:
1. Set breakpoints in the code where you want the execution to pause.
2. Start the program in debug mode.
3. When the program hits a breakpoint, the debugger pauses execution, allowing you to inspect variable values, evaluate expressions, and step through code interactively.
4. Adjust variables, continue execution, or step through the program to analyze behavior.
Advantages:
- Full control over execution: You can pause execution at any point, inspect the full program state, and even modify variables on the fly.
- Stepping through code: You can step through the program line by line, making it easier to see where things go wrong.
- Comprehensive context: You get access to the entire program’s state, including call stacks, memory, and variable values, without needing to preemptively insert print statements.
- Dynamic: You can modify variables and retry code execution without restarting the program.
Disadvantages:
- Steeper learning curve: Interactive debugging tools can be complex to learn and set up, especially in a more advanced debugger like `gdb`.
- Environment-specific: Some debuggers work best in specific IDEs or environments, and setting up debugging for large or distributed systems may be challenging.
- Overhead: Debugging tools may introduce a small performance overhead during runtime.
- Configuration: In some cases, configuring the environment to allow for interactive debugging can be complex (e.g., for remote systems or in containerized environments).
---
### Comparison Summary
| Aspect | Print Debugging | Interactive Debugging |
|-------------------------|-----------------------------------------------|---------------------------------------------------|
| Ease of use | Simple to implement, universally applicable | Requires setup and a learning curve |
| Runtime control | No control after program execution starts | Full control during program execution |
| Visibility | Only what is printed is visible | Full program context (variables, memory, etc.) |
| Reusability | Temporary, needs removal after debugging | Non-intrusive and doesn’t clutter code |
| Performance impact | Minimal, but output can be excessive | Small overhead due to the debugging tools |
| Best suited for | Simple issues, small projects, or quick tests | Complex bugs, large codebases, or intricate logic |
### When to Use Print Debugging
- You’re working with simple scripts or short code paths where you want a quick, non-intrusive way to track values.
- The issue is easy to reproduce and doesn’t require stopping the program mid-execution.
- You don’t have access to a debugging environment.
### When to Use Interactive Debugging
- You need to debug complex, multi-step issues where controlling execution and inspecting state at specific points is important.
- You want to interactively test changes without modifying the source code.
- You need access to deeper details like call stacks, memory, or threads.
---
### Conclusion
Print debugging is quick and dirty but effective for simple tasks, while interactive debugging provides more powerful and flexible tools for dissecting complex problems. Depending on your workflow, you might start with print debugging and transition to interactive debugging when the issue becomes harder to trace or requires deeper inspection of the program’s state.
-->