Progateでrubyを学んだ初心者はインスタンス変数の理解でつまづく


私はProgateでプログラミングの勉強を始めた初心者です🔰
なのでProgateで学んだことを基準として考えていたのですが、
それが先入観となり理解に苦しんだ場面がありました。

自分が参加しているオンラインサロンで質問をした際、
自分が質問をする前にも後にも、同じ質問をしている方がいました。
その方々もProgateで学んだようです。
これはProgateで学んだ人がつまづきやすい点なのではないかと思い、記事にしてみました!

initializeメソッドで"self.変数名を使う"と思い込んでいる

例えば
Movieというクラスを作り
movie1というインスタンスを生成
映画のタイトル(となりのトトロ)を表示させる
というプログラムを作るとします。

Progateで学んだ内容に沿って書くと

class Movie
  attr_accessor :name

  def initialize(name)
    self.name = name
  end

end

movie1 = Movie.new("となりのトトロ")
puts movie1.name

こう書く人が多いのではないでしょうか。
私はそうでした!

しかし、サロンでの皆さんのコードを見ている限り

class Movie
  attr_accessor :name

  def initialize(name)
    @name = name
  end

end

movie1 = Movie.new("となりのトトロ")
puts movie1.name

と書く人が多いです。

Progate出身者を悩ませているものは
initializeメソッド内の@です!!!
Progateではinitializeメソッドでself.変数名しか使わなかったのに、
@が出てきた、これはなんだろう…という疑問が生まれるのです。

インスタンス変数を理解する

インスタンス変数とは?

インスタンス毎に独立して値を持つことができる変数で
@変数名と書いて定義をするもの。

とあるが、Progateの中では@変数名と表記した変数を目にすることはなく、
インスタンス変数を扱うことのできるattr_accessorとselfを使っていました。

理解した今だからわかるのですが、
attr_accessorとselfがどのようにしてインスタンス変数を扱っているのかがわからないと、
@変数名が出てきたときに意味がわからなくなるのです。

attr_accessorを使わないで書いてみて?と言われたら、
Progateで学んだだけの人はおそらく書けない人が多いでしょう。

ということで、1から仕組みを理解していきましょう!

initializeメソッドは初期化を行っている

attr_accessorを使わず書いていきます。

class Movie
  def initialize(name)
    @name = name
  end

  movie1 = Movie.new("となりのトトロ")

  puts movie1.name

initializeメソッドは、「クラス名.new」でインスタンスを生成した直後に自動で呼び出されます。

とありますが、そのinitializeメソッド内では初期化が行われています。
initializeメソッドではnameが引数とされ、
@nameというインスタンス変数にnameが代入される、これを初期の状態としています。

必ず初期化しなくてはいけない訳ではないのですが、後から初期化と同じ処理をしようとすると、処理を忘れてバグが発生したり、記述が煩雑になったりするため、
それを防ぐために、initialize メソッドで初期化を行うの良いとされています。

movie1というインスタンスを生成、引数nameにはとなりのトトロを入れて表示させようとすると、エラーが発生します。
これはクラスの外からインスタンス変数の値を取得しようとしたためです。

どうしたらとなりのトトロを表示させることができるのか?
ここでゲッターやセッターが出てきます。
Progate学習者である私はこのゲッター・セッターも初耳でした。

インスタンス変数を参照するメソッド "ゲッター"

本来Rubyではインスタンス変数の値はクラス内からしか取得出来ません。
そこで、それを可能にする為にクラス内にインスタンス変数を参照する専用のメソッド「ゲッター」を定義しなくてはいけません。

この説明を読んでも、正直わかってませんでした笑
とりあえず、ゲッターというものを付け加えてみました。

class Movie
  def initialize(name)
    @name = name
  end

  def getname # このメソッドがゲッター
    @name
  end
end

movie1 = Movie.new("となりのトトロ")

puts movie1.getname

getnameというメソッド、これがゲッターです。
このメソッドはクラスの中でインスタンス変数を参照されるように定義され、
それをクラスの外で呼び出すことによって表示させていたのです!

これでとなりのトトロは表示されるようになりました!

ちなみに、self.nameはインスタンスメソッドnameを呼び出すためのものなので、
self.nameはこのゲッターを呼び出していたようなものです。

ゲッターがインスタンス変数を参照するメソッドに対し、
セッターはインスタンス変数を更新するメソッドです。

インスタンス変数を更新するメソッド "セッター"

インスタンス変数の参照と同様に、インスタンス変数の更新もクラス内でしかできません。
そこで、それを可能にする為にクラス内にインスタンス変数の内容を更新する専用のメソッド「セッター」を定義します。

