Visual LISP でデバッグ

Visual LISP でデバッグしよう

プログラムを書いていて、一度の訂正もなく完成することはまずありません。必ず誤りが紛れ込んでいるものであり、間違っているところを探し出して正しく訂正する必要があるものです。エラーが出たときは即座に原因が頭に浮かぶこともありますが、まったく見当がつかないときもあり、そんな時はすこし絶望的な気持ちになります。しかし、ひとつひとつ確認していけば必ず誤りは見つけ出せるものです。ここでは、絶望的な気分の時に思い出して即座に次ぎに進んでいけるよう Visual LISP 上での有効なデバッグの手順を整理しておきます。

Visual LISP は今となっては少し古くささを感じるインターフェースになっていますが、プログラム開発に必要なデバッグ支援機能は一通りそろっています。そして、これを利用しないと本当に手探りのデバッグとなりますので効率が上がりません。そのような状況ではおもしろみがあるプログラムはとうてい作れません。AutoLISP プログラミングは、外部のエディタではなく Visual LISP を使うことを強くお勧めします。

ソースコードのロード時のエラー

ソースコードのロード時に発生するエラーは次のようなものが上げられます。

  1. 括弧の対応が取れていない
  2. setq の引数が偶数個になるはずが奇数個である、関数定義 defun の中身がない、などといった明白な構文エラー
  3. ロード時実行されるトップレベルのコードで必要な関数が定義されていない
  4. それ以外の実行時エラー

1 と 2 は Visual LISP の【チェック】機能に掛けます。【チェック】を実行すると、【<作成出力>】ウィンドウが開き情報が表示されます。エラーがある場合は、水色にハイライトされた部分をダブルクリックするとテキストエディタの該当の部分が選択表示されます。

Visual LISP Tool Bar
ボタン ショートカット メニュー 説明
Visual LISP 選択したコードをチェック CTRL+SHIFT+C 【ツール】>【選択したコードをチェック】 選択範囲をチェックします。
Visual LISP エディタ内のテキストをチェック CTRL+ALT+C 【ツール】>【エディタ内のテキストをチェック】 エディタ内の全てをチェックします。
Visual LISP チェック

水色の部分をダブルクリックしてソースコードを表示させたら、エディタのショートカット CTRL+SHIFT+[ (右括弧) や CTRL+SHIFT+] (左括弧) で括弧の対応を確認し、整形を行いながらソースコードを整理して正しく修正します。

Visual LISP Tool Bar
ボタン ショートカット メニュー 説明
Visual LISP 選択範囲内のコードを整形 CTRL+SHIFT+F 【ツール】>【選択範囲内のコードを整形】 選択範囲を整形します。
Visual LISP エディタ内のコードを整形 CTRL+ALT+F 【ツール】>【エディタ内のコードを整形】 エディタ内の全てを整形します。

3 については、ロード時に【コンソール】にエラーが表示されますので、直ぐに原因がわかります。

; error: no function definition: INVERCE

関数名の綴り間違いであったり、上で言われたとおりに関数の定義がまだ済んでいない可能性があります。プロジェクト機能を使っている場合は、ソースコードをロードする順番を吟味してリストの項目の上下関係を確認してください。プロジェクトを使っていない場合は、必要な関数を先にロードしておきます。ソースコードのトップレベルのコードはロード時に実行されるので、その実行までにそこで必要な関数が定義されていないといけません。

ケースとしてはだいたい 3 までが多いと思いますが、その他のロード時実行時エラーなどのエラーが【コンソール】に出たり、どうにも原因がつかめない 4 の「それ以外」という場合があります。こういうケースでは次で紹介する実行時エラーと同じようにロード時の実行状況をステップ実行などして確認するわけですが、ブレークポイントを置く代わりに【デバッグ】メニューから【1 回で停止】にチェックを入れて、ソースコードのトップレベルの先頭からの実行を先頭で一時停止することができます。【1 回で停止】という日本語は意味が分かりませんが、英語メニューの STOP ONCE の直訳では「すぐに停止」で、言い換えますと「最初のステップで一時停止」とか「先頭で一時停止」という意味です。もちろん、トップレベルのコードの適当な位置にブレークポイントを置いて、おかしな所を絞っていく方法も使えます。

実行時のエラー

ロードが無事済んで実際にプログラムをテストする段階での実行時エラーでも、ロード時でも問題になった関数の未定義が新たに顕在化する場合があります。同じように関数名の綴りや定義がきちんとできているか確認してください。

実行時エラーに対応するために、Visual LISP の【デバッグ】メニューの【エラーでブレーク】オプションにチェックを入れておくことをお勧めします。このモードをオンにすることによってエラーが起こった箇所でプログラムの実行が一時停止し、ブレークループに入ることでその時の変数の値などを確認できるからです。

