Ctrl+Lで呼び出し履歴を読む ― 止まった行から原因の入口へ

LWP | Ctrl+Lで呼び出し履歴を読む ― 止まった行から原因の入口へ

図解1|止まった行から、原因の入口へ

停止した明細処理から、0を渡した注文処理と入口へ調査を広げる

異常を見つけた場所と、その値を渡した場所をつなぎます。図は教材の模式図です。

解説1|停止位置を、原因へ向かう出発点にする

ストーリー

シャケモンが調べているマクロは、明細処理で中断しました。画面には行番号を確かめる式が見えています。「この行を書き換えれば直るでしょうか」。コイ丸先輩は、呼び出した側から何が渡ってきたかを一緒に見ることにします。止まった行は、異常が表面に出た場所です。値をおかしくした場所まで同じとは限りません。

今回使うCtrl+Lは、VBEの「呼び出し履歴(Call Stack)」を開く操作です。スタックトレースを見る、と呼ばれる調査のうち、VBAで現在の呼び出し階層を確認する部分に当たります。明細処理が誰から呼ばれ、その呼び出し元がさらに誰から呼ばれたかを、実行途中の状態でたどれます。

三つの問いをつなぐ

調べる問い

この教材で見る証拠

どこで前提に反したか

明細のa_rowNo > 0がFalse

どんな値を受け取ったか

a_orderNoはA001、a_rowNoは0

誰がその値を渡したか

注文処理の呼び出し行の第2引数が0

この三つを結ぶと、「明細処理の条件が厳し過ぎる」という見立てと、「呼び出し元が不正な行番号を渡している」という見立てを、コードと値で比較できます。停止した行だけを見て条件を削ると、正の行番号が必要という前提まで失われます。

この記事でたどる道筋

初めに呼び出しと戻りの構造を整理し、次に三つのSubを使ってCtrl+Lとローカル変数を読みます。その後、現在の値と過去の値の違い、ステップ実行、エラー処理による見え方の違いを確かめます。最後に同じ条件で比較する方法へつなぎます。

目標は、一覧を開けることに加えて、原因候補を「どの呼び出しの、どの引数が、どの前提を外れたか」で説明できるようになることです。

図解2|呼び出すと積まれ、戻ると外れる

入口、注文、明細の呼び出しが積まれ、明細から戻ると二段に減る

一つの箱は、まだ終了していない一回の呼び出しを表します。

解説2|呼び出し元は、呼び出し先の終了を待っている

呼び出しの途中には、複数の処理が残る

入口のSubが注文処理を呼ぶと、入口の処理はそこで待ちます。注文処理が明細処理を呼ぶと、注文処理も待ちます。明細の中で中断した瞬間には、明細、注文、入口の三つが、まだ終了していない呼び出しとして残っています。三つが同時並行に計算している、という意味ではありません。

このように、後から呼ばれた処理が手前に積まれ、そこから先に終了して戻る関係をスタックで表します。一段分を「フレーム」と呼ぶことがあります。ここでは、呼び出された一回分の実行の文脈を表す言葉として使います。プロシージャの定義そのものを複製して保存する、という説明ではありません。

時点

まだ終了していない呼び出し

現在進めている処理

入口を開始

入口

入口

注文から明細を呼ぶ

明細、注文、入口

明細

明細が終了して戻る

注文、入口

注文

戻る場所を保つための構造

明細が終わると、明細の呼び出しは一覧から外れ、注文の呼び出し行の続きへ戻ります。注文も終われば、入口の続きへ戻ります。呼び出し元が残っているからこそ、どこへ戻るかという関係を保ったまま、処理を分けて書けます。

デバッグでは、この残っている関係を観察します。現在の明細だけでなく、その明細を必要とした注文処理、その注文処理を開始した入口も調査対象にできます。同じ名前の処理が複数回呼ばれる場合も、「プロシージャ名の一覧」だけでなく、今回の呼び出しの階層として読むことが大切です。

終了した呼び出しは一覧から外れる

