當你開始編寫應用程序時,幾乎可以確定會出錯。導致大多數錯誤的原因是我們心里的 Shiny 設計模型與 Shiny 實際的運行情況的不匹配。當你閱讀本文時,你的思維模式將得到改善,從而減少犯錯,而一旦犯錯,就更容易發現問題。但是,要想首次使用代碼就可以可靠地解決復雜的問題,就需要使用多種語言的多年經驗。這意味著你需要構建一個強大的工作流來識別和修復錯誤。
我們將在下面討論三種主要問題:
- 你收到意外錯誤。這是最簡單的情況,因為你將獲得一個錯誤追蹤,使你可以準確確定錯誤的出處。一旦發現問題,就需要系統地測試假設,直到發現期望值與實際情況之間存在差異。交互式調試器是解決該問題的強大工具。
- 你沒有收到任何錯誤,但是值不正確。在這里,通常最好將其轉換為第一個問題,方法是在出現錯誤值時使用
stop()
引發錯誤。 - 所有值都是正確的,但是在你期望的時候它們不會更新。這是最具挑戰性的問題,因為它是 Shiny 所特有的,因此你無法利用現有的 R 調試技能。
當出現這些情況時,這很令人沮喪,但是你可以將它們變成練習調試技能的機會。
在下一部分中,我們將介紹另一種重要的技術,以最小的可重現性為例。如果你陷入困境并需要別人的幫助,創建一個最小的示例至關重要。但是,在調試自己的代碼時,創建最少的示例也是一項極為重要的技能。通常,我們有很多可以正常運行的代碼,還有很少量的會引起問題的代碼。如果我們可以通過刪除有效的代碼來縮小問題代碼的范圍,則可以更快地迭代解決方案。這是我一直使用的技術。
閱讀錯誤追蹤
每個錯誤都伴有一個追溯或調用堆棧,它實際上是追溯導致該錯誤的調用堆棧。例如,采取以下簡單的調用順序:f()
調用 g()
調用 h()
,而 h()
使用了乘法操作。
f <- function(x) g(x)
g <- function(x) h(x)
h <- function(x) x * 2
如果代碼報錯了,如下:
f("a")
#> Error in x * 2: non-numeric argument to binary operator
調用堆棧是導致問題的調用順序:
1: f("a")
2: g(x)
3: h(x)
您可能已經熟悉 R 中的 traceback()
。此功能可以在發生錯誤之后以交互方式運行以查看導致錯誤的調用順序。我們無法在 Shiny 中使用此功能,因為我們無法在應用運行時以交互方式運行代碼,而是 Shiny 會自動為我們打印調用堆棧。例如,以使用我上面定義的 f()
函數的簡單應用程序為例:
library(shiny)
ui <- fluidPage(
selectInput("n", "N", 1:10),
plotOutput("plot")
)
server <- function(input, output, session) {
output$plot <- renderPlot({
n <- f(input$n)
plot(head(cars, n))
})
}
shinyApp(ui, server)
如果運行此代碼,我們會在應用程序中看到錯誤消息,并在控制臺中看到調用堆棧:
Warning: Error in *: non-numeric argument to binary operator
173: g [~/.active-rstudio-document#4]
172: f [~/.active-rstudio-document#3]
171: renderPlot [~/.active-rstudio-document#13]
169: func
129: drawPlot
115: <reactive:plotObj>
99: drawReactive
86: origRenderFunc
85: output$plot
5: runApp
3: print.shiny.appobj
1: source
Shiny 將一些其他調用添加到調用堆棧中。要了解發生了什么,請先將其上下顛倒,這樣我們就可以按顯示順序查看調用順序:
Warning: Error in *: non-numeric argument to binary operator
1: source
3: print.shiny.appobj
5: runApp
85: output$plot
86: origRenderFunc
99: drawReactive
115: <reactive:plotObj>
129: drawPlot
169: func
171: renderPlot [~/.active-rstudio-document#13]
172: f [~/.active-rstudio-document#3]
173: g [~/.active-rstudio-document#4]
調用棧包含三個基本部分:
-
前幾個調用會啟動應用程序。
1: source 3: print.shiny.appobj 5: runApp
-
接下來,我們看到一些內部 Shiny 的代碼負責調用反應式表達式。
85: output$plot 86: origRenderFunc 99: drawReactive 115: <reactive:plotObj> 129: drawPlot 169: func
在這里,發現 output$plot
非常重要-它告訴我們哪個反應堆導致了錯誤。接下來的幾個功能是內部的,我們可以忽略它們。
-
最后,在最底部,我們將看到編寫的代碼函數。
171: renderPlot [~/.active-rstudio-document#13] 172: f [~/.active-rstudio-document#3] 173: g [~/.active-rstudio-document#4]
如果你在應用程序中遇到錯誤但沒有看到回溯,請確保你正在使用 Cmd/Ctrl + Shift + Enter(或者如果不在 RStudio 中,請調用 runApp()
)運行該應用程序,并且已保存你正在運行的文件。其他運行應用程序的方式并不總是捕獲進行調用堆棧所需的信息。
交互式調試器
找到錯誤的根源并弄清楚是什么原因后,你可以使用的最強大的工具是交互式調試器。調試器會暫停執行,并為你提供一個交互式 R 控制臺,你可以在其中運行任何代碼以找出問題所在。有兩種啟動調試器的方法:
-
在你的源代碼中添加對
browser()
的調用。這是啟動交互式調試器的標準 R 方法,并且可以在你運行狀況良好的情況下使用。browser()
的另一個優點是,由于它是 R 代碼,因此可以通過將其與 if 語句結合使用來使其成為有條件的:if (input$debug) { browser() }
通過單擊行號左側來添加 RStudio 斷點。你可以通過單擊紅色圓圈刪除斷點。斷點的優點在于它們不是代碼,因此你不必擔心將它們意外地插入到版本控制系統中。
如果你使用的是 RStudio,則在調試器中將顯示下圖中的工具欄。工具欄是記住現在可用的調試命令的簡便方法。它們也可以在 RStudio 之外使用;你只需要記住一個字母命令即可激活它們。三個最有用的命令是:
- 下一步
n
:執行函數的下一步。請注意,如果你有一個名為n
的變量,則需要使用print(n)
來顯示其值。 - 繼續
c
:離開交互式調試并繼續正常執行該函數。如果你已修正錯誤狀態并希望檢查函數是否正常運行,這將非常有用。 - 停止
Q
:停止調試,終止功能,然后返回全局工作區。確定問題出在哪里后,就可以使用它,并準備好進行修復并重新加載代碼。
將不對的值轉換為錯誤
如果你遇到的問題不是錯誤,建議將其轉換為錯誤,以便更輕松地查找問題。讀者可以通過編寫自己的調用 stop()
的代碼來實現。
調試響應式編程
使用 message()
向控制臺發出消息,這樣我們可以準確查看代碼何時運行。你可以將消息放入任何反應式表達式或輸出中,只需確保它們不是最后一行(否則它們將成為反應表達式或輸出使用的值)。
如果要輸出多個值,則 glue 包可能會有用;它使創建內容豐富的文本字符串變得非常容易。
如果問題是反應式事件未按預期觸發,你可能需要查找反應式日志,reactlog 包可能會很有用。