さて、実行時エラーが起こると次の様にその時のエラー内容が表示されてプログラムが一時停止します。プロンプトがブレークループのものに変化します。

_$ (inverse "A")
; error: bad argument type: numberp: "A"
_1$

まず、どこでエラーが起こったか確認します。CTRL+SHIFT+R を押して【エラートレース】ウィンドウを表示させます。<1> の行をダブルクリックすると【コンソール】に表示されたのと同じエラー内容が表示されます。[2] の行がエラーを起こした式です。[2] の行を選択して右クリックのメニューから【呼び出し点のソース】を選択しますと、エラーを起こしたソースコードが開かれ、該当部分が選択状態になります。

Visual LISP Error Trace

エラーを起こした所を確認できたので、次にエラーを起こした時の変数の値を確認してみます。ブレークループ中ですので値を確認できます。

_1$ a
"A"
_1$

シンボル a の内容は文字列 "A" でした。これで除算していたエラーだったと解りました。ここでは関数の使い方を勘違いして引数の与え方が間違っていたことにします。inverse 関数は引数の逆数を返す関数のつもりでした。原因が分かったので CTRL+R を押してプログラムの実行をリセットします。

Visual LISP Tool Bar
ボタン ショートカット メニュー 説明
Visual LISP トップレベルにリセット CTRL+R 【デバッグ】>【トップレベルにリセット】 ブレークループをトップレベルまでリセットします。
Visual LISP 現在のレベルを終了 CTRL+Q 【デバッグ】>【現在のレベルを終了】 ブレークループを一段だけリセットします。

気を取り直して次の様に実行します。今度は「0 による除算」のエラーが出ました。

_$ (inverse 0)
; error: divide by zero
_1$

先ほどと同じように CTRL+SHIFT+R を押して【エラートレース】ウィンドウを表示させ、同じようにエラーを起こした箇所を表示させます。

Visual LISP Error Trace

簡単な例で示していますのでもうエラーの原因は明白かと思いますが、ここからのデバッグとして三つの方法があることを挙げておきます。

  1. エラーを起こす箇所の前にブレークポイントを置いて一時停止し、そこから変数の値を【ウォッチ】ウィンドウでリアルタイム表示させながらステップ実行していく。

    ある場合ある箇所で必ずエラーになるようなケースで有効です。ブレークポイントを置きたい式のはじめの括弧にカーソルを合わせてブレークポイントを置きます。

    Visual LISP Tool Bar
    ボタン ショートカット メニュー 説明
    Visual LISP ブレークポイントの切り替え F9 【デバッグ】>【ブレークポイントの切り替え】 ブレークポイントを設定・解除します。

    また、【ウォッチ】ウィンドウを開いて、Visual LISP の上の【デバッグ】メニューから【最後の評価をウォッチ】にチェックを入れて、「*LAST-VALUE*」の値を表示させておきます。また、シンボル a の値も見たいので、ソースコードで a を選択し、右クリックで表示されるコンテキストメニューから【ウォッチを追加】でウォッチリストに表示させます。

    Visual LISP Tool Bar
    ボタン ショートカット メニュー 説明
    Visual LISP ウォッチウィンドウを表示 CTRL+SHIFT+W 【表示】>【ウォッチウィンドウ】 【ウォッチ】ウィンドウを表示します。
    Visual LISP Watch Window

    準備ができたら先ほどと同じように実行すると、赤いブレークポイントの所でプログラムが一時停止しますので、各種ステップ実行を行いながらどのようにプログラムが実行され変数の値が変化していくか確認します。各ステップ実行の動作は次の様なものです。

    Visual LISP Tool Bar
    【ステップ イン】 Visual LISP ステップ イン
    【ステップ オーバー】 Visual LISP ステップ オーバー
    【ステップ アウト】 Visual LISP ステップ アウト
  2. 関数に注目して、その引数と結果の戻り値の実行状況を【トレース】ウィンドウに表示させてエラーが起こる状況を推定する。

    関数 inverse が特殊な目的の関数で、複数回の呼び出しの中でおかしな動作をする場合に有効な方法です。ソースコードの中から関数名 inverse を選択して右クリックのコンテキストメニューから【シンボルサービス】のダイアログを表示させます。そして「トレース」にチェックを入れ、OK で閉じます。それから、普段は最小化されている【トレース】ウインドウを表示させておきます。

    Visual LISP Trace

    inverse 関数をいろいろな条件でテストしてみます。最後は 0 を引数として予想通りエラーとなり結果が表示されていませんが、他の引数でもどうも変な結果となっています。

    Visual LISP Trace Window
  3. 関数の中の特定の式に注目して、print 関数などをコード中に挿入して、値を【コンソール】に表示させる。

    関数 inverse が汎用的な目的の関数でいろんな所から呼び出している場合は、そのすべてを【トレース】ウィンドウに表示させると煩雑です。この方法はある程度エラーがでる部分が絞り込まれており、複数回実行されるのでトレースでひとつひとつ見ていっては時間がかかり、なおコードのここの部分の値を知りたい場合に有効な手法です。また、原始的な方法でもあるので、どのようなケースでも扱えます。そして print 関数などは表示させる変数の型を選ばないで表示できるうえに、整数を与えられたら戻り値もその整数となるようなパイプとなる関数で、式の入れ子構造の中に単純に挿入しても、その構造を崩す必要はありません。今回の場合はあまり有り難みがありませんが、次のようにして【コンソール】で関数をテストしてみます。

    (defun inverse (a) (/ 1 (print a))) 

    結果の行の一番目が print 関数の出力、二番目が inverse 関数の戻り値です。実数の除算のつもりが整数の除算になるケースがあったことが判りました。

    _$ (inverse 2)⏎
    2 0
    _$ (inverse 1)⏎
    1 1
    _$ (inverse 2.0)⏎
    2.0 0.5
    _$ (inverse 1.0)⏎
    1.0 1.0

