Rubyではメソッド名と同じ名前のローカル変数を宣言しないようにしよう

## はじめに
問題です。次のプログラムを保存して実行したとき、ターミナルにはどのような結果が表示されるでしょうか？

```ruby 
# fromからtoまでの連続した整数を配列として返す
def numbers(from: 0, to: 10)
  (from..to).to_a
end

def main
  p numbers

  numbers = numbers(from: 3, to: 7)

  p numbers
  p self.numbers
  p numbers()
end

main
```

はい、答えはこうです。

```
$ ruby sample.rb
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```

予想は当たりましたか？

## 簡単に解説

`main`メソッドの振る舞いにコメントを付けていきます。Ruby初心者の方もこれでなんとなく理解できるかもしれません。

```ruby 
def main
  # これはnumbersメソッドの呼び出し
  p numbers

  # numbersメソッドの結果をローカル変数numbersに代入する
  numbers = numbers(from: 3, to: 7)

  # これはローカル変数numbersを参照している
  p numbers

  # selfを付けるとローカル変数ではなくメソッドのnumbersを呼び出す
  p self.numbers

  # ()を付けたときもローカル変数ではなくメソッドのnumbersを呼び出す
  p numbers()
end
```

ややこしいのは2回登場する`p numbers`ですね。
最初はメソッド呼び出しなのに、2回目はローカル変数の参照に変わっています。
これは`main`メソッドの途中で`numbers = numbers(from: 3, to: 7)`のように、メソッドと同名のローカル変数が宣言されたためです。

そのため、単に`numbers`と書いた場合、ローカル変数が宣言される前は`numbers`メソッドが、宣言された後はローカル変数の`numbers`がそれぞれ優先して参照されます。

Rubyは文法上、以下のような書き方をするとローカル変数とメソッドの呼び出しが区別が付きません。

```ruby 
# fooはメソッドかもしれないし、ローカル変数かもしれない
# （この1行だけではなんとも判断ができない）
foo
```

そのため、その行を実行したときのコンテキストによって、振る舞いが変わります。

```ruby 
def foo; end

# このfooはメソッド呼び出し！
foo
```

```ruby 
foo = nil

# このfooはローカル変数！
foo
```

ちなみにメソッドもローカル変数も定義されていない場合は以下のようなエラーが出ます。

```ruby 
foo
#=> undefined local variable or method `foo' for main:Object (NameError)
```

ご覧のとおり、"undefined local variable or method `foo`"（未定義のローカル変数またはメソッド`foo`）というエラーメッセージが出ていますね。このことからRuby自身も`foo`がローカル変数なのかメソッドなのか、区別が付いてない（区別が付けられない）ということがわかります。

なので、みなさん、Rubyのコードを読み書きするときは自分が目にしている識別子がメソッドなのか、ローカル変数なのか十分気を付けましょう・・・という話をするのがこの記事の趣旨ではありません！

## 上記のコードはアンチパターン！じゃあどう書くの？

そもそもこのややこしさはメソッドとローカル変数がまったく同じ名前になっていることに起因しています。ですので、特別な理由でもない限り、上で挙げたようなコードは書くべきではありません。

そうではなく、次のようにメソッド名とローカル変数名に異なる名前を付けて区別できれば、こういったややこしい問題は起きません。

```ruby 
# メソッド名をgenerate_numbersに変更
def generate_numbers(from: 0, to: 10)
  (from..to).to_a
end

def main
  # これはどう考えてもメソッド呼び出し
  p generate_numbers
  #=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  numbers = generate_numbers(from: 3, to: 7)

  # これはどう考えてもローカル変数
  p numbers
  #=> [3, 4, 5, 6, 7]

  # こちらは疑いようもなくメソッド呼び出し
  p generate_numbers
  #=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
end

main
```

上のコードではローカル変数とメソッド名が別々なので、実行コンテキストを意識しなくても良くなりました。

## メソッド名は「動詞のみ」または「動詞 + 目的語」がオススメ

基本的なセオリーとして、メソッドは「動詞のみ」または「動詞 + 目的語」にしましょう。改善後のコードでも`generate_numbers`という「動詞 + 目的語」のメソッド名に変えました。この命名ルールを意識すると、ローカル変数とメソッド名が被る問題を避けやすくなります。

命名に関する話は以下の記事で詳しく説明しています。こちらの記事もあわせてどうぞ。

https://qiita.com/jnchito/items/459d58ba652bf4763820

## 同じメソッド内であろうとなかろうと、メソッド名と同じ名前のローカル変数は避ける！

最初に示した例では同じメソッド内で`numbers`メソッドを呼び出したり、`numbers`変数を宣言したりしましたが、以下のコードのように別のメソッドにわかれているときも考え方は同じです。

```ruby 
def numbers(from: 0, to: 10)
  (from..to).to_a
end

def max_number
  # ん！？
  numbers = [4, 7, 2, 6]
  numbers.max
end

def main
  p numbers.max #=> 10
  p max_number  #=> 7
end

main
```

このコードでは`max_number`というメソッドで`numbers`というローカル変数を宣言していますが、これは`numbers`メソッドと同じ名前です。

```ruby 
def max_number
  # この変数名が他のメソッド名と被っている
  numbers = [4, 7, 2, 6]
  numbers.max
end
```

このコードは短いので`numbers.max`の`numbers`はローカル変数であることが明らかですが、長くて複雑なコードになると一瞬「あれっ、どっち？？」とわからなくなることがあります。

```ruby 
def max_number
  numbers = [4, 7, 2, 6]
  # 長いロジックが続く
  # .
  # .
  # .
  # .
  # .
  # .
  # .
  # あれ？これはメソッドのnumbersだっけ？？
  numbers.max
end
```

このほかにも、メソッド自身と同じ名前のローカル変数を宣言する人もたまに見かけます。

```ruby 
def numbers(from: 0, to: 10)
  # numbersメソッドの内部で定義された、numbersという名前のローカル変数（ややこしい！！）
  numbers = (from..to).to_a
  numbers.reverse
end
```

いずれのコードもRubyは文句を言わずに動いてくれますが、人間が読むときには誤解や混乱の原因になります。ですので、こうしたコードは極力避けましょう。

先ほども書いたとおり、メソッド名は「動詞のみ」または「動詞 + 目的語」というルールを心がけることで、この問題が避けやすくなります。

## まとめ
というわけでこの記事では「Rubyではメソッド名と同じ名前のローカル変数を宣言しないようにしよう」という話を書いてみました。

プログラミング初心者の方はこうしたコードをうっかり書きやすいので、注意してくださいね😉

### クレジットとPR
この記事は「新人プログラマ応援 - みんなで新人を育てよう！」のイベント参加用に書いた記事です。

https://qiita.com/official-events/3f21c92121aa125807b4

また、ここで紹介したようなコード例は筆者がメンターをやっている「フィヨルドブートキャンプ」で、提出物のコードレビュー中によく指摘する内容です。

https://bootcamp.fjord.jp/

2022年4月29日(金)から5月8(日)まで、フィヨルドブートキャンプでは「GW学習応援キャンペーン」をやっています。興味がある方はこのキャンペーンを利用してぜひ無料体験してみてください！

https://prtimes.jp/main/html/rd/p/000000006.000061066.html