Debug in R #4: Post-Mortem Debugging


エラーが出ちゃったあとにデバッグする

今までのやり方は、前もってデバッグを仕込んでおいて、動作を確認する、という感じなので、デバッグすべき場所が分かっていることが前提だった。だけど多くの場合、「エラーどこで起こってんのー」と、死んだあと(post-morten)にデバッグしたいことが多々ある。

デフォルトではRはエラーがどこで起こったか非常に分かりづらいんだけど、post-morten debuggingが簡単にできる方法が幾つかある。

  • tracebackでエラーまでのコールスタックをたどる。
  • option(error)をいじってエラーをデバッグのトリガーにする。
  • 上と似てるけど、dump.framedebuggerを組み合わせて、天国からデバッグする。
  • おまけ

1. tracebackでエラーをたどる。

デバッグというより、エラーが起こった場所を特定するのに使える。個人的にはよく使う。

関数定義

a <- function() stop() # aはエラーを起こす。
for (w in 10:2) { # b...jを定義。
	eval(parse(text=paste(letters[w], "<- function()", letters[w-1], "()")))
}

実行例


> j # jの定義はこんな感じ。これがaまで連鎖する。
function() i ()

> j()
 以下にエラー a() : # 普通だとこれだけ表示しておしまい。

> traceback() # tracebackしてみると、呼出履歴が表示される。
11: stop() at <text>#1
10: a() at <text>#1
9: b() at <text>#1
8: c() at <text>#1
7: d() at <text>#1
6: e() at <text>#1
5: f() at <text>#1
4: g() at <text>#1
3: h() at <text>#1
2: i() at <text>#1
1: j()

上で#1というところは、スクリプトをsource()した場合には、スクリプト上の行番号が表示される(こともある)。

なんかあんま役に立たなさそうに見えるので、どういう時に役立つかと言いますと、

スクリプトファイル

## testrun03.R
##
f <- function() {
	a <- 10
	sample(10, a)
	a <- 1
	sample(10, a)
	a <- -1
	sample(10, a) # サイズに負の値を渡してるのでエラーが起きる
}

f()

実行例

> source("testrun03.R") # 読み込み
 以下にエラー sample(10, a) :  'size' 引数が不正です # これだけじゃ、どこでエラーが起こったのかわからないし。

> traceback() # tracebackでコールスタック表示
5: sample(10, a) at testrun03.R#9 # 行番号が表示されてる。
4: f()
3: eval.with.vis(expr, envir, enclos)
2: eval.with.vis(ei, envir)
1: source("testrun03.R")

とか。まあエラーが起こって困ったときには、とりあえずtracebackしてみるといいですよ。

2. option(error)で、エラーをデバッグのトリガーにする

エラーしたらその時にbrowse(前の記事、Debug in #1、#2を参考に)出来ればかなりいいんじゃないかと思う。実はできる。そのためには、options(error)を指定する。なお、以下のエラーでデバッガ起動を止めたくなったら、options(error = NULL)で。

関数定義

f <- function() {
	a <- 1
	b <- 2
	print(3)
	c <- z # エラーが起きる。
	print(4)
}

g <- function() f()

実行例

> options(error = browser) # optionsのerrorをbrowserに設定。
> g() # 関数呼び出し
[1] 3
 以下にエラー f() :  オブジェクト 'z' がありません 
Called from: f()

Browse[1]> where # コールスタック表示
where 1: f()
where 2: g()

Browse[1]> ls() # だけど、aとかbとかない。
[1] "f" "g"
Browse[1]> a # ない。
 wrapup 中にエラーが起こりました  オブジェクト 'a' がありません 
Browse[1]> environment() # ここはグローバル環境。
<environment: R_GlobalEnv>
Browse[1]> c

というように、browserが起動することはするんだけど、グローバル環境で呼び出されてしまうので、デバッグの役にたたない。

エラーが起こったときにその環境でごにょごにょするには、browserじゃなくて、recoverを使う。

実行例


> options(error=recover) # errorが起きたらrecoverを起動するように設定。
> g() # 関数呼び出し。エラーが起こる。
[1] 3
 以下にエラー f() :  オブジェクト 'z' がありません 

Enter a frame number, or 0 to exit  # コールスタックのどこに入るか、またはやめるか選ぶ。

1: g()
2: f()

 選択: 2 # エラーを起こしてるf()に入ろう。
Called from: top level 
Browse[1]> ls() # 変数リストを見ると、今度はaもbもある。
[1] "a" "b"
Browse[1]> a # 確かにある。
[1] 1
Browse[1]> c # recoverの場合は、cは「以降を実行」ではなくて「フレーム選択に戻る」

Enter a frame number, or 0 to exit   

1: g()
2: f()

 選択: 1 # 一応g()にも入ってみる。
Called from: top level 
Browse[1]> ls() # 特に何も無い。
character(0)
Browse[1]> c # さよなら

Enter a frame number, or 0 to exit   

1: g()
2: f()

 選択: 0 # 終了。

という感じで、エラーが起こった時点での変数の中身とかを、エラーが起こった後で確認することが出来る。

天国からデバッグ

上の方法は、死に際にデバッグしてる感じがする。Rには、死んでからデバッグする方法もある。これも、options(error)で。

実行例

> ls() # グローバル環境の変数リスト。
[1] "f" "g"

> options(error = dump.frames) # error に dump.framesを設定。

> g() # 関数呼び出し。
[1] 3
 以下にエラー f() :  オブジェクト 'z' がありません # 普通にエラーが起きて、普通に終了してる、

> ls() # と思いきや、グローバル環境の変数リストを見てみると、last.dumpというのが出来てる。
[1] "f"         "g"         "last.dump"

> debugger(last.dump) # debuggerでこれを除くと、上のrecoverと同じことを後からできる。以下は同じなので解説略。
 メッセージ:    以下にエラー f() :  オブジェクト 'z' がありません 
 利用可能な環境は以下の呼び出しを含んでいました: 
1: g()
2: f()

 環境ナンバーを入力してください.0 を入力すると終了します   
 選択: 2
 以下の呼び出しに伴う環境でブラウジング:  
   f()
Called from: debugger.look(ind)
Browse[1]> ls()
[1] "a" "b"
Browse[1]> Q

というように、コールスタックを変数にダンプしてくれる。実際には、変数じゃなくてファイルにダンプすることも可能。?dump.framesにやり方が書いてある。先生が教えてくれたサンプルコードにしょうもないバグが有ったときには、この方法でフレームダンプをファイルに書きだして、メールに添付して送ってあげると喜ばれると思う。

おまけ:option(error)って何なん?

ここまで来ると予想付きそうだけど、options(error=XXX)XXXには、「任意の関数もしくは未評価の式」が渡せる。例えば、

実行例

> options(error = quote(cat("あほちゃう?"))) # error時に評価される式を設定。
> g() # 関数呼び出し。
[1] 3
 以下にエラー f() :  オブジェクト 'z' がありません 
あほちゃう?
> xxx
 エラー:  オブジェクト 'xxx' がありません 
あほちゃう?

このoptions設定を同僚の.Rprofileに書き加えておくといいと思う。

About these ads

2 thoughts on “Debug in R #4: Post-Mortem Debugging

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s