呼び出し履歴は、終了済みの処理を蓄積する帳簿ではありません。戻った後で一覧から明細が消えても異常ではなく、実行状態が変わったためです。過去の値が必要なら、実行中に別途記録する手段を用意します。

図解3|履歴・ログ・呼び出し図を使い分ける

実行途中の呼び出し履歴、時系列のログ、静的な呼び出し関係図を比較する

履歴は現在の階層、ログは記録済みの出来事、静的な図はコード上の関係を扱います。

解説3|欲しい証拠に合う道具を選ぶ

似ている図でも、示す範囲が違う

呼び出し履歴は、今の停止位置につながる未完了の呼び出しを示します。直前に終了した別の処理や、以前の実行で通った経路まで自動保存しているわけではありません。「履歴」という名前から、すべての行の実行順を後から再生できると思うと、必要な証拠を取り逃します。

実行ログは、コードがその時点で出力した出来事や値です。Debug.Printで注文番号を出しておけば、その出力を後から読めます。ただし、書き出していない値や処理まで分かるわけではなく、記録する項目と場所の設計が必要です。

静的な呼び出し図は、コードを読んで呼び出し関係を整理したものです。今回通っていない分岐も含められるので、全体を知る助けになります。一方、文字列で指定するApplication.Runやイベント経由の呼び出しなどは、解析方法によって関係を取り切れない場合があります。

調べたいこと

まず使う手段

今、どこからこの処理へ来たか

中断中の呼び出し履歴

変更する前の値はいくつだったか

その時点の停止・ログ

ほかに呼び出す場所があるか

コード検索・静的な呼び出し図

二つの方向から照合する

最初に検索や図で候補をつかみ、実際に中断して今回の経路を確かめると、推測と観測を分けられます。逆に、呼び出し履歴で見つかった処理名を検索し、別の入口や条件を探すこともできます。

図解のログと別経路は、役割を比べるための模式例です。後の教材コードがそのログを自動出力したり、集計処理を呼び出したりするわけではありません。

読むポイント

「この一覧にはなかった」と「過去にも一度も実行していない」は別の判断です。呼び出し履歴にないことだけを理由に、その処理の関与を否定しないようにします。

図解4|3つのSubで値の流れを見る

入口からA001が渡り、注文処理から行番号0が明細へ渡る

書籍の例を、説明コメントを省いた同じ処理で掲載します。実データを読み書きしない教材です。

解説4|今回のソースコードのサンプル

入口・注文・明細の三段を用意する

新しい確認用ブックの標準モジュールに、次のコードをまとめて置きます。入口はSample_CallStackEntryです。注文番号は文字列、行番号はLong型の整数として受け取ります。第2引数の0は、異常を観測するために教材で意図的に渡している値です。

三段の呼び出しと前提の確認

01

Option Explicit

02

Public Sub Sample_CallStackEntry()

03

    Sample_LoadOrder "A001"

04

End Sub

05

06

Public Sub Sample_LoadOrder(ByVal a_orderNo As String)

07

    Sample_LoadDetail a_orderNo, 0

08

End Sub

09

10

Public Sub Sample_LoadDetail(ByVal a_orderNo As String, _

11

                            ByVal a_rowNo As Long)

12

    Debug.Assert a_rowNo > 0

13

    Debug.Print a_orderNo, a_rowNo

14

End Sub

入力・結果・読むポイント

Sample_LoadOrderの引数a_orderNoには、入口の"A001"が入ります。続いてSample_LoadDetailへ同じ注文番号と0を渡します。a_rowNo > 0がFalseになるため、Debug.Assertで中断するのが期待する動きです。

ここではByValで受け取った引数を、明細の中で書き換えていません。呼び出し行の0と、停止中のa_rowNoの0をそのまま対応付けられます。図11では別のByRef例を扱います。

Debug.Assertは開発中に前提を確かめるための仕掛けです。行番号が正という条件だけで、対象シートの存在や最終行以内であることまで確認できるわけではありません。

図解5|観察できる場所で中断する

入口から実行し、Debug.Assertの条件がFalseになった時点で中断する

