Rのデバッグ用関数群
人間がコードを書く以上、コーディングにバグはつきものです。Rには原因追求のための関数群が標準で用意されており、統合開発環境に頼らなくてもそこそこは効率よくデバッグができるようになっています。
Rを本格的に使っている人々は、RStudioやVSC/R Extension/R Debuggerのような統合開発環境を利用していおり、GUIを使ったデバッグに慣れていると思いますが、対話型インターフェイスのCUIで使えるデバッグ関数群も知っておくと便利なことがあります1。存在ぐらいは覚えておきましょう。
1 ブレークポイントを仕掛ける
プログラム中のある箇所の状態やある部分の処理フローの流れを追いかけたいときは、ブレークポイントを仕掛けるのが定番です。コード中にbrowser
関数を埋め込む方法、trace
やdebug
やdebugonce
で関数にブレークポイントを仕掛ける方法の3種類があります。手軽なのはbrowser()
と書いてしまうことなので、まずはそこから慣れていきましょう。
1.1
browser
関数
最初に覚えるべき関数がbrowser
関数です。実行すると、プログラムの実行がそこで中断され、変数の値を確認したり、変数に値を代入したり、一行づつプログラムを実行していったり(ステップ実行)できる、デバッグモードに入ることができます。
例えば、
function(x){
(browser()
<- x + 1
x print(x)
1) })(
と埋め込めば、関数内でx
が定義されたところで実行が中断されます。挿入したところがブレークポイントになるわけです。
中断された後は、
コマンド | 挙動 |
---|---|
n | ステップ・オーバー(一文づつ実行,関数の中は追いかけない) |
s | ステップ・イン(一文づつ実行,関数の中も追いかける) |
c | 再開(デバッグモードを抜けて実行を再開) |
f | 再開(ステップ実行で入った関数やループを抜ける) |
Q | 終了(プログラムの実行自体を終了) |
where | トレースバックを表示 |
help | コマンドの説明を表示 |
と言うコマンドを入れるか、Rの命令を入力します。ENTERキーは、options(browserNLdisabled=TRUE)
で無効にできますが、デフォルトでは最初は再開で、ステップ・オーバー実行後はステップ・オーバー、ステップ・イン実行後はステップ・インになります。なお、c
とf
は実用上の違いは無い2こt、? browser
で出てくるドキュメントにあるr
コマンドは機能しない3ので注意してください。また、デバッグモードに入ったとき、次に実行する文がdebug:
の後に表示されていないときのn
とs
は無視されます。
1.1.1
browser
関数を使ってみる
習うより慣れろ系なので、上述の例を実際に動かして見ましょう。
Called from: (function(x) {
browser()
x <- x + 1
print(x)
})(1)
Browse[1]>
とコマンド・プロンプトが表示されるので、コマンドか命令を入れていきます。ステップ・オーバーしていくと
Browse[1]> n
#3 の debug: x <- x + 1
Browse[2]>
#4 の debug: print(x)
Browse[2]>
[1] 2
となり、ステップ・インしていくと
e[1]> s
#3 の debug: x <- x + 1
Browse[2]>
#4 の debug: print(x)
Browse[2]>
debugging in: print(x)
debug: UseMethod("print")
Browse[3]>
debugging in: print.default(x)
debug: {
args <- pairlist(digits = digits, quote = quote, na.print = na.print,
print.gap = print.gap, right = right, max = max, width = width,
useSource = useSource, ...)
missings <- c(missing(digits), missing(quote), missing(na.print),
missing(print.gap), missing(right), missing(max), missing(width),
missing(useSource))
.Internal(print.default(x, args, missings))
}
print
関数の中まで追跡してくれます。ステップインした関数の中でf
かc
を押すと、関数を抜けて、関数の次の文に飛びます。
変数名をいれれば値を確認できますし、代入演算子<-
で変数に異常値を代入したり、フォーマット付きで値を確認したり、plot
したりと、Rの命令が使えるのは便利です。コンパイル言語のデバッガーと比較して融通が利きます。
1.1.2
デバッグモードに入る条件を引数expr
で指定する
条件付きにデバッグモードに入れます。以下のように引数expr
を指定したら、x <= 0
がFALSE
なのでデバッグモードに入りません。
function(x){
(browser(expr = x <= 0)
<- x + 1
x print(x)
1) })(
1.1.3
引数skipCalls
ほかにtext
とcondition
とskipCalls
を指定することができます。
function(x){
(browser(text = "example", condition = 1, skipCalls=2)
<- x + 1
x print(x)
1) })(
上を実行するとskipCalls=2
の効果でCalled from:
が変わっているのが分かります。
1.1.4
引数text
とcondition
複数のブレークポイントを管理する統合開発環境用だそうですが、text
とcondition
で指定した値をbrowserText
関数とbrowserCondition
関数でとることができます。
Called from: top level
Browse[1]> browserText()
[1] "example"
Browse[1]> browserCondition()
[1] 1
Browse[1]>
1.2
trace
関数
trace
関数とbrowser
関数を組み合わせて、関数にブレークポイントを仕掛けることができます。
<- function(x){
fn <- -1/2 + sqrt(3)/2i
z ^x
z
}trace(fn, browser, at=3)
fn(3)
この例では、fn
の3行目になるz^x
の実行前にブレークポイントを仕掛けています。
デバッグモードに入ったあとは、browser
関数を呼んでいるだけに、browser
関数と同様です。
芸達者な関数で、exit
で関数終了時に呼び出す関数を指定できるほか4、print=FALSE
で表示を抑制したり、edit=TRUE
でtraceしている関数を実行前にテキストエディタを開いて編集したりできます。
総称関数(e.g. summary
)をデバッグする場合は、引数signature
でクラス名を指定します。また、パッケージ内の関数のように現在の環境と異なる環境にある関数の場合は引数where
で環境を指定します。
untrace(fn)
なお、デバッグ完了後は、次回実行時もデバッグモードに入らないように、untrace
する必要があります。untrace
にもtrace
につけたsignature
とwhere
は必要です。
1.2.1
tracingState
関数
tracingState(FALSE)
とすると、あらゆる箇所のtrace
が基本的に動かなくなります5。tracingState(TRUE)
で有効になります。
1.3 debug
,
debugonce
関数
debug
関数とdebugonce
関数はtrace
関数に似た用途に用います。debug
を使うとundebug
をしないといけないので、1度だけデバッグモードに入れるdebugonce
を使ってみましょう。
debugonce(fn)
fn(3)
browser
関数と同様の引数text
とcondition
と、trace
関数と同様のsignature
をもてます。isdebugged
でブレークポイントが仕掛けられているか確認することができ、debuggingState(FALSE)
で無効にすることができます。もちろんTRUE
を指定すれば有効化です。
2 エラーや警告が出た状態を把握する
コードを書いているときはエラーが出てもどこが問題かだいたい想像がついているわけですが、データセットを変えるなどしてエラーを出した場合などで、直観的に状況を把握できないときがあります。
2.1
traceback
関数
コードが複雑になってくると、例えばsource
関数で外部ファイルを実行するとき、エラーが出て停止したときにどこで停止したか分からないときがあります。そういうときにtraceback()
と入力すると、直前のエラーが生じた関数とその中の位置、エラーが生じた関数を呼んでいる関数を表示してくれます。
assign(".Traceback", NULL, "package:base") # 説明のためframes/stackをクリア
<- function(x) fn2(x)
fn1 <- function(x) fn3(x)
fn2 <- function(x) stop("an example error!")
fn3 fn1(1)
traceback()
入れ籠構造になっているわけですが、しっかり関数名と行番号を追いかけてくれます。
4: stop("an example error!") at #1
3: fn3(x) at #1
2: fn2(x) at #1
1: fn1(1)
2.1.1 警告でも中断する
warningでも中断してtraceback()
したいときは、options(warn = 2)
としてください。デフォルトはoptions(warn = 0)
です。
2.2
options(error = recover)
グローバルオプションを変更することで、エラーが出る直前でデバッグモードに入って状態を確認できます。
<- function(x){
fn2 print("fn2 begin")
stop("an example error!")
print("fn2 end")
}
<- function(x){
fn1 print("fn1 begin")
fn2(x)
print("fn1 end")
}
options(error = recover)
fn1(1)
エラーになった関数と、それを呼び出している関数のどちらかで起動できますが、ステップ実行をすると必然的にすぐエラーになって止まります。
> fn1(1)
[1] "fn1 begin"
[1] "fn2 begin"
fn2(x) でエラー: an example error!
Enter a frame number, or 0 to exit
1: fn1(1)
2: #3: fn2(x)
Selection:
0
を入力して終了、fn1(1)
を呼ぶところ、fn2(x)
の3行目と選択肢が出ていますね。
options(error = NULL)
で解除できます。
2.3
options(error = dump.frames)
→
debugger(last.dump)
グローバルオプションを変更して、ダンプ情報を取得することで、エラーが出た直後の状態を確認することができます。
<- function(x){
fn2 print("fn2 begin")
stop("an example error!")
print("fn2 end")
}
<- function(x){
fn1 print("fn1 begin")
fn2(x)
print("fn1 end")
}
options(error = dump.frames)
fn1(1)
使ったことがなかったので、こんな仕掛けが・・・と驚いています。
[1] "fn1 begin"
[1] "fn2 begin"
fn2(x) でエラー: an example error!
> ls()
[1] "fn1" "fn2" "last.dump"
> debugger(last.dump)
メッセージ: fn2(x) でエラー: an example error!
利用可能な環境は以下の呼び出しを含んでいました:
1: fn1(1)
2: #3: fn2(x)
3: #3: stop("an example error!")
環境ナンバーを入力してください。0 を入力すると終了します Selection: 0
3 まとめ
統合開発環境を使わない/使えないことが前提ですが、browser()
を挟んでデバッグモードに入るだけで、だいぶ効率が上がると思います。後はtraceback()
で十分で、かなりはまらないと細かく引数やオプションをつけていくことはないと思いますが、何かの不幸で統合開発環境が使えないときにどん詰まりのときは、気分転換に試してみましょう。
例えば、R Markdownのファイルにブレークポイントを設定できない統合開発環境でも、R Markdownの中のRのコードの中に
browser()
と書いておけば、そこでデバッグモードに入ることができます。↩︎c
とf
を押した後、ループや関数が終了する前にbrowser()
の行が来た場合に、c
の場合はデバッグモードに入りなおすのでスタックトレースが表示される一方で、f
は入ったままなので表示されないと言う違いはなくもないのですが。↩︎ソースコードを確認したところ未実装。↩︎
例えば
recover
を指定すると、終了するか継続するか選択できます。↩︎edit=TRUE
をつけておくと、なぜか編集は実行されます。コードは実行されません。↩︎