【譯文】使用Rstudio除錯程式碼(debug)

錢亦欣發表於2017-08-27

作者 Jonathan McPherson

譯者 錢亦欣

引言

在R語言中dubug是個廣泛討論的話題,本文將聚焦於Rstudio內整合的debug工具。如果你想了解更多這個領域的內容,請參考下面這篇Hadley Wickham的文章。 Debugging, condition handling, and defensive programming

大體上說,debug是被設計開幫助你發現程式碼中的問題,為了達成這個目的,你需要:

  1. 執行程式碼
  2. 在你懷疑有問題的地方停止執行
  3. 在問題點逐步檢查程式碼

我們將詳細討論後面兩個步驟

進入除錯模式(stopping)

為進入除錯模式,我們要告知R*何時*停止執行程式碼。R中沒有暫停的功能 (並且大部分計算速度太快,就算有這麼個特性也沒啥用),所以你需要在啟動計算之前先設定好暫定的點。

有好幾種方法可以實現這樣的功能,你需要根據情況選擇最合適的方法。

1. 在某一行停止

編輯斷點

最常用同時也是最簡單的方法是在程式碼的某一行設定一個斷點。在Rstudio的程式碼編輯窗裡,在行號的左側點選一下,或者按下Shift+F9就能在改行設定一個斷點。

img

我們將其命名為"編輯器斷點"。編輯器斷點即點即用,並且不需要改動程式碼(和下文提及的瀏覽器斷點不同)。它的作用機理是在R的函式物件內插入一些程式碼,這樣含有這些程式碼的函式就會在環境窗顯示一個紅點,表名它們含有斷點。

img

如果這個函式不存在(比如你忘記source了),或者函式與編輯器的內容不一致(比如在最後一次source後又改動了指令碼),這個斷點就會被延後。

img

上圖的空心紅點就表名了這一情況,大部分情況下,你再次source一下就能解決這個問題了。

瀏覽器斷點

R中的函式 browser() 會中斷程式碼的執行並調出環境瀏覽器。你可以在任意需要設定斷點的地方放置 broswer() 來進行除錯。下面的例子就是在一個函式要返回 TRUE 時終止程式碼。

img

和編輯器斷點不同,broswer() 本身就是程式碼的一部分,所以你需要改動你的指令碼來實現斷點。它調出的環境瀏覽器可是試做最底層的除錯工具,所有其他的除錯方法都會用到它。broswer() 函式不需要其他特殊工具,可以在任何編輯器斷點不起作用的時候使用。

broswer() 也常用來設定條件斷點。舉個例子,如果你要在上百次的迴圈後啟動除錯模式:

for (i in 1:1024) {
  start_work()
  if (i == 512)
    browser()
  finish_work()
}

在函式執行時停止

針對你想要除錯的程式碼有相應的 .R 檔案,使用編輯器斷點或 broswer() 函式可以很方便地除錯。但有時,你不需要原始檔也能除錯程式碼。

在這個情況下,你可以給那個函式立個flag,相當於在函式被宣告前就設定好斷點。它不會改變函式本身,卻能在函式執行時立馬啟動除錯狀態。

使用R函式 debugonce() 可以給函式立除錯的flag。如果你想除錯tdevtools::install():

> debugonce(devtools::install)

debugonce() 會設定一個一次性斷點,也就是說這個函式在下次執行時而不是在執行之後進入除錯模式。如果你想在一個函式每次執行時都執行除錯,就對那個函式呼叫 debug() 函式,當你不想每次都debug了就呼叫 undebug() 。當然,我並不推薦這個模式,它可能讓你陷入無限除錯的死迴圈。

在錯誤發生時停止

如果你在診斷一個特定的錯誤,你可以讓Rstudio在這個錯誤發生的時候停下來。點選Debug -> On Error 並把這個值從 "Error Inspector" 改為 "Break in Code"。

img

為了讓偵錯程式在錯誤發生的任意時間地點都能啟動,Rstudio 會在你的程式碼不在棧堆的時候不啟動除錯模式。如果你發現它漏掉了你想捕捉的錯誤,請到Tools -> Global Options把 "Use debug error handler only when my code contains errors" 前面的勾去掉。

如果你想一有錯誤就啟動除錯,請按下方程式碼修改配置:

> options(error = browser())

這個命令會修改 Rsutido 的預設配置,所有錯誤都會變得格外惱人,所以當你結束除錯記得要修改回之前的設定 options(error = NULL)。

使用偵錯程式

一旦你的程式碼停止執行了,IDE 會自動進入除錯模式,提供給你多種工具來檢測並調整你的程式,讓我們來逐一介紹。

img

環境窗

通常你在用R寫程式碼時都是在和全域性環境互動,它包括了一系列的函式、資料和值等物件。當你進入除錯模式,IDE會把工作環境轉變到函式內部,你在環境窗看到的物件其實都是屬於函式的內部環境,這是你在控制檯輸入的命令都是在函式內部執行的。

img

本案例中,灰色的值都是沒有執行的函式引數。

區域性環境物件列表的上方是你當前所處環境棧堆的下拉選單。它顯示了你當前所屬環境的繼承關係。

img

大部分時間它只會包括當前函式,全域性環境和一些包的名稱空間。但如果你正在寫一個包或者巢狀函式,它會包含有額外的入口。你可以點選下拉選單裡的任何物件來檢視對應環境的內容。

如果你看這段話有些糊里糊塗,沒關係大部分時間你無須瞭解環境棧堆。如果你想學習這方面的知識看男生的Environments就可以。

追溯 (呼叫棧堆)