さて、以上のような調査の結果、一つの例として次のように inverse 関数を修正しました。引数が 0 の場合はエラーではなく nil を返し、また割られる数を 1.0 と実数にすることで引数が整数で与えられても実数の除算になるようにしました。

(defun inverse (a)
  (if (zerop a)
    nil
    (/ 1.0 a)
  )
)

関数定義をすばやく見つける

ソースコードをテストしている際に、この関数からあの関数を呼び出していて、あの関数の定義はどうなっていたかと探すケースが多々あります。そのような時に目的の関数のソースにすばやくたどり着く方法が【シンボルサービス】です。関数を呼び出しているところの関数名を選択して右クリックから【シンボルサービス】のダイアログを表示させます。そのダイアログの【定義を表示】ボタンを押すと、該当の関数が定義されているファイルが開いて定義が選択状態になります。

【シンボルサービス】ツールボタン 説明
Visual LISP 定義を表示 【定義を表示】 シンボルが定義されているファイルを開いて、該当部分を選択表示します。関数が、コンパイルされていないUSUBRの場合に有効です。

「最後の評価」を表すシンボル

【最後の評価をウォッチ】にチェックが入っている場合【ウォッチウィンドウ】で「*LAST-VALUE*」という形で表示されていました。これはシステムで使用されている変数名(シンボル名)でもあります。そのため、一時停止している際に「*LAST-VALUE*」というシンボル名を使って【コンソール】に値を表示させたり、引数として簡単な計算を行うこともできます。

コンパイルエラー

ソースコードでのテストを終えてデバッグが終了したと考え、コンパイルをして仕上げようとすると新たに出てくるエラーが、ごく稀にあります。

一応前段階までで文法上のエラーなど一通り修正済みとして、この段階で表面化する問題で、 ShiftJIS の日本語、 2 バイト文字をコンパイラがきちんと処理できないで止まってしまうケースが見られます。これは、コードの方で上手くいかない 2 バイト文字を整数のリストで表してから vl-list->string 関数で文字列に変換するとか、問題が起こる文字列で日本語を使うのはあきらめるのが、残念な話ですが手っ取り早い回避策です。

コンパイルしたプログラムの実行時エラー

次ぎにコンパイルしたコードのロードと実行時のケースですが、この段階で新たに出るエラーで明確に言える原因は、関数の型がコンパイル済みの SUBR とソースコード付きの USUBR の二種類あることを区別できていないコードを書いてしまっていたケースです。しかし普通は関数の型が関係するようなものを扱わないのでこれはあまり参考にはなりません。しかし、何度かコンパイルするとエラーが出るような経験が無きにしもあらずで、コンパイルしてしまうとステップ実行も使えないので、そのデバッグ作業は骨の折れる作業になります。

さてそのような対処方法は次の通りです。

  1. すこし休憩して、頭をすっきりさせましょう
  2. ソースコードのテスト中の実行環境とコンパイルしたコードの実行環境と、関数の定義など違ってしまっているところがないか確認しましょう
  3. もう一度、print 関数をプログラムの要所に書いてからコンパイルし、どこでおかしくなるか表示させながら絞り込みましょう
  4. おかしくなっている箇所が絞られてきて、その箇所が回りくどいコードになっていれば、もう一度すっきり書き表せないか改良してみましょう
  5. もう一度休憩してみましょう

なんともあてにならないアドバイスですが、そんなこんなで切り抜けて、今まで結果的にコンパイル出来なくてあきらめたケースはありませんので、くじけないでください。