【Ruby】メソッドを理解してシンプルなコードを書こう

Ruby

メソッドとは?

メソッドとは、複数の処理を1つにまとめたものです。
メソッドを利用する事で、コードの追加・変更した場合の修正箇所を少なくする事が出来たり、同じ様なコードを繰り返し記述する必要がなくなります。

メソッドとは?

メソッドは関数の事ですが、厳密には「オブジェクトに結び付けられた関数」を指します。しかし、メソッドと関数を混同して呼んでいても、意味は近いので特に問題はありません。

用語 意味
関数 複数処理をまとめるもの
オブジェクト Rubyで扱う事ができる全てのデータのこと
メソッド オブジェクトに結び付けられた関数のこと

そしてRubyには、getsメソッドputsメソッドの様にプログラム言語に組み込まれたメソッドもありますが、ここでは自分で定義するメソッドを解説していきます。

※putsメソッドの返り値はnilになりますが、この記事のサンプルコードではコードを分かりやすくする為に省略しています。

メソッド定義の方法【引数なし】
リンクをコピーしました

自分で定義するメソッドには、引数があるものとないものがあります。まずは引数がない基本的なメソッドの定義方法を見ていきます。

メソッド定義は、下記の様にdefの後にメソッド名を記述して、def〜endの間にメソッドで処理する内容を記述します。

基本的なメソッドの定義方法
1
2
3
def メソッド名
  # メソッド内で処理する内容を記述
end

下記のsay_helloメソッドを試しに定義していきましょう。

irb | say_helloメソッドを定義
1
2
3
4
def say_hello
  puts "hello!"
end
=> :say_hello

say_helloメソッドが定義された事は、最後の行の=>:say_helloで確認する事が出来ました。

定義したメソッドを実行する方法とは?
リンクをコピーしました

定義したメソッドは、メソッド名を記述し実行することが出来ます。

メソッドを実行する方法
1
2
3
4
5
6
7
# メソッド定義
def メソッド名
  # 処理
end

# メソッド実行
メソッド名

先ほどのsay_helloメソッドを実行していきましょう。

irb | 定義したメソッドを実行する
1
2
3
4
5
6
7
8
# say_helloメソッドを定義
def say_hello
  puts "hello!"
end

# say_helloメソッドを実行
say_hello
#=> hello!

上記のコードでは、say_helloを実行すると、メソッド内で定義した「puts "hello!"」が読み込まれ「hello!」が出力された事を確認する事が出来ました。

メソッドの読み込み順
リンクをコピーしました

通常コードは上から順番に読み込まれますが、メソッドの定義部分は、メソッドが実行されるまで無視されて読み込まれないので注意してください。

下記のサンプルコードにあるsay_helloメソッドは、メソッドが実行されて初めて定義(def ~ end)部分が読み込まれます。

メソッドの読み込み順

ただし、メソッド定義部分はメソッド実行するまで読み込まれませんが、必ずメソッドを実行する前に記述する必要があります。

メソッドの命名規則
リンクをコピーしました

複数の単語が続く場合のメソッド名は、スネークケースと呼ばれる単語の間をアンダースコア(_)で区切った書き方にします。

メソッド命名規則

命名規則とは、プログラミングのメソッド、変数、型など名前を付けるルールのことです。

メソッドを使うメリット
リンクをコピーしました

メソッド内の処理は、メソッドが無くても正常に処理することは出来ますが、メソッドを使うことで2つのメリットが生まれます。

メソッドを使うメリット

それでは、メソッドを使った場合と使わなかった場合どうなるのかをコードを比較しながらメリットの部分を確認していきます。

1. 同じ処理を実行する場合に、何度も同じコードを書かずに済む
リンクをコピーしました

メソッドを定義して処理を記述すると、何度も同じコードを書かずに済みます。実際にメソッドを利用した場合と利用しない場合の比較をしてみましょう。

下記のサンプルコードは、「おはようございます。こんにちわ。こんばんわ。さようなら。」の一連の挨拶を出力し3回繰り返されています。同じコードが繰り返されているので冗長なコードになります。