棧堆回溯與環境窗共用空間,它會展示程式碼是如何執行到當前節點的,從第一個被呼叫 (棧底)函式到最後一個 (棧頂)。

img

在大部分語言中這個過程叫呼叫棧堆,Rstudio 將其稱作追溯,用 traceback() 就可以執行。

你可以在呼叫棧堆的下拉框裡選擇任何函式來檢查環境和函式程式碼的執行位點。請注意此時的選擇不會改編控制檯的現有環境,如果你需要這麼做,使用下方介紹的 recover() 函式。

大部分時間你只對自己的程式碼的棧堆感興趣,所以 Rstudio 預設隱藏了內部函式 (就是那些沒有 .R 檔案的函式) 是的呼叫棧堆的過程更乾淨。勾選上View Internals 可以顯示內部函式,比如在 tryCatch 程式碼塊內部,你可以展開來看 R 的異常處理函式 (灰色部分) 。 img

程式碼窗

程式碼窗顯示了你當前正在執行的函式,將被執行的程式碼行會用黃色高亮。

img

如果 Rstudio 無法找到和函式對應的 .R 檔案,它會將代買呈現在 Source Viewer 裡。這種情況會在 .R 檔案不存在或者函式物件和 .R 檔案中的定義不一致時出現。

img

控制檯

img

在除錯模式下,你會發現 R 的控制檯有兩大變化,第一是提示符變了:

Browse[1]> 

這個提示符代表你在 R 的環境瀏覽器中。

除錯模式的控制檯支援普通模式的所有命令,只有一點點不同:

  1. 命令是在當前環境被執行的,也就是說如果你的函式有個變數 x ,那麼在提示符後輸入 x 就會顯示對應的值 (使用 ls() 可以檢視所有變數)。
  2. 在控制檯按下Enter鍵可以執行當前程式碼行並跑到下一行,對於逐行執行而言很方便。
  3. 提供了一系列除錯命令 (詳見下文)

如果你想在控控制檯中和不同的函式互動,使用 recover() 來列出執行中的函式,然後從中選擇就可以。

第二個變化是頂部的工具欄變了:

img

工具欄提供的很多按鈕讓你方便地發出各類除錯指令 (詳見下表) ,這和直接在控制檯輸入那些命令並無不同,所以有了它你能省下不少時間。

除錯命令

enter image description here 所有命令的說明都在 browser() 的幫助文件裡,直接檢視就行。

特殊情況

一般來說你都會直接除錯獨立的函式和指令碼,但如果你除錯的程式碼是一個大專案的一部分,這裡有4種情況值得注意:

除錯外部函式

你或許已經注意到 Rstudio (還有R本身,通過 setBreakpoint()) 在設定斷點時會改動函式,那麼在函式 外部 設定斷點會怎樣呢?

最常見的用例是在執行 source() 時停下,以檢查 R 指令碼的執行狀況。R 內建的 source() 並沒有這個功能,好在 Rstudio 有名為 debugSource() 的命令來實現這一功能。如果一個檔案在函式外有斷點,你需要用 debugSource() 來代替 source() 。Rstudio中的 Source命令會自動幫你完成這個替換。

值得注意的是,debugSource 函式是非遞迴的。如果你在 file2.R 中 呼叫了 source("file1.R"),然後執行了 debugSource("file2.R"),你會在 file2.R 中開啟除錯模式,file1.R 則不會。這能是的你的程式碼在除錯時保持獨立性。

在包內除錯

斷點也可以直接設立在包內的程式碼中,主要的差別在於你需要把包更新到最新版本,否則 Rstudio 會發出 warning。

為了高效地在包內除錯,你要確定那個包開啟了 --with-keep.source 選項。這個選項對於新的包而言預設開啟,不然就在 Tools -> Project Options -> Build Tools 內設定。

img

當一個斷點被設定在指令碼中的一個包內,如果這個包沒載入,Rstudio 會自動忽略這個斷點。

在 Shiny 應用上除錯

Shiny 應用除錯起來有些麻煩,因為無法事先設定斷點,哪些需要被除錯的函式只有在應用被執行後才存在。因此 Shiny 應用中只有在 shinyServer() 函式內的斷點才會生效,ui.R, global.R 或 Shiny 中使用的其他 .R 檔案中都無法設定斷點。

img

最後,請記住 Shiny 的棧堆中有許多基礎元件,在除錯到你的程式碼之前,確實有很多元件要執行。

在 RMarkdown 文件中除錯

斷點在 RMarkdown 文件的程式碼塊中不會起作用,所以你需要用 broswer() 函式來終止程式碼塊。

預設地,Rstudio 會啟用一個獨立的 R 程式來渲染你的 RMarkdown 文件。這麼做好處不少:將文件和現有的 R 程式獨立使得它可以重複生產,並且讓 UI 和控制檯在文件渲染時保持響應。然而,調式模式只能在 R 的主程式中啟動,所以你需要用直接對你的檔案呼叫 rmarkdown::render() 函式。

> rmarkdown::render("~/mydocs/doc.Rmd") 

最後,由於RMarkdown 的程式碼塊不包含 source references ,大部分除錯模式的視覺化工具都無法使用,例如在編輯器裡你看不到高亮的當前行,大部分的除錯工作就只能在控制檯裡完成。

更多資料

Introduction to Debugging in R (video, about 11 minutes)

R function documentation: browser(), debug(), debugonce(), options(error = …), recover(), setBreakpoint(), traceback().

Debugging, condition handling, and defensive programming by Hadley Wickham

Source References (R Journal article), by Duncan Murdoch

R語言-除錯程式碼

原文連結:https://support.rstudio.com/hc/en-us/articles/205612627-Debugging-with-RStudio

相關文章