エラーの読み方をマスターしよう！

## はじめに

**自己紹介**

　未経験からWEB系エンジニアへの転職を目指している者です。現在はRuby on Railsで学習を進めています！

　Progaku Advent Calendar 2023 15日目の記事を担当させていただきました。初投稿ですので拙い記述や誤記もあるかと思いますがご容赦ください。

**この記事を書いたきっかけ**

　プログラミングを教えてもらっている友人に「エラー読むの苦手そうだから、この本読んでみたらいいかもよ～」とオススメされた『コードが動かないので帰れません！新人プログラマーのためのエラーが怖くなくなる本』を読んだので、エラーの読み方についてピックアップして内容をまとめてみました。
 

## エラーを避けてしまう理由

　エラーを避けてしまう理由は主に、以下の3つが上げられるそうです。

- 英語で書かれている
- 何行にも渡るエラーが出る
- エラーを読んでもすぐに原因がわからない

　実際、僕は「英語でかかれている」、「何行にも渡るエラーが出る」の2点を理由にエラーを避けていたなーと思いましたので、本記事では自戒の念を込めて、本に書かれていた対処法を自分なりにまとめました。

## エラーでよく使われる英単語を覚えよう！

　どの言語を使っていてもエラーは英語で書かれています。しかし、文章のフォーマットが決まっており、よく使われる単語も限られています。

　例として、本の中で「エラーに登場しやすい単語」がまとめられていたので、下表に引用させていただきます。他にも、「プログラミング特有の意味を持つ単語」もまとめられていました。気になる方はぜひ本を読んでみてください。

　英語も暗記も苦手な自分ですが、エラーをスラスラ読めるようになるためにも、英単語をしっかり覚えたいと思います！

**表1 エラーに登場しやすい単語**

| 単語 | 意味 |
| --- | --- |
| valid / invalid | 有効な / 不正な |
| expected / unexpected | 予期した、期待された / 予期しない |
| defined / undefined | 定義された / 未定義の |
| declared / undeclared | 宣言された / 未宣言の |
| reference | 参照 |
| require | 必要とする |
| deprecated | 非推奨な |
| expired | 期限切れの |
| apply | 適用する |
| deny | 拒否する |
| permission | 拒否する |
| range | 範囲 |
| missing | 見当たらない |

※「コードが動かないので帰れません！　新人プログラマーのためのエラーが怖くなくなる本」P14、表1-1 エラーに登場しやすい単語 より引用

## エラーの構成と種類を知ろう！

　エラーが何行にもに渡って表示されると、「うわっ」と思って読むのを躊躇してしまいます... 実際、僕も長文のエラーが出たらテキトーに読み飛ばしてたことがありますw
 
　しかし、「エラーの構成要素」と「エラーの種類」の2点を知っておけば、長文のエラーからもどのようなエラーが発生しているか読み取ることができます。
 
　本ではJavaScriptを例に解説されていたので、この記事では現在、自分が勉強しているRubyを例にまとめました。

### 1. エラーの構成要素

　エラーは「エラーの種類」、「エラーメッセージ」、「スタックトレース」という3つの要素で構成されています。
 
　例として、以下のサンプルコードを実行した際のエラーを見てみます。
　（サンプルコード中のundefined_methodメソッドは、自分で定義していないメソッドです。）

```ruby:サンプルコード (sample.rb)
def method_1
  puts'method_1が呼ばれたよ'
  method_2
end

def method_2
  puts 'method_2が呼ばれたよ'
  method_3
end

def method_3
  puts 'method_3が呼ばれたよ'
  undefined_method
end

# method_1を呼び出す
method_1
```

```:実行結果
$ ruby sample.rb
method_1が呼ばれたよ
method_2が呼ばれたよ
method_3が呼ばれたよ
sample.rb:13:in `method_3': undefined local variable or method `undefined_method' for main:Object (NameError)
        from sample.rb:8:in `method_2'
        from sample.rb:3:in `method_1'
        from sample.rb:17:in `<main>'
```
　実行結果からエラーの構成要素を見ると、次のようになります。
 
#### エラーの種類
　実行結果の5行目の末尾にある`(NameError)`がエラーの種類にあたります。