class Movie
  def initialize(name)
    @name = name
  end

  def getname #ゲッター
    @name
  end

  def changename=(name) # セッター
    @name = name
  end
end

movie1 = Movie.new("となりのトトロ")
puts movie1.getname

movie1.changename = "もののけ姫"
puts movie1.getname

これでとなりのトトロを表示させ、
値をもののけ姫に更新して表示させることは出来ます。

が!
ここでまたProgate出身者を悩ますものが、、

変数名に"="使ってる!?

Progateではメソッドの定義の仕方を

 def メソッド名(引数)
   処理
 end

って覚えましたよね?っとことは…

ここでの変数名は "name= "ってこと!?
このイコールってなに?泣

今回の例での場合、

def changename=(name)

ここです。なにこれヤバすぎ、意味わからなさすぎ
ゲッターを追加した際のコードの部分まで戻って、最後の2行を見てください。

movie1.changename = "もののけ姫"
puts movie1.getname

movie1.changenameにもののけ姫を代入してるみたいに見えますよね?
違います!
実はこれ…

movie1.changename=("もののけ姫")
puts movie1.getname

と書いても同じように出力されます!

つまり、
chagename=というメソッドに、もののけ姫という値を引数にして渡しているのです!!
まるで代入されたかのような書き方。。
メソッド名に=を使い、それを呼び出す場合はスペースを空けて書いても同じ結果になるんです。

※なぜメソッド名に=を使うのか?という疑問も生まれましたが、
セッターはそう定義されることでセッターとしての役割を与えられるのです!
そう決められた書き方なんだなぁ、と思えばいいかと。

ここまで理解出来たところで、アクセサについて説明していきます。

アクセサ

クラスを作ってすぐ下に書いたアレ

attr_accessor :name

この1文がどんな役割をしているのかを理解することが重要です!
これって実は

attr_areader :name
attr_writer :name 

この2行文の役割を上の1行で実行しています。

これはアクセサと呼ばれるもので、
attr_reader :変数名はゲッターメソッドを
attr_writer :変数名はセッターメソッドを呼び出しています。
attr_accessor :変数名はゲッター・セッターメソッドどちらも1度に呼び出しています。

どういうことかというと…

attr_reader :name

と書くことで

  def getname #ゲッター
    @name
  end

が呼び出されている。
つまり
attr_reader :nameを記述すれば、ゲッターメソッドを書かなくてもいいということ!

同様に

attr_writer :name

  def changename=(name) # セッター
    @name = name
  end

を呼び出しているので、
attr_writer :nameを書けば、セッターメソッドも書かなくていい!

ということは…??
attr_readerattr_writerこの2つの役割を1つにまとめた

attr_accessor :name 

この1行を書くことによってゲッターとセッターどちらのメソッドの記入も省けます!

class Movie
  def initialize(name)
    @name = name
  end

  def name #ゲッター
    @name
  end

  def name=(name) # セッター
    @name = name
  end
end

movie1 = Movie.new("となりのトトロ")
puts movie1.name

movie1.name = "もののけ姫"
puts movie1.name

class Movie
  attr_accessor :name

  def initialize(name)
    @name = name
  end

end

movie1 = Movie.new("となりのトトロ")
puts movie1.name

movie1.name = "もののけ姫"
puts movie1.name

と書くことができ、
クラス内に書いていたゲッター・セッターメソッドがattr_accessorというアクセサによって呼び出されることにより、同じ出力を可能にしているのです!

attr_accessorってこんなに便利なものだってことをわからずに使っていました。

まとめ

まずなぜインスタンス変数を理解するのにつまづいたのか

  • インスタンス変数は@変数名と書くものだと思っていなかったから
  • イコールを使った変数名なんてあると思っていなかったから
  • attr_accessorが何をしているものなのか理解していなかったから

ですね。

  • インスタンス変数は@変数名と書いて定義する
  • initializeメソッドは初期化をしている
  • ゲッターというインスタンス変数を参照するメソッドがある
  • セッターというインスタンス変数を更新するメソッドがある
  • その2つのメソッドを使ってクラスの外からインスタンス変数の値を取得することができる。
  • attr_accessorを使えばゲッター・セッターどちらも一度に呼び出すことができる。

Progateで学んだことは基本中の基本で、こうやってまた別の学習で知らないことを調べていくのって
理解がさらに深まるし、とても面白いことだなと思って日々学習しています。

批判している訳ではありませんが、ここはつまづきやすいなと思って書いてみました^^

参考と引用
【Ruby】「ゲッター」と「セッター」を理解する