エラーが出た時の考え方、相談の仕方

## はじめに

研修で新卒の方と関わることがあった際に、エラーが発生した時の対処の方法や相談の仕方があまり整理できていないようだったので、自分がエラーが出た時に取り組んでいることや気をつけていることを言語化しました。

個人差がある内容だと思いますし、私も誰かに教えていただいたわけではないので、改善点等あればコメントしていただけると幸いです。

## エラーが出た時の対応フロー

:::note
一応1から順番に対応する想定ですが、エラー内容によってはすぐ解決策がわかったり、状況把握を先に行うケースもあります
:::

### 1. まずはエラーメッセージ・エラーログを理解する

エラーメッセージやエラーログはエラーの説明書です。読みたくない気持ちはめちゃくちゃ分かりますが、しっかり読みましょう。英語の場合は、Google翻訳やChatGPTを使って日本語に変換し、内容を理解するようにします。

また、読む際には、まずどこが根本的なエラーなのかを見つけると良いです。

pythonの場合は以下のように出るので、まず最下部のエラーメッセージをみてエラーの内容を把握しましょう。次にtracebackの一番下にある自分のコードをみて、エラーが発生した処理を特定しましょう

```python
Traceback
  File .../a.py
    ...
  File .../b.py               <- 2. 一番下にある自分のコード（エラーが発生した処理を特定する）
    ...
  File .../Python/lib/c.py
    ...
  ...
xxError: <Error Message>      <- 1. エラーメッセージ（エラーの内容を一番明確に表す）
```

### 2. 状況を把握する

エラーが発生している際の状態を正確に把握することも大切です。ここで正しく状況を認識しないと、誤った方法で時間を浪費してしまうことになります。

例えば、 `PermissionError` のような外部とのやり取りを行う処理でエラーが出た場合は `ls -la` でファイルの存在や権限を確認しましょう。 そもそもファイルが存在しないのか、権限が想定外なのか、ファイルや権限は正常なのかで、大きく状況は変わります。
また、`TypeError: XXX object is not subscriptable` のようなプログラム内のオブジェクトに関するエラーであれば、オブジェクトの中身が想定通りになっているかを確認することが必要です（これを簡単に行うための方法として、デバッグのやり方を発展に記載しています）。

### 3. 検索する

エラーメッセージを見ても原因の目処が立たない場合は、エラーメッセージで検索しましょう。同じエラーに対して解決策が示されている場合があります

Google Chromeで検索する際は、””で囲うと完全一致検索できるので、利用すると素早く同じエラーを探すことができます。

なお、Stack OverflowやGitHubのIssueは信頼できる情報源なので、これらを優先的に参照しましょう

各サイトの一例を示します。
- https://stackoverflow.com/questions/76925247/vscode-issue-running-old-container
- https://github.com/containers/toolbox/issues/628

### 4. 原因を切り分ける

エラーを解決する際には、原因の範囲を絞ることが大切です。

例えば、環境の問題なのか、ライブラリの問題なのか、自分のコードの問題なのかわからないと、全部確認する必要があります。そこで、例えば、他の人の環境で動くかどうかを確認してもらい、動けば環境に依存する問題ではないと判断できます。これを繰り返して、見なければいけない範囲を狭めることが大切です。

この際に使える方法としては、以下の2種類があります

1. 対照実験を行う
    ある条件以外を全て同じにして、実験を行い、結果が変わらなければその条件は原因ではない
2. 最小限のサンプルで問題が発生するか確認する
    あるライブラリを利用するだけの単純なコードを実行し、それでもエラーが発生すれば、その原因はライブラリに起因すると判断できる

### 5. 施策を試す

ここまでで原因の目処が立ったら、その解決策を試してみましょう。

### 6. 人に聞く

1~5を試しても解決できない場合には、人に聞くことも大切です。一般的には、15分ほど一人で悩んだら他人に相談したほうが良いと言われています

この時、1~5の内容も一緒に伝えるようにしましょう。相談する際のメッセージの例は以下のようになります

```text
@<相談先の人>
お疲れさまです。〇〇の処理で××というエラーが出ているのですが、どうすれば良いでしょうか？

[エラー内容]
<エラーメッセージ>

[状況]
<実行環境や手順を簡単に書けるとよい>
<エラーに関連する状況（ls -laの結果など）>

[類似のエラー]
<検索して出てきた類似のエラーのリンクと概要>

[原因]
（〇〇なので、）△△が原因なのではと考えています。

[試したこと]
〇〇を実行してみましたが、△△というエラーが出ました。

[依頼事項]
思い当たる原因はありますでしょうか？/解決策を教えてください etc.

<文字だけでは伝えづらい場合は、エラーの画面や動画があれば添付すると効果的>
```