コンソール | メソッドを利用しない場合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 一連の挨拶を3回繰り返す
puts "おはようございます。"
puts "こんにちわ。"
puts "こんばんわ。"
puts "さようなら。"

puts "おはようございます。"
puts "こんにちわ。"
puts "こんばんわ。"
puts "さようなら。"

puts "おはようございます。"
puts "こんにちわ。"
puts "こんばんわ。"
puts "さようなら。"

しかしメソッドを利用すれば、下記の様にコードを繰り返すことなく記述することが出来ます。更にコードの記述量が少なくなるので、可読性も良くなります。

コンソール | メソッドを利用した場合
1
2
3
4
5
6
7
8
9
10
11
def greeting
  puts "おはようございます。"
  puts "こんにちわ。"
  puts "こんばんわ。"
  puts "さようなら。"
end

# メソッド実行を3回記述するだけ
greeting
greeting
greeting

2. 修正箇所がある場合に、メソッド内のコードだけ修正すれば良い
リンクをコピーしました

メソッドを定義して処理を記述すると、コードを修正する箇所がメソッドの定義元の処理だけで済みます。

先ほどのサンプルコードで確認してみます。一連の挨拶の中で「さようなら。」だけを削除する場合、修正箇所は3箇所発生します。

コンソール | メソッドを利用しない場合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 一連の挨拶を3回繰り返す
puts "おはようございます。"
puts "こんにちわ。"
puts "こんばんわ。"
puts "さようなら。" # ここを削除する

puts "おはようございます。"
puts "こんにちわ。"
puts "こんばんわ。"
puts "さようなら。" # ここを削除する

puts "おはようございます。"
puts "こんにちわ。"
puts "こんばんわ。"
puts "さようなら。" # ここを削除する

しかしメソッドを利用すれば、下記の様に削除する箇所はメソッド内の1箇所だけで済みます。

コンソール | メソッドを利用した場合
1
2
3
4
5
6
7
8
9
10
11
def greeting
  puts "おはようございます。"
  puts "こんにちわ。"
  puts "こんばんわ。"
  puts "さようなら。" # この箇所だけを削除すれば良い
end

# メソッド実行を3回記述するだけ
greeting
greeting
greeting

メソッドを利用することで、コードの変更・追加がしやすく作業時間の効率も上がります。

メソッドとスコープ
リンクをコピーしました

メソッドの引数の定義の前に「スコープ」について学んでいきましょう。
変数には、変数の使える範囲(有効範囲)が決まっています。これをスコープと言います。

メソッドにも変数の使える範囲が決まっていて、メソッド内で定義した変数しか使う事が出来ません。メソッド外で定義した変数を利用するとエラーが起きてしまいます。

スコープは、最初理解しにくいので具体例を使って解説していきます。

スコープの具体例
リンクをコピーしました

変数のスコープをお金で例えてみます。お金といっても日本で作られる日本円や海外のドルなど国によって種類は色々ありますが、お金が使える有効範囲はどうでしょうか?
変数のスコープをお金で例える

上記の様に日本国内で使えるお金は、日本で作られた日本円のみです。日本国外で作られたドルなどのお金は日本国内で使う事が出来ません。

そして、お金を変数として例えましたが、メソッドを日本国と考えてみると次の様なことが言えます。

メソッドのスコープの例え

まずは、「日本国内で作られた(円)は日本国内で使う事が出来る」ですが、メソッド内(def ~ endの間)で定義された変数はメソッド内で正常に使う事が出来ます。これをコードにすると、下記の様になります。

コンソール | メソッド内で定義した変数をメソッドで使う例
1
2
3
4
5
6
7
8
9
def japan
  money = "日本円" # メソッド内で変数moneyを定義
  puts "日本国内では、#{money}を使用する事が出来る" # メソッド内で変数moneyを使用
end


# japanメソッドを実行
japan # メソッドが正常に処理される
#=>日本国内では、日本円を使用する事が出来る