　サンプルコードでは未定義のメソッドを参照しようとしたことがエラーの原因だと分かります。
    
#### エラーメッセージ
　実行結果の5行目がエラーメッセージにあたり、具体的な原因が記載されています。

>```
>sample.rb:13:in `method_3': undefined local variable or method `undefined_method' for main:Object (NameError)
>```

　サンプルコードでは`method_3`の中で`undefined_method`を呼び出していますが、メソッドが未定義のため、`NameError`が発生しています。

#### スタックトレース
　実行結果の6行目～8行目がスタックトレースにあたります。

> ```
>from sample.rb:8:in `method_2'
>from sample.rb:3:in `method_1'
>from sample.rb:17:in `<main>'
>```

　スタックトレースはエラーが発生するまでの処理の流れを表したもので、プログラム内のメソッドがどの順番で呼ばれたかを示す履歴情報です。ちなみにスタック(stack)は「山積みにされたもの」、トレース(trace)は「足跡、記録」という意味を持ちます。
　サンプルコードを例に考えてみると、最初に`method_1`が呼ばれ、その内部で`method_2`が呼ばれるという順番で履歴が山積みにされていきます。
 　実際、実行結果の6行目～8行目を見ると、メソッドが呼ばれた順番にスタックトレースが表示されていることが分かります。

### 2. エラーの種類

　Rubyでよく出てくるエラーを表にまとめてみました。
　一度に覚えるのは大変ですが、少しずつ慣れていきたいです！

**表2 Rubyでよく出てくるエラー**

| エラーの種類 | 概要 |
| --- | --- |
| NameError | 未定義の変数やメソッドを参照しようとしたときに発生 |
| NoMethodError | 存在しないメソッドが呼び出された場合に発生 |
| SyntaxError | Rubyの文法に違反しているときに発生 |
| ArgumentError | メソッドに対して期待される引数の数や型が合わない場合に発生 |
| TypeError | オブジェクトの型が期待される型と一致しない場合に発生 |
| ZeroDivisonError | 整数を0で割り算しようとした場合に発生 |
| LoadError | 必要なファイルやライブラリをロードしようとしたが見つからない場合に発生 |
| RuntimeError | 特定の例外クラスに分類されない一般的な問題が発生したときに発生 |

　エラーへの理解を深めるため、実際に上記のエラーをraiseしてみました。(NameErrorはエラーの構成要素の解説で扱ったので割愛しています)
 
#### NoMethodError
```ruby:サンプルコード (raise_nomethod_error.rb)
class Greeting
  def self.hello_method
    puts 'Hello!'
  end
end

# 存在するメソッドを呼び出す 
Greeting.hello_method
# 存在しないメソッドを呼び出す
Greeting.morning_method
```

```:実行結果
$ ruby raise_nomethod_error.rb
Hello!
raise_nomethod_error.rb:10:in `<main>': undefined method `morning_method' for Greeting:Class (NoMethodError)
```
　`Greeting`クラスには`hello_method`がありますが、`morning_method`は定義されていません。そのため、`object.morning_method`を呼び出したとき`NoMethodError`が発生しています。

#### SyntaxError
```ruby:サンプルコード (raise_syntax_error.rb)
def odd_number?(number)
  if number.is_a?(Integer)
    number % 2 == 1
  else
    return false
  # ここにendが必要
end

# odd_number?メソッドを呼び出す
puts odd_number?(3)
```

```:実行結果
$ ruby raise_syntax_error.rb
raise_syntax_error.rb:10: syntax error, unexpected end-of-input, expecting `end'
```
　`odd_number?`メソッドのif文に`end`が不足しており、Rubyの文法に違反しているため、`object.morning_method`を呼び出したとき`SyntaxError`が発生しています。
#### ArgumentError
```ruby:サンプルコード (raise_argument_error.rb)
def add_numbers(x, y)
  puts x + y
end

# add_numbersメソッドの引数を1つだけ渡す
add_numbers(5)
```

```:実行結果
$ ruby raise_argument_error.rb
raise_argument_error.rb:1:in `add_numbers': wrong number of arguments (given 1, expected 2) (ArgumentError)    
        from raise_argument_error.rb:6:in `<main>'
```
　`add_numbers`は2つの数値を足し算するメソッドで2つの引数が必要ですが、メソッドを呼び出すときに1つの引数しか渡していないため、`ArgumentError`が発生しています。