:::note
チャットを書くのに必要以上に時間を取るのはもったいないので、面識がある相手であれば極端に形式ばった書き方である必要はありませんが、必要な情報は盛り込みましょう
:::

## 発展

### 1. エラーログの出し方を工夫してみよう

エラーが出た時のためにできることとして、エラーログの出し方を工夫しておくことも可能です。エラーが発生した時、必要なログ情報が出ていますか？逆に関係ないログが大量に出ていませんか？

ログはエラーが発生した場合のみ見たいものも多いので、loggerを利用してレベルでログを出すか制御することが望ましいです

以下にPythonでの設定例を示します。

```python:logging.py
import logging
import os
import sys
from logging.config import dictConfig

import yaml  # type: ignore

LOGGER_CONFIG_FILE = "logger_config.yaml"  # 環境変数などで、ログファイルを指定

def _is_exc_info() -> bool:
    return sys.exc_info()[0] is not None

class DefaultLogger(object):
    """Logger object.

    ログを出力する際はこれを利用すること

    Example:
        >>> logger = DefaultLogger(__name__)
        >>> logger.debug("debug message")
    """

    def __init__(self, name: str) -> None:
        self.logger = logging.getLogger(name)

    def debug(self, msg: object) -> None:
        # stacklevel=2を指定することで、このメソッドを呼び出したメソッドの情報を出力する
        # エラーが発生している場合のみexc_infoを付与する
        self.logger.debug(msg, stacklevel=2, exc_info=_is_exc_info())

    def info(self, msg: object) -> None:
        self.logger.info(msg, stacklevel=2, exc_info=_is_exc_info())

    def warning(self, msg: object) -> None:
        self.logger.warning(msg, stacklevel=2, exc_info=_is_exc_info())

    def error(self, msg: object) -> None:
        self.logger.error(msg, stacklevel=2, exc_info=_is_exc_info())

    def critical(self, msg: object) -> None:
        self.logger.critical(msg, stacklevel=2, exc_info=_is_exc_info())

def init_logger() -> None:
    """Loggerを初期化する"""
    logger_config_path = os.path.join(os.path.dirname(__file__), LOGGER_CONFIG_FILE)
    dictConfig(yaml.load(open(logger_config_path).read(), Loader=yaml.SafeLoader))

init_logger()
```


```yaml:logger_config.yaml
version: 1

formatters:
  simple:
    format: "%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s"

handlers:
  console_handler:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple

loggers:
  src:
    level: DEBUG
  __main__:
    level: DEBUG

root:
  level: WARNING
  handlers: [console_handler]

disable_existing_loggers: False
```

### 2. デバッグする

VSCodeなどのエディタを利用していると、デバッグ機能を利用することができます。各行での変数の状態を確認できるので積極的に利用することをお勧めします

VSCodeの場合は、.vscode/launch.jsonに以下のようなファイルを置くと、F5キーでデバッグ実行が可能です。

```json:.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "debugpy",
      "request": "launch",
      "program": "${file}",
      "console": "internalConsole",
      "justMyCode": false
    },
    {
      "name": "Python: Debug Test",
      "type": "debugpy",
      "request": "launch",
      "purpose": ["debug-test"],
      "console": "internalConsole",
      "justMyCode": false
    },
    // Webフレームワークなど特定のエンドポイントがある場合は以下のようにしてください
    {
      "name": "FastAPI: サーバーを起動",
      "type": "debugpy",
      "request": "launch",
      "module": "fastapi.app",  // python -m fastapi.appに相当する
      "console": "internalConsole",
      "justMyCode": false
    },
  ]
}
```

### 3. 公式ドキュメント/ソースコードを読む癖をつけよう

QiitaやZenn、ChatGPTの情報は誤っている可能性があります。できれば公式ドキュメントをたどりましょう。

また、Pythonを使う場合はライブラリの中のソースコードを見ることもできます（VSCodeならF12）。ソースコードは公式ドキュメントよりも正確な挙動を表すので、特に公式ドキュメントが充実していないライブラリや更新頻度が高くドキュメントが追いついていないライブラリの場合はソースコードを見ることが重要です。

### 4. GitHubで検索する

マイナーな機能を利用する場合は実装例をGitHubで検索してみましょう。ドキュメントはなくても実装例が見つかることがあります。ただし、GitHubの実装は正確性が保証されないことに注意してください。

- /word word/で完全一致検索
- path: hogeでパス（ファイル名）の絞り込み