正常に処理される事が分かりました。次に「日本国外で作られたお金(ドル)は、日本国内で使う事が出来ない」は、メソッド外で定義された変数はメソッド内で使う事が出来ません。これをコードにすると下記の様になります。

コンソール | メソッド外で定義した変数をメソッド内で使う例
1
2
3
4
5
6
7
8
9
def japan
  puts "日本国内では、#{money}を使用する事が出来る" # メソッド外で定義した変数moneyを使用
end

money = "海外のお金"  # メソッド外で変数moneyを定義

# japanメソッドを実行する
japan # エラーが発生
NameError: undefined local variable or method `money' for main:Object

この様にメソッド外で定義された変数moneyをメソッド内で使用すると、未定義のエラーが発生します。

Pikawakaマークポイント

  1. 変数には、変数の有効範囲(スコープ)が決まっています。
  2. メソッドの変数の有効範囲(スコープ)は、メソッド内で定義されている変数のみです。
  3. メソッド外で定義した変数をメソッド内で使うと未定義のエラーが発生します。

同じ名前の変数でもスコープの範囲は違う
リンクをコピーしました

使っている変数の名前が同じ名前だったとしても、変数のスコープ(有効範囲)は違います。

例えば、下記の1行目に値が"yen"の変数moneyを定義します。americaとfranceの2つのメソッド内の変数moneyにそれぞれ"dollar"と"euro"の値を代入します。

コンソール | 同名の変数のスコープの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
money = "yen"

def america
  money = "dollar"
end

def france
  money = "euro"
end

# メソッドを実行
america
france

puts money # メソッドに干渉する事なくyenが返る
#=> "yen"

メソッドを実行後に、puts moneyで返ってきた値は、"dollar"でもなく"euro"でもなく"yen"でした。このことから分かるように、メソッド内に定義された変数は、スコープの違いによって同じ名前の変数であっても干渉する事が出来ません。

変数のスコープの例

メソッド外で定義された変数moneyをメソッド内で使用する為にはどうすれば良いのでしょうか?それは、引数を利用する必要があります。

メソッド定義の方法【引数あり】
リンクをコピーしました

引数とは、メソッドに外部から任意の値を渡すための仕組みのことです。引数を利用する事で、メソッド外から定義した変数や値をメソッド内で使える様にします。

引数を利用する場合のメソッドの定義方法は下記の様にします。

引数ありの場合のメソッドの定義方法
1
2
3
4
5
6
7
# メソッド定義側
def メソッド名(引数)
# 引数を使って処理を記述する
end

# メソッドの呼び出し側
メソッド名(引数)

メソッドの呼び出し側の引数にメソッドに渡したい任意の値(変数)を持たせて、メソッド定義側の引数で値を受け取ります。引数を通して受け取った値(変数)はメソッド内でも利用する事が出来ます。

先ほどのjapanメソッドは、メソッド外で定義した変数moneyをメソッド内で使う事が出来ませんでしたが引数を使う事でメソッド内でも変数を利用する事が出来ます。

コンソール | メソッド外で定義した変数を引数を利用してメソッドで使う例
1
2
3
4
5
6
7
8
# メソッド定義側
def japan(money)
  p "#{money}を円に両替した" # メソッド内で変数moneyを使用する
end

money = "ドル"
japan(money) # メソッド呼び出し側
=> "ドルを円に両替した"

メソッド呼び出し側の引数に変数を持たせます。そして、メソッド定義側のdef japan(money)の引数moneyに値を渡します。メソッド内では引数moneyの名前で値を使う事が出来ます。

引数ありのスコープの関係

もう少し詳しく引数について解説していきます。

仮引数と本引数
リンクをコピーしました

引数には、「外部から値を受け取る仮引数」と「メソッド実行の際に値を渡す本引数」の2つの引数があります。メソッド定義側の引数が仮引数で、メソッド呼び出し側が本引数です。

仮引数と本引数
1
2
3
4
5
6
7
# メソッド定義側
def メソッド名(仮引数)
# 引数を使って処理を記述する
end

# メソッドの呼び出し側
メソッド名(本引数)

メソッドの呼び出し側の本引数にメソッド内で使いたい値(変数)を入れます。そして、その値を仮引数で受け取り、メソッド内で使う事が出来ます。

仮引数と本引数の関係

メソッド定義側の引数の名前に仮と付いているのは、仮引数がメソッド呼び出し時に作られる一時的な変数だからです。メソッドの処理が終了すると、この変数はなくなります。(メモリから解放される)

メモリなど少し難しい話になるので、ここでは仮引数は、メソッド実行時に作られる一時的な変数(メソッド終了時に解放)とだけ覚えておきましょう。

引数の名前
リンクをコピーしました

先ほどのサンプルコードでは本引数と仮引数の名前は一緒でしたが、本引数と仮引数は違う名前でも利用できます。

例えば、仮引数名をmoneyからa_moneyに変更してみます。

コンソール | 本引数と仮引数の名前が違う例
1
2
3
4
5
6
7
8
9
# メソッド定義側
def japan(a_money)
  puts "#{a_money}を円に両替したので手持ちのお金を日本で使う事が出来る" # メソッド内で変数a-moneyを使用する
end

money = "ドル"
# メソッド呼び出し側
japan(money) # メソッドが正常に処理される
#=> ドルを円に両替したので手持ちのお金を日本で使う事が出来る

仮引数の名前を変更した時に、メソッド内の名前も変更します。本引数と仮引数は異なる名前でも大丈夫ですが、メソッド内で引数として渡された値を使うためには、仮引数と同名でなければ使う事が出来ません。

実際に仮引数名とメソッド内の名前が違うパターンも確認してみましょう。

コンソール | 仮引数名とメソッド内の名前が違う例
1
2
3
4
5
6
7
8
9
10
# メソッド定義側
def japan(a_money)
  puts "#{money}を円に両替したので手持ちのお金を日本で使う事が出来る" # メソッド内で変数a-moneyを使用
end

money = "ドル"

# メソッド呼び出し側
japan(money) # エラーが発生
NameError: undefined local variable or method `money' for main:Object