#### TypeError
```ruby:サンプルコード (raise_type_error.rb)
def add_numbers(x, y)
  puts x + y
end

# 文字列を含む引数を渡す
add_numbers(5, "2")
```

```:実行結果
$ ruby raise_type_error.rb
raise_type_error.rb:2:in `+': String can't be coerced into Integer (TypeError)
        from raise_type_error.rb:2:in `add_numbers'
        from raise_type_error.rb:6:in `<main>'
```
　`add_numbers`メソッドは2つの数値を足し算するメソッドですが、メソッドを呼び出すとき2番目の引数が文字列となっているため、演算子`+`が使用できず`TypeError`が発生しています。

#### ZeroDivisonError
```ruby:サンプルコード (raise_zerodivision_error.rb)
def divide_numbers(x, y)
  result = x / y
  puts result
end

# 0で割り算するように引数を渡す
divide_numbers(10, 0)
```

```:実行結果
$ ruby raise_zerodivision_error.rb
raise_zerodivision_error.rb:2:in `/': divided by 0 (ZeroDivisionError)
        from raise_zerodivision_error.rb:2:in `divide_numbers'
        from raise_zerodivision_error.rb:7:in `<main>'
```
　`divide_numbers`メソッドが2つの数値を割り算するメソッドですが、メソッドを呼び出すとき2番目の引数が0となっています。これにより、`x / y`の部分で0で割り算しようとしてしまい、`ZeroDivisionError`が発生しています。

#### LoadError
```ruby:サンプルコード (raise_load_error.rb)
def load_file
  # 存在しないファイルをrequireする
  require 'nonexistent_file'
end

# load_fileメソッドを呼び出す
load_file
```

```:実行結果
$ ruby raise_load_error.rb
<internal:C:/Ruby30-x64/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require': cannot load such file -- nonexistent_file (LoadError)
        from <internal:C:/Ruby30-x64/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'       
        from raise_load_error.rb:3:in `load_file'
        from raise_load_error.rb:7:in `<main>'
```
　`load_file`メソッド内で`nonexistent_file`というファイルを`require`しようとしていますが、そのような名前のファイルは存在しないため、`LoadError`が発生しています。

#### RuntimeError
```ruby:サンプルコード (raise_runtime_error.rb)
def capital_of(country)
  case country
  when :Italy
    'Rome'
  when :Germany
    'Berlin'
  when :France
    'Paris'
  else
    raise "#{country}は無効な国名です"
  end
end

# capital_ofメソッドの引数に:Itaryを渡す
puts capital_of(:Italy)
# capital_ofメソッドの引数に:Japanを渡す
puts capital_of(:Japan)

```

```:実行結果
$ ruby raise_runtime_error.rb
Rome
raise_runtime_error.rb:10:in `capital_of': Japanは無効な国名です (RuntimeError)
        from raise_runtime_error.rb:16:in `<main>'
```
　`capital_of`メソッドは国名のシンボルを引数として、首都名を返すメソッドです。また、引数に未登録の国名が渡された場合には、`raise`メソッドを用いて例外を発生させます。よって、引数に`:Japan`を渡したときは「:Japanは無効な国名です」というエラーメッセージを持つ`RuntimeError`が発生しています。
 
## まとめ

　自分のような初学者は、エラーへの耐性がなく心が折れてしまうこともあるかと思います。（実際、僕は今まで何度も心折れています...）
 
　しかし、最初からエラーをスラスラ読めてデバッグできる人なんていないはずです。本記事をきっかけに、少しでもエラーへの心構えが変わり「エラーをちゃんと読んでみよう」と思ってくれる方がいれば幸いです。
 
　最後までお読みいただきありがとうございます！

## 参考文献

　この記事は以下の本を参考にして執筆しました。
 
　エラーの読み方だけでなく、デバッグの方法についても分かりやすくまとめられているので気になる方はぜひ読んでみてください！

「コードが動かないので帰れません！新人プログラマーのためのエラーが怖くなくなる本」(https://www.shoeisha.co.jp/book/detail/9784798180892)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625095/01588149-cf06-ce41-3dfd-2e57d52a448d.png)