【Rails,Django初学者必見】デバックの方法は必ず身につけておきましょう

これは「Happiness Chain Advent Calendar 2024」の1日目の記事です。

https://adventar.org/calendars/10341

# はじめに

みなさん、こんにちは。[Happiness Chain](https://happiness-chain.com/)メンターのryoです。

今回は、RailsとDjangoの初学者向けに、デバッグの重要性と、VSCodeを使用したRubyとPythonのデバックの方法、RailsとDjangoのデバッグ環境の構築手順について、さらにDockerを使用している場合のポイントも合わせて解説します。

ぜひ、実際に手を動かしながら進めてみてください。

# デバックの重要性

デバッグは、プログラム内のバグを見つけ、修正する作業です。

プログラムはコンパイル(PCが実行できる形式に変換する作業)時のエラーと実行時のエラーが想定されます。RubyとPythonはインタプリタ言語で、基本的にはコンパイルは不要ですので、実行時のエラーでデバックする必要が出てきます。

実行時のエラーに関して、**例外発生の有無**でデバックの方法が異なります。

**例外**とは、プログラム実行時に発生する予期せぬエラーです。

例外が発生した場合については、例外発生時点で処理が止まり、例外が発生したファイル名、行数、内容等のエラーメッセージがコンソールに表示されます。ですので、そのエラーメッセージを読み解くことで、ほぼ修正が可能です。（ですので、エラーメッセージは必ず翻訳して、原因を理解しましょう。）

次に例外が発生しない場合についてですが、例外が発生しないエラーは、期待しない挙動をするような時です。
以下のような関数を作成し、実行したとします。

```rb:main.rb
def sum_of_evens(numbers)
  sum = 0

  numbers.each do |num|
    if num.even?
      sum = num 
    end
  end

  sum
end

list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = sum_of_evens(list)
puts result

```

```py:main.py
def sum_of_evens(numbers):
    sum = 0

    for num in numbers:
        if num % 2 == 0:
            sum = num

    return sum

list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = sum_of_evens(list)
print(result)

```

上記の関数は数値が要素の配列を受け取り、偶数の合計値を取得する関数を想定し作成しました。
そして、1から10の要素を持つ配列を渡した際に、`出力値`として30を期待しますが、実際には`10`と出力されます。例外は発生せず、エラーの原因はコンソールに出力されません。

これくらいの行数の処理であれば、ひと目見ただけでエラーの原因が分かると思います。しかし、実務ではこれ以上に膨大なコード量で、どこにエラーがあるのか見つけるのが大変ですので、デバックの方法を知っておくのは大切です。

# Ruby,Pythonのデバックの方法

実際に手を動かす方は、新しいディレクトリを作成して、ディレクトリ内に、`main.rb`もしくは`main.py`を作成して、上記のコードをコピペで貼り付けてください。
上記を例にデバックしてみましょう。VSCodeには便利なデバック機能があります。
左側のアイコンの上から4つ目をクリックし、右側に表示される`create a launch.json`をクリックしましょう。

![create_lunch.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/da788d3e-7633-40cd-9f1d-2e074733628c.jpeg)


そうすると、プロジェクト直下に`.vscode/launch.json`が作成されます。

<details><summary>Rubyの場合</summary>

※Rubyの基本的な拡張機能として、Rubyを入れている場合は、`Ruby`はアンインストールして、`Ruby LSP`をインストールしましょう。
Ruby LSPがインストールされている状態で、`create a launch.json`をクリックしましょう。

https://qiita.com/rys0707/items/0b0ed63f9b266bc1358b#vscode%E3%81%B8%E3%81%AErubocop%E3%81%AE%E5%B0%8E%E5%85%A5%E6%89%8B%E9%A0%86

以下のような`lunch.json`が作成されます。

```json:lunch.json
{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "ruby_lsp",
      "name": "Debug script",
      "request": "launch",
      "program": "ruby ${file}"
    },
    {
      "type": "ruby_lsp",
      "name": "Debug test",
      "request": "launch",
      "program": "ruby -Itest ${relativeFile}"
    },
    {
      "type": "ruby_lsp",
      "name": "Attach debugger",
      "request": "attach"
    }
  ]
}
```

このファイルを簡単に説明すると、`configurations`にはデバックの構成が記述されており、現在nameが`Debug script`、`Debug test`、`Attach debugger`の3つがあるかと思います。使い道は以下の通りです。

- Debug script: 単純なRubyスクリプトをデバッグする場合に使用
- Debug test: Rubyのテストコードをデバッグする場合に使用
- Attach debugger: 既に起動している Rubyプロセスをデバッグする場合に使用

今回の場合は、`Debug script`を使用します。


</details>

<details><summary>Pythonの場合</summary>

`create a launch.json`をクリックした際に、以下のように表示されるかと思いますので、`Python File`を選択しましょう。

![select_python_file.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/a47f359e-77f7-1371-5811-da3fe1a12f54.jpeg)

そうすると、以下のようなlunch.jsonを作成されるかと思います。

```json:lunch.json
{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python Debugger: Current File",
      "type": "debugpy",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal"
    }
  ]
}
```

このファイルを簡単に説明すると、`configurations`にはデバックの構成が記述されており、現在`Python Debugger: Current File`があります。

今回はこちらを使用します。

</details>

これでデバックの準備は整いました。

サイドバーに`実行とデバック`と表示されていて、その横に再生ボタンとどのデバックの構成を使用するかが表示されているかと思います。

![debug_start.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/1164b9ac-8ca9-0409-da24-f483acc92a6c.jpeg)


Rubyの場合は、`Debug Script`、Pythonの場合は、`Python Debugger: Current File`と表示されていればOKです。

この状態で再生ボタン、もしくは`fn` + `F5`キーを押すと、デバックが開始されます。
VSCodeの右側、もしくは下側にコンソールが表示され、`10`と出力結果が表示されるかと思います。コンソールが表示されない場合は、`option` + `command` + `B`を押すと表示されるかと思います。

ここまでは、通常と変わらず、ただコードが実行されただけですが、**ブレイクポイント**を設置することで、詳細なデバックが可能となります。
**ブレイクポイント**の設置方法は、行数の左側をクリックすることで設置する事ができます。もしくは設置したい行数を選択した状態で`fn` + `F9`キーを押すことで設置することができます。では、`result`を定義している行にブレイクポイントを設置しましょう。
設置すると、行数の左側に赤丸が表示されるかと思います。

![breakpoint.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/0346b5e4-6f7c-00ba-a43e-4d8a9c6727d5.jpeg)


この状態で`fn` + `F5`キーを押し、デバックを開始すると、ブレイクポイントで処理が中断していることが分かります。
左側を見ると、その時点で定義されている変数を確認することができます。
そして、上部に以下画像の様なツールバーが表示されているかと思います。


- Rubyの場合

![ruby_tool.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/ba060046-5c8b-3506-c6ac-d8d52f07b093.jpeg)

- Pythonの場合

![python_tool.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/86b51ee5-d617-7447-6f7f-442d840d4c0e.jpeg)

上記のボタンについて、共通のボタンについて説明していきます。

***

### 続行(fn + F5キー)

![continue.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/6498ae9c-79f8-fad0-6637-101c5750a948.jpeg)

次のブレイクポイントまで処理を進めます。
`puts・print`の行にブレイクポイントを設置しましょう。
現在`result`の行にて処理が止まっているかと思いますが、`fn` + `F5`キーを押すと、`puts・print`の行で処理が止まります。再度`fn` + `F5`キーを押すと、処理が最後まで実行されるかと思います。

![debug_continue.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/80727d46-c187-ccf0-586b-c526aa3ffe67.gif)


### ステップオーバー(fn + F10キー)

![step_over.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/715e38b1-dedd-2887-ca00-3e402d2f40de.jpeg)

停止位置の処理を行い、次の行に移動します。`result`の行にて処理が止まっている状態で、`fn` + `F10`キーを押すと、`puts・print`の行で処理が止まります。

![debug_stepover.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/bf5bea74-2bd9-9b72-e1fb-9ba06ec01cdf.gif)


### ステップイン(fn + F11キー)

![stepin.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/6d0da98d-20a2-d0a9-6d29-a0a34815ff27.jpeg)

停止位置の処理を行い、次の行に移動します。ステップオーバーとの違いは、現在の停止位置で関数やメソッドが呼び出されている場合は、関数内の最初の行で処理が止まります。`result`の行にて処理が止待っている状態で、`fn` + `F11`キーを押すと、`sum_of_evens関数`内の最初の行にて処理が止まります。

![debug_tepin.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/70381580-6daf-5e9f-be87-08348c4f9d9f.gif)


### ステップアウト(shift + fn + F11キー)

![stepout.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/cec96240-f48c-f912-26ee-3ac24be331c5.jpeg)

停止位置が関数やメソッド内の場合は、現在の関数やメソッドの実行を完了させて、呼び出し元に戻ります。ステップインを実行し、`sum_of_evens関数`内の最初の行にて処理が止まっている状態で、`shift` + `fn` + `F11`キーを押すと、`result`を定義している行に移動します

Rubyの場合は、`sum_of_evens関数`の`end`にフォーカスがされるかと思いますが、内部的には`sum_of_evens`の処理が終わった状態で停止しています。ですので、再度ステップオーバーもしくはステップインを行うと、`puts・print`の行に移動します

![debug_stepout.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/cfd867ca-ffec-c313-0dc4-286a6c6b42e3.gif)

### 再起動(command + shift + fn + F5キー)

![debug_reload.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/6828089b-535a-7650-da4b-97291f5ed710.jpeg)

デバックを最初の行からやり直します。`fn` + `F5`キーを押し、デバックを開始後、`command` + `shift` + `fn` + `F5`キーを押すと、最初の行から処理をやり直し、`result`の行にて処理が止まります。

### 停止(shift + fn + F5キー)

![stop.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/c9023011-41f4-922b-5248-f7c658043d61.jpeg)

デバックを中断します。
`fn` + `F5`キーを押し、デバックを開始後、`shift` + `fn` + `F5`キーを押すと、デバックが中断されます。

***

では、上記のコマンドを使いこなし、今回のエラーをデバックしていきましょう。

今回は`result`が期待する値にならないので、`sum_of_evens関数`内の処理が間違っていると分かっている状態です。
ですので、`sum_of_events関数`内の処理を確認していきます。
`result`を定義している所にブレイクポイントを設置した状態で、デバックを開始し、ステップインして、`sum_of_events関数`内に入ります。

![func_internal.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/3ab11117-0156-7941-a5ee-473c8ef63750.jpeg)

さらにステップインをして、ループ処理内に入ります。初回のループ内処理は`num`に`1`が格納されている事が分かります。条件分岐は`false`になるので、初回のループ内処理は終わります。

![loop_debug.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/ba92bc84-1503-50e1-f1bc-85a824f30120.jpeg)

2回目のループ内処理は`num`に`2`が格納されており、条件式は`true`となり、条件式内の処理に入っていることが分かります。
そして、`sum`に`2`が格納されていることが分かります。

![loop_debug.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/1a3d62ff-6403-b2ab-b46c-5ecf7315caf3.gif)

3回目のループ内処理では、条件式が`false`になるので、`sum`は変化しません。
4回目のループ内処理は、条件式は`true`となり、条件式内の処理が実行されますが、この時に`sum`の値を確認すると`4`が格納されていることが分かります。

![loop_debug2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/8858f332-d8c7-36f2-a9e9-f60bc7e0bcc4.gif)

上記のことから、`sum`に加算ではなく、代入するようになっているので、`sum += num`と修正すれば良いことが分かりました。
ここまでは、単純なスクリプト実行の場合の方法で、
RailsやDjangoのデバックは方法が異なります。

# Railsのデバック方法

※これからRailsアプリを作成する際や、既存で作成したアプリにて是非試してみてください。

Railsは`debug`という`gem`を使用することをオススメします(Rails7以降はデフォルトでインストールされるようになっています)。
`debug`がインストールされていれば、通常通り`rails s`で起動し、
デバックしたい箇所に`debugger`を記述することで、そこで処理を止めることができます。

例えば、tasksコントローラのindexアクションに`debugger`を記述し、`/tasks`にアクセスします。

```rb:tasks_controller.rb
class TasksController < ApplicationController
  before_action :set_task, only: %i[ show edit update destroy ]

  # GET /tasks or /tasks.json
  def index
    @tasks = Task.all
    debugger
  end
  
  ...
end
```

そうすると、ページは読み込み状態のままで、レンダリングされず、`rails s`実行中のコンソールにて、以下画像の様に表示され、最下部には`(rdbg)`と表示されているかと思います。

![task_list_debug.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/1923cb2e-4ddc-df70-5c32-301862e460c5.jpeg)

ここでは、Rubyコードを実行できます。また、現在実行中の処理内の変数を参照することができます。上記の例だと`@tasks`を参照する事ができます。

```
(rdbg) @tasks

[#<Task:0x0000000104ed16b0
  id: 1,
  title: "タイトル1",
  content: "",
  created_at: Thu, 28 Nov 2024 13:03:54.774480000 UTC +00:00,
  updated_at: Thu, 28 Nov 2024 13:03:54.774480000 UTC +00:00>,
 #<Task:0x0000000106811418
  id: 2,
  title: "タイトル2",
  content: "",
  created_at: Thu, 28 Nov 2024 13:04:05.514263000 UTC +00:00,
  updated_at: Thu, 28 Nov 2024 13:04:05.514263000 UTC +00:00>]
```

`c`と入力すると、次の`debugger`と記述されているところまで処理を進めます。

`debug`のgemの詳細な使用方法は、以下Railsガイドを参照してください。

https://arc.net/l/quote/lwsulwyt

VSCodeを使用した方法もあるのですが、デバックのしやすさでは、gemを使用するほうが良いです。一応、以下にVSCodeを使用した方法を載せておきます。以下を`lunch.json`の`configurations`にセットすることで、デバックができます。

```json:lunch.json
...
    {
      "type": "ruby_lsp",
      "name": "Rails Server",
      "request": "launch",
      "program": "bin/rails server"
    }
...
```

# Dockerを使用しているRailsのデバック方法

Dockerを使用しているRailsで、gemの`debugger`を使用する場合、対話型操作が必要となるため、まず`docker-compose.yml`のRailsのコンテナに以下のオプションを追記する必要があります。
```yml:docker-compose.yml
...
services:
  web: # Railsのコンテナ
    ...
    tty: true
    stdin_open: true
....
```

そして`docker compose up`で起動させます。
次に別タブでターミナルを開き、`docker compose ps`で起動しているコンテナを確認します。
```
docker compose ps

CONTAINER ID   IMAGE                   COMMAND                  CREATED          STATUS          PORTS                    NAMES
427bfc7d4748   rails_template:latest   "entrypoint.sh bundl…"   22 seconds ago   Up 21 seconds   0.0.0.0:3000->3000/tcp   rails_template-web-1
...
```
このコンテナ名を指定して、`docker attach rails_template-web-1`を実行して、コンテナにアタッチします。
```
docker attach rails_template-web-1
```

これで、`Railsのデバック方法`のセクションと同じ流れで、
コード中に`debugger`を記述し、その処理が動くようにページアクセスを行うと、処理が止まり、`docker attach`を実行したコンソールで、Rubyコマンドを実行することができます。


# Djangoのデバック方法

※これからDjangoアプリを作成する際や、既存で作成したアプリにて是非試してみてください。

VScodeにて`create a launch.json`をクリックした後に、`python Debugger`を選択、`Django`、`manage.py`を選択しましょう。

![create_lunch_json.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/102a9962-b7d2-caca-e78b-2eb90e2bdd64.jpeg)

![create_lunch_django.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/30f26da2-a0e8-08f5-cc66-dd13865f6f94.jpeg)

![create_lunch_manage.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/1e2aea95-424c-610c-e2da-5e346c378693.jpeg)

そうすると、以下のようなlunch.jsonが作成されます。

```
{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python デバッガー: Django",
      "type": "debugpy",
      "request": "launch",
      "args": [
        "runserver"
      ],
      "django": true,
      "autoStartBrowser": false,
      "program": "${workspaceFolder}/manage.py"
    }
  ]
}
```

後は、Pythonスクリプトのデバック時と同様に、ブレイクポイントを設置することで、処理を止めることができます。例えば、TaskListViewの処理にブレイクポイントを設置し、`/tasks`にアクセスすると、ページは読み込み状態のままで、レンダリングされず、処理が中断し、左側で変数の確認ができます。後は、ツールバーのコマンドを使いデバックを行います。

![django_debugger.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/bdd8f904-1bbb-42e2-0740-cfaadc9277ac.jpeg)


# Dockerを使用しているDjangoのデバック方法

まず、`Dev Containers`という拡張機能をインストールしましょう。

![dev_containers.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/bd947041-7076-885a-47f8-2ca7c42eaf18.jpeg)

次に、左下のアイコンをクリックし、`Add Dev Container Configuration Files...`をクリックし、

![add_container_file.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/ef845336-1c78-e35a-62cf-06dc92208bd8.jpeg)

`Add configuration to workspace`を選択し、

![add_configuration.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/38655afe-5c9d-adea-ed47-3eb1819ad1fc.jpeg)

`From docker-compose.yml`を選択し、

![from_docker_compose.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/348d9521-50a6-f146-9254-a7878b98b2f1.jpeg)

後の2回は何も選択せずOKを押してください

![select_feature.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/e136a7ff-d9c5-d364-ac0b-67868565d230.jpeg)

![ignore_file.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/f4c3f34a-68ee-2ff5-32ed-f3e29090204a.jpeg)

これで、`.devcontainer/devcontainer.json`ファイルが作成されます。

また、.vsocode/lunch.jsonを以下のように修正しましょう。
```diff:lunch.json
{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python デバッガー: Django",
      "type": "debugpy",
      "request": "launch",
      "args": [
        "runserver",
+       "0.0.0.0:8001"
      ],
      "django": true,
      "autoStartBrowser": false,
      "program": "${workspaceFolder}/manage.py"
    }
  ]
}
```

"0.0.0.0:8001"と記述しましたが、`docker compose up`で使用しているポート以外であれば、大丈夫です。
これで設定は完了です。
通常通り`docker compose up`でコンテナを起動しましょう。

次にVSCodeの左下のアイコンをクリックし、`Attach to Running Container...`を選択し、

![attach_container.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/5330dd4e-5f8b-4965-33c1-531fd08660bd.jpeg)

先ほど起動したコンテナを選択しましょう。

![select_container.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/c642f73e-f4c7-22e2-007b-37141c99151e.jpeg)

そうすると、新しいVSCodeウィンドウが開きます。
これはコンテナに接続して、コンテナ内をVSCodeで開いている状態です。
このVSCodeにてDjangoアプリのディレクトリを開きましょう。
左側の一番上のアイコンをクリックし、`Open Folder`をクリック、入力欄にDjangoアプリを配置しているディレクトリを入力して、`OK`を押しましょう。
Djangoアプリを配置しているディレクトリは、基本的にDockerfileの`WORKDIR`に配置しているかと思います。例えば、`WORKDIR /app`の場合は、 `/app/`と入力します。

![select_dir.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/afed6ba4-70f1-4710-737c-58bd75ff4b9b.jpeg)

左側にDjangoアプリのディレクトリ構成が表示されていればOKです。

![django_dir.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/f39e6398-8c30-f74b-3569-7fba3eaace91.jpeg)

次に、コンテナ内を開いているVSCodeにて、Pythonの拡張機能をインストールしましょう。

![python_install.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/17c9a6f2-26bd-7c9a-60af-2cb8aa0c3f70.jpeg)

そして、コンテナ内を開いているVSCodeにて、デバックと実行をしましょう。

![container_debug.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/ccc5d1e9-a9c3-abfd-c42b-bd020c5b5680.jpeg)

これで、`lunch.json`で指定したポート番号(今回の場合は8001)にアクセスできるかと思います。
デバックする際は、`Djangoのデバック方法`のセクションと同じ流れで、
VSCode上にブレイクポイントを設置するのですが、注意点として、コンテナ内を開いているVSCodeにて、ブレイクポイントを設置してください。

# 終わりに

いかがだったでしょうか？
デバッグの方法を知っておくと、特に初学者にとってエラーによるストレスを軽減できます。また、デバッグを活用することでプログラムの挙動を深く理解することにもつながります。ぜひ積極的に活用してください！