この記事は『Code Polaris Advent Calendar 2023』 の13日目の記事です。
このアドベントカレンダー2回目の登場です。
だって盛り上げたかったんだもん。
この記事の前提
直近の案件で一緒に働く後輩くんたちに、先んじてプログラム修正やテストを進めている身として、「ここで失敗したから、気をつけて」とか「このエラーが出た時はこうしたら解決したよ」とか、そういう先人の知恵をチーム内チャットにどんどん共有していっています。この記事は、その先人の知恵の中の1つに上げたものを、公開できるようにいじったものです。
そういう事情があるので、ここでのデバッグはOracle DatabaseのPL/SQLのプロシージャでのデバッグを元にしていますし、当該案件の制限事項等を踏まえた内容となっています。なので、「そんなまどろっこしいやり方してんの?」「そんな前時代的なやり方はモダンな世界では役に立たない」というツッコミはあるかもしれませんが、そういう事情がある点、ご理解いただけると有り難いです。
でも、デバッグに使うコマンドや用語を読み替えてもらえれば、他のプログラミング言語にも応用できたり、考え方というかエッセンスは他の環境でも有効だと思っていますので、何かの足しにでもなれば良いかと思って書きます。
※ちなみに、PL/SQLに関しては下記が詳しいです。
【Oracle】PL/SQL入門
SHIFT the Oracle
BEGIN
の後にDBMS_OUTPUT.PUT_LINE
※を入れること
※DBMS_OUTPUT.PUT_LINE
とは標準出力にメッセージを表示するためのパッケージです。
PROCEDURE
のBEGIN
より上(前)は、引数とかを宣言する場所なので、そこにDBMS_OUTPUT.PUT_LINE
を入れても、コンパイルエラーになります。なので、DBMS_OUTPUT.PUT_LINE
を入れるときは、BEGIN
よりも後ろに入れましょう。FUNCTION
の場合も同様です。
-- 計算結果を返すストアドファンクション
CREATE OR REPLACE FUNCTION calc_result RETURN NUMBER
-- 宣言部
IS
n_result NUMBER;
DBMS_OUTPUT.PUT_LINE('ここはコンパイルエラーになる');
-- 処理部
BEGIN
n_result := 1 + 2;
DBMS_OUTPUT.PUT_LINE('n_result:' || n_result);
RETURN n_result;
END;
この場合だと、DBMS_OUTPUT.PUT_LINE('ここはコンパイルエラーになる');
のところに入れるとコンパイルエラーになります。
これ、結構間違いがちで、「このプロシージャ/ファンクションに処理が入ってこれてるか確認しーようっと・・・あれ?おかしいなぁ」となっちゃうので、「おかしいな」と思ったら、まずはこれを確認。
PROCEDURE
もFUNCTION
も最初と最後にDBMS_OUTPUT.PUT_LINE
を入れて確認
最初に入れるのは、特にFUNCTION
の場合で、ちゃんとFUNCTION
に入っているかを確認できます。上述の通りですね。
また、最後に入れるのは、エラー無く最後まで通ったか確認するためです。エラーになった場合の拾い方はまた後で書きます。
分岐は入った時と出た時にDBMS_OUTPUT.PUT_LINE
を入れて確認
分岐処理がある部分こそ、慎重に確認できるように、分岐に入った時と分岐の中の処理が終わって出た時に入れるとよいです。
入った時が確認できないと、必要な処理をすっ飛ばしていることに気づけないので。
IF p_result = TRUE THEN
DBMS_OUTPUT.PUT_LINE('IF文に入った');
p_result := func_get_data(v_table);
END IF;
DBMS_OUTPUT.PUT_LINE('IF文から出た');
この場合だと、「IF文に入った」と表示されたら、p_result
がTRUEであることが分かります。また、「IF文から出た」が表示されると、func_get_data
はEXCEPTION
でキャッチするようなエラーは起きずに処理が済んだことが分かります。
※あくまでも一例です。p_result
の中身を書き出して確認した上で、「IF文から出た」と表示するようにすれば、より確実に確認できます。
EXCEPTION
の中ではエラーコードとメッセージを出す
何かエラーが起きた時に飛ばされてくるのがEXCEPTION
部です。
その起きているエラーの情報を拾うことが大切です。例えば下記のような感じですね。
DBMS_OUTPUT.PUT_LINE('[PROCEDURE名・FUNCTION名] EXCEPTION');
DBMS_OUTPUT.PUT_LINE('SQLCODE:' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('SQLERRM(SQLCODE):' || SQLERRM(SQLCODE));
この場合は、どこのプロシージャ/ファンクションのEXCEPTION部で、ORACLEエラーコードは何で、どんなエラーメッセージなのかをDBMS_OUTPUT.PUT_LINE
で表示させています。
※SQLCODE
とSQLERRM
については、下記がイメージしやすいと思います。
オラクルエラーコード/エラーメッセージを取得する(SQLCODE/SQLERRM)
RETURN
文の前にDBMS_OUTPUT.PUT_LINE
を入れる
ファンクションでRETURN
がある時は、RETURN
より上(前)にDBMS_OUTPUT.PUT_LINE
を入れるようにしましょう。
RETURN
は呼び出し元に戻す(いわゆる戻り値)ので、RETURN
より下(後ろ)に書いた処理には入らないです。
-- 計算結果を返すストアドファンクション
CREATE OR REPLACE FUNCTION calc_result RETURN NUMBER
-- 宣言部
IS
n_result NUMBER;
-- 処理部
BEGIN
n_result := 1 + 2;
DBMS_OUTPUT.PUT_LINE('n_result:' || n_result);
RETURN n_result;
-- DBMS_OUTPUT.PUT_LINE('この処理は実行されない');
END;
上記の場合は、calc_result
ファンクション内では、NUMBER型の変数n_result
に「1+2」の結果を代入して、calc_result
ファンクションの戻り値としてn_result
の値を返すという処理です。
RETURN n_result;
よりも上(前)のDBMS_OUTPUT.PUT_LINE('n_result:' || n_result);
は実行されますが、RETURN
より下(後ろ)のDBMS_OUTPUT.PUT_LINE('この処理は実行されない');
は実行されないです。
時々うっかりして、ここまでの処理が通っているか確認しようと思ってファンクションの最後に入れたら表示されなくて「あれー?」となることがあります。そんな時は、RETURN
の前に入れているか確認しましょう。
最後に
ああしたらいい、こうしたらいいと書きましたが、標準出力を使ってデバッグをする時は、細かくメッセージを出力するようにして、コツコツやっていくと確実だし、エラーになってもすぐに分かるので、結果的に早く問題にたどり着ける気がします。
スマートな手法もステキですが、1歩ずつやっていこうと自戒を込めて。