中断した状態を保って、呼び出し履歴と変数を調べます。

解説5|終了させる前に、調べられる状態を作る

中断と終了で、残る情報が違う

中断モードでは、実行途中の呼び出しと変数の状態を観察できます。処理を終了・リセットすると、その途中の関係は失われます。まず止め、観察し、それから続行するか終了するかを決める順番が基本です。

今回の教材はDebug.Assertで止まるため、先にブレークポイントを設定しなくても観察できます。実行時エラーのダイアログを出す例ではなく、a_rowNo > 0という前提がFalseになったことを検出する例です。

操作:教材を中断させる

確認用ブックでAlt+F11を押し、VBEを開きます。標準モジュールへ前ページのコードを置き、Sample_CallStackEntryの中へカーソルを置いてF5を押します。引数のあるSample_LoadDetailを直接実行するのではなく、値を渡す入口から始めます。

Debug.Assertの行で中断したら、a_rowNoが0であることを確認します。ここで終了やリセットを選ばず、Ctrl+Lへ進みます。F5をもう一度押すと続行するため、調べる前に押し進めないようにします。

ブレークポイントを使う場合

止め方

観察する場面

ブレークポイント

指定した行を実行する前

Stop

コード中の意図した位置

Debug.Assert

指定した条件がFalseになったとき

ブレークポイントは、調べる行にカーソルを置いてF9で設定・解除できます。コード左側の余白をクリックする方法もあります。呼び出す直前の引数を見たいなら呼び出し行、受け取った値を見たいなら呼び出し先の先頭付近に置く、と観察したい時点から位置を選びます。

読むポイント

入力条件と入口が違えば、同じ行で止めても調べている実行が変わります。まず「どのブックの、どの入口を、どの入力で動かしたか」をそろえます。

図解6|Ctrl+Lで呼び出しの階層を読む

明細、注文、入口の順に表示される未完了の呼び出し

現在の処理から、直接の呼び出し元、さらにその呼び出し元へたどります。

解説6|プロシージャ名を、階層として読む

一覧の一行は、まだ終わっていない呼び出し

明細の中で中断した教材では、現在のSample_LoadDetail、その呼び出し元Sample_LoadOrder、入口のSample_CallStackEntryという関係を確認します。ソースコードの上から下への配置順や、名前の五十音順を表す一覧ではありません。

VBEの表示では、プロシージャ名に加えてプロジェクトやモジュールの情報も確認します。同名の処理や複数のブックがある場合は、名前の末尾だけを見ず、どのプロジェクトの処理かを照合します。

操作:呼び出し履歴を開く

VBEのコード画面が中断モードになっている状態でCtrl+Lを押します。メニューからは[表示]→[呼び出し履歴]を使います。ローカルウィンドウの呼び出し履歴ボタンから開く方法もあります。

Excelのシート側へフォーカスがあると、Ctrl+Lが別の操作になることがあります。VBEを開いただけの実行前や、処理がすべて終わった状態でも、今回の中断中の一覧は見られません。使えないときは、VBE側を操作しているか、実際に中断しているかを先に確認します。

教材の一覧

役割

Sample_LoadDetail

現在中断している処理

Sample_LoadOrder

明細を呼んだ処理

Sample_CallStackEntry

注文を呼んだ入口

一段ずつ、具体的な問いを持つ

一覧を眺めるだけで終わらず、「この処理を呼んだ行はどこか」「第2引数は何か」という問いと組み合わせます。次にSample_LoadOrderを選んで表示し、呼び出したコード行を見ます。

外部のコードやホスト側の処理が関わる場合、すべての内部実装がVBAのコードとして見えるとは限りません。表示されない部分を想像で埋めず、まず読める呼び出しと引数を確認します。

図解7|表示する場所と、実行する場所

明細で中断したまま、注文処理の呼び出し行を表示する

呼び出し元の表示は、処理の巻き戻しでも再実行でもありません。

解説7|画面が切り替わっても、実行は中断したまま

見る位置と、実行を再開する位置

