プログラマーのためのデバッグ心理学：エラーとの共存

※この記事は[ビジネスエンジニアリング株式会社（B-EN-G）アドベントカレンダー2024](https://qiita.com/advent-calendar/2024/b-en-g) - 18日目の記事です。

# この文章について
デバッグは、プログラマーにとって避けて通れない日常の一部です。  
しかし、多くのエンジニアはエラーに直面した瞬間ストレスやフラストレーションを感じ、冷静さを失いがちです。  
このような心理状態では、エラーの本質を見抜き、最適な解決策を見つけることが難しくなります。  
ここでは、エラー解決の技術的手法に加え、心理学の観点からデバッグ時の心の持ち方やストレス対策に焦点を当てました。  
なぜなら、心が平穏であるときこそ、エラーに対して真摯に取り組み、より良い改善策を選択できるからです。   
エラーはプログラムの中に隠れた問題を見つけるためのヒントであり、エンジニアとしての成長のきっかけでもあります。  
この文章を通じて、エラーを「敵」ではなく「成長の友」として迎え入れる心の準備を整えていただければ幸いです。

# １．オープニング：デバッグはプログラマーの宿命
朝、コーヒー片手に「今日こそ完璧なコードを書こう」と机に向かう。
ところが最初に目に飛び込んでくるのは、赤々としたエラーメッセージ。
「またかよ…」とため息をつきながらも、よく考えてみてください。
エラーは敵ではありません。
それはあなたのコードを進化させるための試練なのです。

最初は困惑し、怒り、時に諦めそうになりますが、そのプロセスこそが成長の鍵。
エラーに立ち向かいスキルを磨いていきましょう！


# ２．エラー解決の心理的プロセス
エラーに直面した時、プログラマーは心理的にいくつかのステージを経ることになります。
それは、エラーを理解し、解決し、学びに変えるための「心の旅」です。
この章では、各ステージごとに適切な心構えと対策を紹介します。

## ショックと否認のステージ：ファーストコンタクト
エラーに遭遇した瞬間、心の中に押し寄せる「これは何かの間違いだ…」という気持ち。
まさに **「ショックと否認」** のステージです。  
### 例   
* **「バグのはずがない…絶対に動いていたはずだ！」** という思い込み
*  画面をじっと見つめ、**「何かの間違いだ…再ビルドすれば直るはず」** と試してしまう
* 「プログラムが動かない？いや、動いていた…夢の中では確かに動いていた…」
    （そして目が覚める）

### 対策 
まずは深呼吸。
そして、5分間席を立ってコーヒーを淹れてみましょう。  
脳をリフレッシュすることで、見逃していたエラーの原因が見えてくるかもしれません。

## 怒りのステージ：フラストレーションとの戦い
エラーが解決しないと、 **「怒り」** の感情が湧き上がります。  
この段階では、プログラマーはキーボードを叩きすぎる、過剰にログを出力するなどの行動に走りがちです。

### 例
* 「もう、何で動かないんだよ！」と叫びつつ
意味のない`Console.WriteLine("ここまで来た");`を大量追加
* 「またこれか！昨日も直したのに！」
*  **「どうしてコンパイラで教えてくれないんだ！」**

### 対策
キーボードを投げない、叩かない。 **（本当に壊れます）**
短い瞑想や呼吸法、あるいは気分転換に短い散歩をしてみましょう。  
怒りの感情が高ぶると視野が狭くなり、解決策が見えなくなります。

## 妥協のステージ： 迷いと向き合うとき
次に訪れるのは、 **「もうこれで良しとしよう…」** と妥協してしまいたくなる段階です。
 しかし、ここで諦めると後々のデプロイ時に大問題が発生することも。

### 例
* 心の中では **「本当にこれでいいのか…？」** と不安に思いつつも
    **動いているからヨシ！** とコミットする
* 「大きな問題ではないなら、後回しにしよう…」

### 対策
「5 Whys」メソッドを使い、なぜ問題が発生しているのかを掘り下げます。
問題の原因を突き止めると、次回同じ問題に遭遇しにくくなります。
必要ならペアデバッグを試し、別の視点で問題を確認します。

## 受容と再生のステージ
最後の段階は、問題が解決したときの **「受容」** です。
この瞬間プログラマーは無限のエネルギーを取り戻し、 **「もっとコーディングしたい！」** 
という気持ちになります。

### 例
* エラーが解消した瞬間「やったぜ！」と声を上げ、周囲の同僚が拍手…
（実際は誰も気にしていない）
* バグが解消すると、プログラマーは一瞬だけ超人になれる…が
次のバグが現れるのに3分とかからない

### 対策
解決したエラーについて記録を残しましょう。
具体的な手順や原因をメモすることで、将来の自分やチームの役に立つ「財産」となります。

# ３．心を軽くするデバッグテクニック
エラー解決までの道のりは時に苦しいものです。
デバッグのストレスを軽減し、問題を解決するためのテクニックを身に着けることも大切ですね。
そこで古典的な「Rubber Duck Debugging」と、現代の「ChatGPT先生」を活用する方法を組み合わせてみましょう。

## Rubber Duck Debugging（ラバーダックデバッグ）
デバッグ手法の古典にして名作、それが「Rubber Duck Debugging」です。
ラバーダック（おもちゃのアヒル）に向かって問題点を説明するだけで、思考が整理され、解決策が浮かぶことがあります。
### 手法
1. ラバーダックをデスクに置きます
1. 問題の原因や疑問点を丁寧に説明します
1. 声に出して説明することで、頭の中が整理され、思いがけない気づきが得られます

### ポイント
ラバーダックは無口ですが、何かを教えてくれることがよくあります。  
むしろ、彼が答えを知っているかのように思える瞬間も。  
実際にやるのが恥ずかしい人は、ペットでも代用可能。

## ChatGPT Debugging（ChatGPT先生の活用）
Rubber Duck Debuggingにデジタルの力を加えた進化形、それが「ChatGPT Debugging」です。
ChatGPT先生にエラーコードを投げるだけで、驚くほど速く解決に近づくことがあります。

### 手法
* エラーコードをそのまま投げる  
    StackTraceやエラーメッセージをそのままChatGPTに貼り付けてみましょう。
    驚くべきことに、ChatGPT先生はすぐに有用なアドバイスをくれることがあります。
* 質問形式で解決を加速  
    単にエラーを投げるだけでなく、「このエラーの意味は？」「どこを修正すべき？」と具体的な質問を添えるとさらに効果的です。

### ポイント
ChatGPT先生に聞くときは『先生』と呼ぶのが礼儀。   
たまにジョークも返してくれます。   
ChatGPT先生のアドバイスを試したら、その結果を報告してみましょう。   
改善案を追加で教えてくれるかもしれません。   

# ４．エラー解決の実践的アプローチ：技術で心を支える

ここまで、エラーに直面したときの心理的な対処法やマインドセットについてお話ししました。  
とはいえ、「心構えは分かったけど、実際どうすればいいの？」という声も聞こえてきそうです。  
ここからは、Azureなどを例に実際にエラーと向き合うための具体的な技術的アプローチを紹介します。  
心理的なアプローチと併用することで、エラーとの戦いがよりスムーズになるはずです。

## クラッシュダンプとプロファイリングで根本原因を探る
### クラッシュダンプの取得    
Azure Web Appsでは、`dotnet-dump`を直接利用できませんが、Kuduの「Process Explorer」を使用してクラッシュダンプを取得できます。  
これをローカル環境で解析することで、問題の根本原因を詳細に把握できます。
#### 手順

1. Azureポータルで「Advanced Tools」>「Go」をクリックしてKuduにアクセスします。
1. 「Process Explorer」で該当プロセスを選択し、ダンプを取得します。
1. ダウンロードしたダンプファイルをVisual StudioやWinDbgで解析します。

### 動的プロファイリングで深掘り
プロファイリングツール（`dotnet-trace`や`perfview`）を使用して、アプリケーションのメモリ使用量やCPU消費の異常を特定します。  
Azure Web Appsでプロファイリングを行う場合、Azure MonitorとApplication Insightsを連携させることで、リアルタイムにリソースの挙動を観察できます。

## Application Insightsでの高度なエラートラッキング
Application Insightsを利用すると、Azure環境でのエラーを詳細にトラッキングし、問題箇所を迅速に特定できます。

### 例外と依存関係エラーの可視化
Application Insightsはアプリケーション内の例外だけでなく、外部サービスとの通信エラー（例: データベースやAPIの失敗）も可視化します。

```csharp:実装例
var telemetryClient = new TelemetryClient();
telemetryClient.TrackException(new ExceptionTelemetry(ex));
```
### Kustoクエリによる詳細な分析
エラーのトレンドや特定の条件を分析するため、Kustoクエリを使用します。
過去のエラーを取得したり、30日間のリクエストを基に、未来7日間のリクエスト数を予測することもできます。
```kusto:例: 過去1時間の500エラーを発生させた依存サービスを特定
dependencies
| where resultCode == "500"
| summarize count() by target, operation_Name
```
```kusto:例: Webアプリケーションのリクエスト数を予測
requests
| where timestamp >= ago(30d)
| summarize TotalRequests = count() by bin(timestamp, 1d)
| order by timestamp asc
| summarize TotalRequests = make_list(TotalRequests), Timestamps = make_list(bin(timestamp, 1d))
| extend Forecast = series_decompose_forecast(TotalRequests, 7)  // 予測を7日間作成
| mv-expand Timestamp = Timestamps to typeof(datetime), TotalRequests to typeof(long), Forecast to typeof(long)
| project Timestamp, Actual = TotalRequests, Predicted = Forecast
| render timechart
```


### アラートと通知の設定
特定のエラー条件（例: 例外頻度の急増や特定のHTTPステータスコード）に基づいて、Azure Monitorでアラートを設定できます。
これにより、問題発生時にリアルタイムで通知を受け取ることが可能です。

## 分散トレーシングで複雑なエラーを解明
マイクロサービスや分散システムにおけるエラーを特定するには、分散トレーシングが有効です。

### OpenTelemetryの導入
OpenTelemetryを活用することで、リクエストの流れをエンドツーエンドで可視化します。Azure Application Insightsとも連携可能です。

```csharp:例
services.AddOpenTelemetryTracing(builder =>
{
    builder.AddAspNetCoreInstrumentation()
           .AddHttpClientInstrumentation()
           .AddAzureMonitorTraceExporter();
});
```

### 相関IDを利用したログの関連付け
サービス間のリクエストを追跡するために、相関ID（Correlation ID）を付与します。   
これにより、複数サービス間のエラーの関連性を把握できます。

```csharp:例
using (var scope = logger.BeginScope(new Dictionary<string, object> { { "CorrelationId", correlationId } }))
{
    logger.LogInformation("Processing request {RequestId}", requestId);
}
```

## カオスエンジニアリングでシステムの堅牢性を試す
エラーが発生してから解決するだけでなく、事前に、エラーに耐えるシステムを構築するための実験を行う。  
それがカオスエンジニアリングであり「Azure Chaos Studio」の役割です。
### 目的
カオスエンジニアリングを実践することで、システムの弱点を事前に発見し、修正します。  
これにより、本番環境での障害を未然に防ぐことが可能です。
### 主な機能
仮想マシンやコンテナに負荷をかけ、システムの挙動を観察する。
ネットワーク遅延やデータベースエラーなどをシミュレーションする。


```json:例：ネットワーク遅延のシミュレーションをするための設定
{
    "targetResourceId": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}",
    "experimentSteps": [
        {
            "stepName": "Inject network delay",
            "actions": [
                {
                    "actionType": "networkDelay",
                    "parameters": {
                        "duration": "00:05:00",
                        "delay": "500ms"
                    }
                }
            ]
        }
    ]
}
```
## Azure Snapshot Debuggerでリアルタイムに問題を解析する
本番環境で発生するエラーを素早く解析するには「Snapshot Debugger」が最適です。  
これは、実行中のアプリケーションのスナップショットを取得し、問題を特定するためのツールです。
アプリケーションにインストルメンテーションを追加することで詳細な情報を得ることができるようになります。

### 手順
1. AzureポータルでSnapshot Debuggerを有効にする
1. アプリケーション内で特定の条件が発生した際にスナップショットを収集
1. Visual StudioまたはAzureポータルでスナップショットを解析

```csharp:例:アプリケーションにインストルメンテーションを追加
services.AddApplicationInsightsTelemetry();
```

# ５．技術と心の調和：次へのステップ
心理的アプローチと実践的な技術を組み合わせることで、エラーは単なる障害ではなく、システム改善のきっかけとなります。  
最近の言語に搭載されている洗練された例外処理とAzureなどのサービスの強力なツールを活用すれば、エラー解決の効率を大幅に向上させることができます。  
エラーに向き合うことは、プログラマーにとって必要不可欠なスキルです。  
エンジニアとして、次のエラーを「克服すべき新しい挑戦」と捉えられるよう、ぜひこれらのテクニックを実践してみてください！    
<br>
# X.番外編：エラーを人間関係に例えると？
※こちらは番外編であり、個人の感想です

## `NullPointerException` ： 突然冷たくなる友達
「どこが悪いのか何も教えてくれない、ツンデレ」  
最もおなじみのエラーである。
まるで親しいと思っていた友達が急に距離を置き始めたかのよう。
### 対策
本当にNullじゃない？と確認する癖をつけましょう。  
C#においてはNullable Reference Typesを活用することで安全性が高まります。

## `Segmentation Fault` ： 不意に現れる裏切り者
「信じていたのに…。そんなところにアクセスするなんて…」  
メモリ管理が絡むエラーは、何も悪気がないように見えるコードが裏切りの行為をしてくるように感じられます。
### 対策
境界チェックとメモリアクセスルールの徹底以外に道はありません。  
Visual Studio 2019 ver 16.9 以降、MSVCとIDEではAddressSanitizer(ASan)がサポートされています。  
これにより発生原因の特定がしやすくなりました。

## `Stack Overflow` ： 過労死寸前の人
「あなた、やりすぎよ…。もう少し負担を減らそう…。」  
無限再起や過剰なメソッドコールでスタックが限界を迎えるとき、コードが助けを求めてきます。
### 対策
再起処理の停止条件を明確に定義しましょう。   
Tail Call Optimizationが使える場合は利用することで軽減できます。

## `OutOfMemoryException` ： 財布が空っぽの友達
「お金（メモリ）がないって？それ、計画性のなさだろ！」
メモリ不足でアプリケーションが動かなくなる状態。  
お金の管理が苦手な友人が、突然「貸して」と言ってくるような状況です。
### 対策
メモリ使用量を定期的にプロファイルし、必要に応じてガベージコレクションを最適化しましょう。

## `UnauthorizedException` ： 警備員に止められる人
「君、ここに入るパス持ってないよね？」   
認証や認可が正しく設定されていないときに発生するエラー。  
まるで招待されていないパーティに入ろうとした瞬間です。

### 対策
認証フローを再確認し、トークンの有効期限やスコープを確認しましょう。

## `DivideByZeroException` ： 無理難題を押し付けられた人
「ゼロで割るなんて、そんなの無理に決まってるだろ！」  
数学的に不可能な0で割る操作を要求された時に発生するエラー。
理不尽な要求をされて戸惑う感覚に似ています。

### 対策
入力値を厳密に検証し、ゼロでないことを確認します。

おしまい。