はじめに
問題です。次のプログラムを保存して実行したとき、ターミナルにはどのような結果が表示されるでしょうか?
# 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.max
のnumbers
はローカル変数であることが明らかですが、長くて複雑なコードになると一瞬「あれっ、どっち??」とわからなくなることがあります。
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学習応援キャンペーン」をやっています。興味がある方はこのキャンペーンを利用してぜひ無料体験してみてください!