呼び出し履歴でSample_LoadOrderを表示すると、Sample_LoadDetailを呼んだ行へコードの表示が移ります。しかし明細で中断しているという事実は変わりません。注文処理を最初から実行し直したわけでも、引数を渡す前へ時間が戻ったわけでもありません。

この区別は、表示を切り替えた後にF8やF5を押すときに重要です。コードの挿入位置だけで、次に実行する行を判断してはいけません。実行位置の表示を確認し、必要なら呼び出し履歴の現在の処理へ表示を戻してから進めます。

操作:呼び出し元を表示する

Ctrl+Lで一覧を開き、Sample_LoadOrderを選んで表示します。ダブルクリックでも呼び出し元のコードを開けます。表示されたSample_LoadDetail a_orderNo, 0の行を読み、第2引数が0であることを確かめます。

VBEには、実行の現在行を示す印と、呼び出し履歴で選んだ位置を示す印があります。配色は設定で変えられるため、色だけで判断せず、それぞれの印の役割と選択中のプロシージャを合わせて見ます。図の黄色と青は、その役割を分けた模式表現です。

操作

何が変わるか

呼び出し元を表示する

観察するコードと呼び出しの文脈

F8で進める

実際の処理と変数の状態

実行位置を移動する

次に実行する位置。表示とは別操作

読むポイント

実行位置の矢印をドラッグしたり、「次のステートメントの設定」を使ったりする操作は、今回の呼び出し元の表示と別です。実行位置を変えても、すでに起きた変数変更やシートへの書き込みまで取り消せるわけではありません。今回は見る位置だけを切り替えて調査します。

図解8|ローカル変数で受け取った値を確かめる

明細の引数a_orderNoとa_rowNoの名前、型、値を確認する

選択した呼び出しと、変数を評価する文脈をそろえます。

解説8|名前・型・値を、同じ呼び出しで照合する

同じ変数名でも、どの処理のものかを確かめる

今回のa_orderNoは、注文処理にも明細処理にもある引数名です。文字が同じだからといって、一つの変数を共有していると決め付けてはいけません。どのプロシージャの、どの呼び出しの引数を見ているかを、呼び出し履歴と対応付けます。

ローカルウィンドウは、現在の文脈の変数を名前・値・型で示します。呼び出し履歴から別の階層へ移ると、その文脈に合わせて表示が変わります。今見ているのが明細なのか注文なのかを確認してから、値の意味を判断します。

操作:明細の値を確認する

[表示]→[ローカルウィンドウ]を開きます。呼び出し履歴で明細の呼び出しを表示し、次の二つを確認します。

変数

型

この停止時点で期待する値

a_orderNo

String

A001

a_rowNo

Long

0

a_rowNoは整数型として受け取れていても、教材が要求する正の行番号という条件を満たしていません。「型が合っている」と「用途に合う値である」は、別々の確認です。a_rowNo > 0へ0を当てはめるとFalseになり、中断した理由を説明できます。

文脈を切り替えた結果も確かめる

注文処理へ表示を切り替えると、その注文のa_orderNoを調べられます。注文にはa_rowNoという引数を宣言していないため、明細で見えた同名の変数をそのまま探すのではなく、呼び出し行の第2引数を読みます。

ローカルの値は編集もできます。調査中に値を書き換えると再現条件が変わるため、この段階では読む操作にとどめます。値を変えて比較する場合は、元の値と変更内容を記録してから行います。

図解9|一段戻って、0を渡した行を見つける

呼び出し行の第2引数0と、明細で受け取ったa_rowNoの0を対応付ける

「0を受け取った」という観察に、呼び出し行という根拠を結び付けます。

解説9|異常な値と、それを渡したコードをつなぐ

呼び出しの一段が、調査範囲の一段になる

明細でa_rowNoが0だと分かっても、それだけでは値の出所は決まりません。入口からずっと0だったのか、途中で書き換わったのか、引数の順序を取り違えたのかを考える必要があります。まず直接の呼び出し元を調べると、確認する範囲を一段に絞れます。

