デバッグに関するTips

:::note info
この記事は[『Code Polaris Advent Calendar 2023』 ](https://qiita.com/advent-calendar/2023/code-polaris "Code Polaris Advent Calendar 2023")の13日目の記事です。
:::
このアドベントカレンダー2回目の登場です。

だって盛り上げたかったんだもん。

# この記事の前提
直近の案件で一緒に働く後輩くんたちに、先んじてプログラム修正やテストを進めている身として、「ここで失敗したから、気をつけて」とか「このエラーが出た時はこうしたら解決したよ」とか、そういう先人の知恵をチーム内チャットにどんどん共有していっています。この記事は、その先人の知恵の中の1つに上げたものを、公開できるようにいじったものです。

そういう事情があるので、ここでのデバッグはOracle DatabaseのPL/SQLのプロシージャでのデバッグを元にしていますし、当該案件の制限事項等を踏まえた内容となっています。なので、「そんなまどろっこしいやり方してんの？」「そんな前時代的なやり方はモダンな世界では役に立たない」というツッコミはあるかもしれませんが、そういう事情がある点、ご理解いただけると有り難いです。

でも、デバッグに使うコマンドや用語を読み替えてもらえれば、他のプログラミング言語にも応用できたり、考え方というかエッセンスは他の環境でも有効だと思っていますので、何かの足しにでもなれば良いかと思って書きます。

※ちなみに、PL/SQLに関しては下記が詳しいです。
[【Oracle】PL/SQL入門](https://qiita.com/nkojima/items/93a9c01741965f11bb8c "【Oracle】PL/SQL入門")
[SHIFT the Oracle](https://www.shift-the-oracle.com/ "SHIFT the Oracle")

# `BEGIN`の後に`DBMS_OUTPUT.PUT_LINE`※を入れること

※`DBMS_OUTPUT.PUT_LINE`とは標準出力にメッセージを表示するためのパッケージです。

`PROCEDURE`の`BEGIN`より上（前）は、引数とかを宣言する場所なので、そこに`DBMS_OUTPUT.PUT_LINE`を入れても、コンパイルエラーになります。なので、`DBMS_OUTPUT.PUT_LINE`を入れるときは、`BEGIN`よりも後ろに入れましょう。`FUNCTION`の場合も同様です。

```sql:begin_sample.sql
-- 計算結果を返すストアドファンクション
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`を入れて確認

分岐処理がある部分こそ、慎重に確認できるように、分岐に入った時と分岐の中の処理が終わって出た時に入れるとよいです。

入った時が確認できないと、必要な処理をすっ飛ばしていることに気づけないので。

```sql:if_sample.sql
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`部です。

その起きているエラーの情報を拾うことが大切です。例えば下記のような感じですね。

```sql:exception_sample.sql
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）](http://oracle.se-free.com/plsql/d6_sql_code.html "オラクルエラーコード/エラーメッセージを取得する（SQLCODE/SQLERRM）")

# `RETURN`文の前に`DBMS_OUTPUT.PUT_LINE`を入れる

ファンクションで`RETURN`がある時は、`RETURN`より上（前）に`DBMS_OUTPUT.PUT_LINE`を入れるようにしましょう。

`RETURN`は呼び出し元に戻す（いわゆる戻り値）ので、`RETURN`より下（後ろ）に書いた処理には入らないです。

```sql:return_sample.sql
-- 計算結果を返すストアドファンクション
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歩ずつやっていこうと自戒を込めて。