💎

【Ruby 31日目】基本文法 - 引数の種類(デフォルト/可変長)

に公開

はじめに

Rubyの引数の種類(デフォルト引数/可変長引数)について、Ruby 3.4の仕様に基づいて詳しく解説します。

この記事では、基本的な概念から実践的な使い方まで、具体的なコード例を交えて説明します。

基本概念

Rubyのメソッド引数には以下の種類があります:

  • 必須引数 - 呼び出し時に必ず指定する必要がある引数
  • デフォルト引数 - 省略時にデフォルト値が使用される引数
  • 可変長引数(*args - 任意の数の引数を受け取る
  • オプション引数 - デフォルト値を持つ引数

これらを組み合わせることで、柔軟なメソッドインターフェースを設計できます。

基本的な使い方

必須引数

def greet(name)
  "Hello, #{name}!"
end

puts greet("Alice")  #=> Hello, Alice!
# greet()  # ArgumentError: wrong number of arguments (given 0, expected 1)

デフォルト引数

def greet(name, greeting = "Hello")
  "#{greeting}, #{name}!"
end

puts greet("Alice")           #=> Hello, Alice!
puts greet("Bob", "Hi")       #=> Hi, Bob!
puts greet("Charlie", "Hey")  #=> Hey, Charlie!

複数のデフォルト引数

def create_user(name, age = 20, country = "Japan")
  "Name: #{name}, Age: #{age}, Country: #{country}"
end

puts create_user("Alice")                    #=> Name: Alice, Age: 20, Country: Japan
puts create_user("Bob", 25)                  #=> Name: Bob, Age: 25, Country: Japan
puts create_user("Charlie", 30, "USA")       #=> Name: Charlie, Age: 30, Country: USA

可変長引数(*args)

def sum(*numbers)
  numbers.reduce(0, :+)
end

puts sum(1, 2, 3)        #=> 6
puts sum(1, 2, 3, 4, 5)  #=> 15
puts sum()               #=> 0

必須引数とデフォルト引数の組み合わせ

def build_url(host, path = "/", protocol = "https")
  "#{protocol}://#{host}#{path}"
end

puts build_url("example.com")                    #=> https://example.com/
puts build_url("example.com", "/api")            #=> https://example.com/api
puts build_url("example.com", "/api", "http")    #=> http://example.com/api

可変長引数と通常引数の組み合わせ

def format_list(title, *items)
  result = "#{title}:\n"
  items.each_with_index do |item, index|
    result += "  #{index + 1}. #{item}\n"
  end
  result
end

puts format_list("My Tasks", "Buy groceries", "Clean room", "Study Ruby")
#=> My Tasks:
#     1. Buy groceries
#     2. Clean room
#     3. Study Ruby

よくあるユースケース

ケース1: ログメッセージのフォーマット

デフォルト引数を使用してログレベルを指定します。

class Logger
  def log(message, level = :info, timestamp = Time.now)
    "[#{timestamp.strftime('%Y-%m-%d %H:%M:%S')}] [#{level.to_s.upcase}] #{message}"
  end
end

logger = Logger.new
puts logger.log("Application started")
#=> [2025-11-07 10:00:00] [INFO] Application started

puts logger.log("Something went wrong", :error)
#=> [2025-11-07 10:00:01] [ERROR] Something went wrong

custom_time = Time.new(2025, 1, 1, 0, 0, 0)
puts logger.log("Happy New Year!", :info, custom_time)
#=> [2025-01-01 00:00:00] [INFO] Happy New Year!

ケース2: データベースクエリビルダー

可変長引数を使用して複数の条件を受け取ります。

class QueryBuilder
  def where(*conditions)
    if conditions.empty?
      "SELECT * FROM users"
    else
      "SELECT * FROM users WHERE #{conditions.join(' AND ')}"
    end
  end
end

query = QueryBuilder.new
puts query.where
#=> SELECT * FROM users

puts query.where("age > 18")
#=> SELECT * FROM users WHERE age > 18

puts query.where("age > 18", "country = 'Japan'", "active = true")
#=> SELECT * FROM users WHERE age > 18 AND country = 'Japan' AND active = true

ケース3: 設定オブジェクトの初期化

デフォルト値を使用して、柔軟な設定を可能にします。

class ServerConfig
  attr_reader :host, :port, :timeout, :ssl

  def initialize(host, port = 3000, timeout = 30, ssl = true)
    @host = host
    @port = port
    @timeout = timeout
    @ssl = ssl
  end

  def url
    protocol = ssl ? "https" : "http"
    "#{protocol}://#{host}:#{port}"
  end
end

# 最小限の設定
server1 = ServerConfig.new("localhost")
puts server1.url  #=> https://localhost:3000

# カスタム設定
server2 = ServerConfig.new("api.example.com", 8080, 60, false)
puts server2.url  #=> http://api.example.com:8080

ケース4: 数学関数の計算

可変長引数を使って複数の値を処理します。

class MathUtils
  def self.average(*numbers)
    return 0 if numbers.empty?
    numbers.sum.to_f / numbers.size
  end

  def self.max(*numbers)
    return nil if numbers.empty?
    numbers.max
  end

  def self.stats(*numbers)
    return {} if numbers.empty?
    {
      count: numbers.size,
      sum: numbers.sum,
      average: average(*numbers),
      min: numbers.min,
      max: numbers.max
    }
  end
end

puts MathUtils.average(1, 2, 3, 4, 5)  #=> 3.0
puts MathUtils.max(10, 5, 20, 15)      #=> 20

puts MathUtils.stats(1, 2, 3, 4, 5).inspect
#=> {:count=>5, :sum=>15, :average=>3.0, :min=>1, :max=>5}

ケース5: テキストフォーマッター

複数のオプションを持つメソッドで、デフォルト値を活用します。

class TextFormatter
  def format(text,
             uppercase: false,
             trim: true,
             max_length: nil,
             suffix: "...")
    result = text
    result = result.strip if trim
    result = result.upcase if uppercase

    if max_length && result.length > max_length
      result = result[0...max_length] + suffix
    end

    result
  end
end

formatter = TextFormatter.new

puts formatter.format("  hello world  ")
#=> hello world

puts formatter.format("hello world", uppercase: true)
#=> HELLO WORLD

puts formatter.format("This is a very long text", max_length: 10)
#=> This is a ...

puts formatter.format("  ruby  ", trim: false, uppercase: true)
#=>   RUBY

注意点とベストプラクティス

注意点

  1. 引数の順序に注意
# BAD: デフォルト引数の後に必須引数を置けない(Ruby 2.0以前)
# def bad_example(a = 1, b)  # SyntaxError
# end

# GOOD: Ruby 2.0以降では可能だが、推奨されない
def confusing_example(a = 1, b, c = 2)
  "a=#{a}, b=#{b}, c=#{c}"
end

puts confusing_example(10, 20)  #=> a=10, b=20, c=2
# 分かりにくいので避けるべき

# BETTER: 必須引数を先に配置
def clear_example(b, a = 1, c = 2)
  "a=#{a}, b=#{b}, c=#{c}"
end

puts clear_example(20)  #=> a=1, b=20, c=2
  1. デフォルト値の評価タイミング
# BAD: デフォルト値は毎回評価される
def add_item(item, list = [])
  list << item
  list
end

# 予期しない動作:デフォルト値が再利用される
# puts add_item("a").inspect  #=> ["a"]
# puts add_item("b").inspect  # Ruby 2.xでは問題が発生する可能性

# GOOD: nilをデフォルトにして内部で初期化
def add_item_safe(item, list = nil)
  list ||= []
  list << item
  list
end

puts add_item_safe("a").inspect  #=> ["a"]
puts add_item_safe("b").inspect  #=> ["b"]
  1. 可変長引数の位置
# 可変長引数の後に通常引数を配置する場合
def complex_method(*items, last_item)
  "Items: #{items.inspect}, Last: #{last_item}"
end

puts complex_method(1, 2, 3, 4)  #=> Items: [1, 2, 3], Last: 4

# 可変長引数の前後に引数を配置
def very_complex(first, *middle, last)
  "First: #{first}, Middle: #{middle.inspect}, Last: #{last}"
end

puts very_complex(1, 2, 3, 4, 5)  #=> First: 1, Middle: [2, 3, 4], Last: 5

ベストプラクティス

  1. デフォルト値は不変オブジェクトを使用
# GOOD: 不変オブジェクト(数値、文字列、シンボル、true/false/nil)
def create_user(name, role = :guest, active = true, retry_count = 3)
  { name: name, role: role, active: active, retry_count: retry_count }
end

user = create_user("Alice")
puts user.inspect  #=> {:name=>"Alice", :role=>:guest, :active=>true, :retry_count=>3}
  1. 多くのオプションがある場合はキーワード引数を検討
# 引数が多い場合はキーワード引数の方が分かりやすい
def create_connection(
  host:,
  port: 3306,
  username: "root",
  password: "",
  database: "myapp",
  timeout: 30
)
  "Connecting to #{username}@#{host}:#{port}/#{database}"
end

puts create_connection(host: "localhost", password: "secret")
#=> Connecting to root@localhost:3306/myapp
  1. 可変長引数は意図を明確に
# GOOD: 可変長引数の用途が明確
def join_paths(*paths)
  paths.join('/')
end

puts join_paths("home", "user", "documents")  #=> home/user/documents

def calculate_total(tax_rate = 0.1, *prices)
  subtotal = prices.sum
  total = subtotal * (1 + tax_rate)
  { subtotal: subtotal, tax: subtotal * tax_rate, total: total }
end

puts calculate_total(0.08, 100, 200, 300).inspect
#=> {:subtotal=>600, :tax=>48.0, :total=>648.0}

Ruby 3.4での改善点

  • Prismパーサーによる引数解析の最適化 - デフォルト引数や可変長引数の解析が高速化
  • YJITの最適化 - デフォルト引数を持つメソッド呼び出しのパフォーマンスが向上
  • エラーメッセージの改善 - 引数の数が合わない場合のエラーメッセージがより詳細に
  • 引数転送(...)との組み合わせ - Ruby 2.7で導入された引数転送構文がより安定的に動作

まとめ

この記事では、引数の種類(デフォルト/可変長)について以下の内容を学びました:

  • 基本概念と重要性 - 必須引数、デフォルト引数、可変長引数の違い
  • 基本的な使い方と構文 - 各種引数の定義方法と組み合わせ
  • 実践的なユースケース - ログ、クエリビルダー、設定管理、数学関数、テキストフォーマット
  • 注意点とベストプラクティス - 引数の順序、デフォルト値の評価、キーワード引数の活用

柔軟な引数設計により、使いやすく保守性の高いメソッドを実装できます。

参考資料

Discussion