今回のSample_LoadOrderには、Sample_LoadDetail a_orderNo, 0という呼び出しがあります。最初の引数が注文番号、二つ目が行番号です。第2引数に0が直接書かれているため、明細の計算途中で偶然0になったのではなく、呼び出す時点で0を渡していると説明できます。

呼び出しと宣言を対応付ける

呼び出し元で渡すもの

明細側で受け取る名前

意味

a_orderNo

a_orderNo As String

注文番号A001

0

a_rowNo As Long

正という前提に反する行番号

実引数は呼び出し側が渡す値や式、仮引数は受け取る側で使う名前です。同じ位置に並ぶ引数を対応付けると、名前が違う実装でも追えます。名前付き引数を使うコードでは、名前による対応も確認します。

次の仮説を一つに絞る

この教材では「注文処理が行番号0を渡している」が原因候補の出発点になります。実務コードで第2引数が変数や計算式なら、その変数を作った直前へ観測点を移し、期待する行番号と比べます。呼び出し履歴が教えるのは、まず調べるべき呼び出し行です。

呼び出し元を見つけたことと、恒久修正が決まったことは区別します。0が「対象なし」の合図なら、値を無理に正にする前に、対象なしを呼び出し側で処理する契約か、明細側で受け付ける契約かを確かめる必要があります。

図解10|さらに入口へ、今回の経路を確かめる

確認した入口からの実行経路と、別途再現が必要な入口の候補を分ける

同じ明細処理でも、どの入口と入力から到達したかを残します。

解説10|直接の呼び出し元から、再現条件までつなぐ

共通処理は、複数の入口から利用される

業務マクロでは、同じ読込処理や共通関数を、ボタン、別のマクロ、シートイベントなどから呼ぶことがあります。共通処理だけを見ていると、今回どの入口から来たのか、どの前提で動いているのかが抜けやすくなります。

教材では、さらに呼び出し元をたどるとSample_CallStackEntryへ到達し、Sample_LoadOrder "A001"という行が見えます。注文番号A001を渡した入口と、注文処理が0を渡した行を、同じ実行の中で対応付けられます。

入口まで戻って残す情報

記録する項目

今回の教材

実行した入口

Sample_CallStackEntry

対象

注文番号A001

直接の呼び出し元

Sample_LoadOrder

中断した条件

a_rowNo > 0がFalse

次に調べる箇所

注文処理の第2引数

このように記録すると、調査を引き継ぐ人が同じ入口から同じ状態を作りやすくなります。「明細で止まった」だけでは、別の注文や別の入口を動かしてしまい、比較条件がそろわないことがあります。

別の入口は、別の実行として確かめる

コード検索で別の呼び出し箇所が見つかっても、今回そこを通った証拠にはなりません。必要なら別入口から再現し、引数や前提を比較します。図の点線は、そうした確認候補を表したもので、教材へ追加した実装ではありません。

入口までたどっても原因が決まらないときは、外部入力、オブジェクトの状態、前回の実行から残った設定などへ調査範囲を広げます。呼び出し履歴は経路を絞る手段です。業務上の正しい値まで自動判定する機能ではありません。

図解11|現在の値は、過去の値の保存ではない

ByRefで5から9へ変わった後、呼び出し元を表示しても現在値9が見える

呼び出し元の値を観察することと、呼び出す前の値を再現することは別です。

解説11|値が変わる前の時点は、別に観測する

ByRefで、表示と巻き戻しの違いを確かめる

前の教材はByValで値を受け取り、明細の中では変更していません。ここでは別の短い例で、過去の値が自動保存されないことを確かめます。ByRefで渡した変数は、呼び出し先の代入によって呼び出し元側の値も変わります。

現在値を確認する別例

01

Public Sub Sample_CurrentValue()

02

    Dim x As Long

03

    x = 5

04

    Sample_ChangeValue x

05

End Sub

06

07

Public Sub Sample_ChangeValue(ByRef x As Long)

08

    x = 9

09

    Stop

10

End Sub

