LoginSignup
40
10

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-04-24

はじめに

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

# 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初心者の方もこれでなんとなく理解できるかもしれません。

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

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

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

def foo; end

# このfooはメソッド呼び出し!
foo
foo = nil

# このfooはローカル変数!
foo

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

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

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

なので、みなさん、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という「動詞 + 目的語」のメソッド名に変えました。この命名ルールを意識すると、ローカル変数とメソッド名が被る問題を避けやすくなります。

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

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

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

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メソッドと同じ名前です。

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

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

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

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

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

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

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

まとめ

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

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

クレジットとPR

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

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

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

40
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
10