また、本引数と仮引数は違う名前でも利用出来ましたが、本引数に与える変数名と本引数の名前が違うとエラーが発生します。

コンソール | 本引数に与える変数名と本引数の名前が違う場合
1
2
3
4
5
6
7
8
9
10
# メソッド定義側
def japan(money)
  puts "#{money}を円に両替したので手持ちのお金を日本で使う事が出来る"
end

a_money = "ドル"

# メソッド呼び出し側
japan(money) # エラーが発生
NameError: undefined local variable or method `money' for main:Object

返り値
リンクをコピーしました

メソッドは、返り値をメソッドの呼び出し元に返すことが出来ます。返り値とは、メソッドから返ってくる値のことです。返り値がなければ、メソッドの処理した結果を得ることが出来ません。

say_helloメソッドでは、「任意の名前, hello!」を出力するだけでしたが、「任意の名前, hello!」をreturn文を使って、メソッド自体の値としてメソッドの呼び出し元に返します。

コンソール | returnで返り値をメソッドの呼び出し元に返す
1
2
3
4
5
6
7
def say_hello(name)
  return "#{name}, hello!" # return文を追加
end

result = say_hello("taro")
puts "私は「#{result}」と挨拶した。"
#=> 私は「taro, hello!」と挨拶した。

上記の例では、returnで返ってきた値を変数resultに代入して使用しています。

return文が実行されると、どの位置でも、return文の後に続く式があってもメソッドの処理はreturn文で終わります。

コンソール | メソッドの処理はreturn文で終わる
1
2
3
4
5
6
7
8
def say_hello(name)
  return "#{name}, hello!" # メソッド内の処理はここで終わる
  puts "ここの記述は実行されない"
end

result = say_hello("taro")
puts result
#=> taro, hello!

そして、return文は省略する事が出来ます。
その場合は、メソッド内の最後の式が評価された値が返り値になります。

コンソール | メソッド内の最後の式が評価された値が返り値になる
1
2
3
4
5
6
7
def say_hello(name)
  puts "#{name}, hello!"
  p "またね!" # ここがsay_helloメソッドの返り値
end

say_hello("taro")
# => "またね!"

ピカわかマークポイント

  1. メソッドは、返り値をメソッドの呼び出し元に返すことが出来ます。
  2. returnが実行されると、その時点でメソッドの処理が終わります。
  3. returnは省略する事が出来き、その場合メソッド内の最後の式が評価された値が返り値になります。

複数の引数
リンクをコピーしました

メソッドには、複数の引数を持たせることが出来ます。引数が複数ある場合は、最初の引数を「第一引数」、次の引数を「第二引数」と呼びます。複数の引数がある場合は、引数の順番に注意が必要です。

下記のサンプルコードでは、say_helloメソッドの第一引数(name)に"taro"、第二引数(greeting)に"hello!"が代入されています。

コンソール | 複数の引数がある場合
1
2
3
4
5
6
def say_hello(name, greeting) # nameが第一引数, greetingが第二引数
  return "#{name}さん, #{greeting}"
end

say_hello("taro", "hello!") # "taro"が第一引数, "hello"が第二引数
# => "taroさん, hello!"

しかし、メソッド実行の際に値を渡す本引数の順番を逆にしてしまうと、意図しない処理が実行されます。先ほどのサンプルコードの本引数say_hello("taro", "hello!")をsay_hello("hello!", "taro")の様に逆にしてメソッドを実行してみます。

コンソール | 引数の順番を逆にして渡す例
1
2
3
4
5
6
def say_hello(name, greeting)
  return "#{name}さん, #{greeting}"
end

say_hello("hello!", "taro") # "hello"に第一引数, "taro"を第二引数に変更
# => "hello!さん, taro"

say_helloメソッドの返り値が"hello!さん, taro"になってしまい、本来返ってきて欲しい値ではなくなりました。この様に意図しない処理になってしまうので、複数の引数がある場合は順番に注意して下さい。

その他の引数の受け渡し方法については、また別記事で紹介させて頂きます。

また、Rubyでは引数のカッコ()を省略する事が出来ます。ただし、第一引数がハッシュの場合はエラーが発生しますので注意して下さい。詳しくは、ハッシュの{}とブロックの{}の違いを参考にして下さい。

メソッド内で他のメソッドを利用
リンクをコピーしました

メソッド内では、他のメソッドを利用することが出来ます。下記のサンプルコードでは、メールアドレスを作成するmake_mailメソッドの中でドメインを追加するadd_domainメソッドを利用しています。

コンソール | メソッド内で他のメソッドを利用する
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def add_domain(domain_name)
  if domain_name == "gmail"
    email_domain = "gmail.com"
  else domain_name == "yahoo"
    email_domain = "yahoo.co.jp"
  end
  return email_domain  # ここの1行は省略することが出来る
end

def make_mail(name, domain_name)
  domain = add_domain(domain_name) # add_domainメソッドを実行
  return "#{name}@#{domain}"
end

make_mail("taro", "gmail") # make_mailメソッド実行
# => "taro@gmail.com"

上記では、make_mailメソッドの中でadd_domainメソッドを実行しています。そして、add_domainメソッドの処理の結果(返り値)を変数domainに代入して使っています。

この様にメソッド内で処理をメソッドに切り分けるとでコードがよりシンプルになります。

メソッドを理解する事が出来たら、次はオブジェクト指向で設計するクラスとインスタンスについて学習してみましょう。

まとめ
  • メソッドとは、複数の処理を1つにまとめたものです。
  • メソッドの定義部分は、メソッドが実行されるまで無視されて読み込まれません。
  • 変数には、変数の使える範囲(有効範囲)が決まっています。これをスコープと言います。