Sample_CurrentValueから実行すると、呼び出す直前のxは5です。呼び出し先で9を代入してからStopで中断します。このままCtrl+Lで呼び出し元を表示しても、そこで見えるxは現在値の9です。実行を呼び出し前へ戻したわけではないため、5には戻りません。

5だった時点を調べたいとき

呼び出し行へブレークポイントを置き、実行する前のxを確認します。その後F8で中へ入り、代入による変化を観察します。繰り返し実行するなら、呼び出し直前にDebug.Printで値と対象を記録する方法もあります。

呼び出し履歴は「どこから来たか」、時点を分けた観測は「いつ変わったか」を調べる助けになります。この二つを組み合わせると、呼び出し元を見ても過去の値が分からないという行き詰まりを避けられます。

図解12|F8で進むと、階層も変わる

明細へ入る前、中断中、戻った後で呼び出し履歴の段数を比較する

呼び出し履歴を読みながらステップ実行すると、構造と実際の動きがつながります。

解説12|一行の前後で、呼び出しの増減を見る

呼び出し前と、呼び出し先の中を比べる

注文処理の呼び出し行へブレークポイントを置き、入口から実行します。明細を呼ぶ前なら、注目する階層は注文と入口です。F8で明細へ入った後にCtrl+Lを開くと、明細が加わります。明細を最後まで進めて注文へ戻れば、明細は一覧から外れます。

この前後比較によって、一つの呼び出し行が別のSubの実行へつながり、終了後に元の処理へ戻ることを実感できます。ソースコードの表示位置だけで追っていたときより、今どの深さにいるかを確かめやすくなります。

操作を、調べたい範囲から選ぶ

操作

主な目的

確認する点

F8:ステップイン

呼び出し先の中まで見る

値が変わる行

Shift+F8:ステップオーバー

呼び出しをまとめて進める

戻った後の状態

Ctrl+Shift+F8:ステップアウト

現在の処理の残りを進めて戻る

呼び出し元の続き

ステップオーバーは、呼び出し先を実行しないという意味ではありません。ステップアウトも、残りの処理を取り消して抜ける操作ではなく、実行してから戻る操作です。途中にブレークポイントや中断条件があれば、その位置で止まる場合があります。

先へ進む前に、必要な証拠を残す

値が分からなくなったので勢いよくF8を押し続けると、見たかった呼び出しが終了してしまうことがあります。まずCtrl+Lとローカルで階層と値を確認し、その後に進める習慣を付けます。

すべての処理が終わると、その実行の未完了の呼び出しは残りません。後から調べる可能性がある値や対象は、終了前に記録しておきます。

図解13|エラー処理で、見える経路が変わる

エラー発生箇所での中断と、上位ハンドラーへ移った後の階層を比較する

ここは実行時エラーの模式例です。Debug.Assertで止まる教材とは分けて読みます。

解説13|発生箇所と、ハンドラーの観測を分ける

ハンドラーへ移る間に、呼び出しが終了することがある

実行時エラーが起きたとき、VBAはOn Errorの指定とVBEのエラートラップ設定に従って動きます。呼び出し先に処理できるハンドラーがなく、呼び出し元のハンドラーへ制御が移る場合、途中の呼び出しは終了します。その後でCtrl+Lを開いても、発生時の階層をそのまま保存した一覧にはなりません。

図は、明細で起きたエラーが注文を経て入口のハンドラーへ移る場合の模式例です。ハンドラーは独立したSubとは限らず、On Error GoToで指定したラベル以降の処理です。Err.Numberなどのエラー情報と、現在の呼び出し履歴は役割が異なります。

停止位置に関係する設定

VBEの[ツール]→[オプション]→[全般]にあるエラートラップを確認します。英語名との対応を知ると、環境が違っても意味を照合できます。

設定

主な意味

エラー発生時に中断

ハンドラーの有無にかかわらずエラーで中断

クラスモジュールで中断

クラス内の未処理エラーを、その発生行で中断

エラー処理対象外のエラーで中断

ハンドラーで処理されるエラーでは中断しない

発生時点を見たい場合

発生しそうな行の前にブレークポイントを置いて、引数と呼び出し履歴を見ます。必要に応じて確認用環境の設定を変えますが、元の設定を記録し、確認後に戻します。On Errorを一律に削除することを通常の修正手順にはしません。

Debug.AssertのFalseによる中断は、Err.Numberを伴う実行時エラーそのものではありません。教材のAssertを、エラーハンドラーへ飛ぶ例として読み替えないことが大切です。

図解14|同じ入口から、仮説を確かめる

行番号0の場合と、確認用コピーで1を渡した場合を同じ入口から比べる

変える条件を一つに絞り、止まらなくなった理由まで説明します。

解説14|動いたことと、正しい修正ができたこと

仮説を、比較できる形にする

今回の観察から、「注文処理が渡す行番号0が、明細の正の行番号という前提に反している」と説明できます。この仮説を確かめるため、確認用コピーで第2引数だけを正の値へ変えます。入口と注文番号は同じにし、比較する条件をそろえます。

確認用コピーで変更する一行

01

    Sample_LoadDetail a_orderNo, 1

変更箇所はSample_LoadOrderの中です。前の中断を終えてからコードを変更し、Sample_CallStackEntryを最初から実行します。中断中の状態を継ぎ足して比較するより、入口と初期状態をそろえやすくなります。

比較する結果

条件

Assertの判定

その後

0を渡す

0 > 0はFalse

Assertで中断

1を渡す

1 > 0はTrue

Debug.Printまで進む

Ctrl+Gでイミディエイトウィンドウを開くと、変更後はA001と1が出力されるのが期待結果です。元の0の例も中断後に続行すれば後続行へ進めるため、「出力があった」だけで比較せず、どの条件で中断したかを合わせて見ます。

業務の修正は、値の根拠まで確認する

この教材では1が前提を満たすため、比較用に使えます。しかし本番の注文が1行目にあるという根拠にはなりません。検索の結果が見つからなかったのか、行番号の計算がずれたのか、対象なしを0で表す仕様なのかを確認して修正します。

停止行の条件を消して通すだけでは、下流へ不正な値を渡す可能性が残ります。正しい入力と異常な入力を分け、元の不具合を再現した条件でも、修正後の振る舞いを確かめます。

図解15|誰が・何を・どの前提で

再現、中断、経路確認、引数照合、仮説の比較、再確認を一つの調査手順にする

呼び出し履歴を、原因候補を絞る調査の中へ組み込みます。

解説15|まとめ:停止位置から、原因を作った区間へ

自分のマクロで使う確認表

段階

確認すること

再現する

ブック・入口・入力・設定をそろえる

中断する

調べたい値が残る位置で止める

経路を見る

Ctrl+Lで現在の処理と呼び出し元を読む

値を結ぶ

引数の位置・名前・型・現在値を照合する

仮説を試す

確認用コピーで一条件を変え、結果を比べる

再確認する

元の再現条件と、正しい入力の両方を確認する

今回の仕掛け

明細へ行番号0を渡し、Debug.Assertで前提の不一致を観察しました。明細の値から注文の呼び出し行へ、さらに入口の注文番号へたどることで、異常と受け渡しの関係を説明できます。図11のByRef例は、呼び出し元を表示しても過去の値は復元されないことを補います。

コイ丸先輩の経験上の問いと、シャケモンが観察した値を突き合わせると、停止行だけを書き換える前に調べる場所が見えてきます。Ctrl+Lを使う理由は、プロシージャ名を知ることに加えて、局所的な異常を呼び出し経路の中へ置き直せるからです。

出典と確認範囲

『デバッキング Excel VBA』V026_261006、第3章「処理を止め、値と呼び出し元を観測する」の「Call Stackを遡ってエラーの発生箇所を特定する」、サンプルVDBG-C03-003を中心に記事化しました。実行位置・ローカル変数・エラー処理は公式資料と照合しています。図は模式図です。

Ctrl+Lの操作には書籍制作時の実機確認記録を参照し、本記事での新規実機追試とは区別しています。図11は現在値を説明するために追加した短い例です。