Tech Racho エンジニアの「?」を「!」に。
  • 開発

Rubyスタイルガイドを読む: 文法(1)メソッド定義、引数、多重代入

こんにちは、hachi8833です。「Rubyスタイルガイドを読む」、今回は文法編です。

文法の項は70ほどもあるので、何回かに分割しようと思います。

文法(1) メソッド定義、引数、多重代入など

2-01【統一】::は、定数(クラスやモジュールも含む)、コンストラクタ(Array()Nokogiri::HTML()など)の参照にのみ使う

Use :: only to reference constants(this includes classes and modules) and constructors (like Array() or Nokogiri::HTML()).
Do not use :: for regular method invocation.

通常のメソッド呼び出しでも::を使うと読みづらくなるのでスタイルを設定していると考えられます。

# 不可
SomeClass::some_method
some_object::some_method

# 良好
SomeClass.some_method
some_object.some_method
SomeModule::SomeClass::SOME_CONST
SomeModule::SomeClass()

2-02【統一】メソッド定義にパラメータがない場合は、def行のメソッドにかっこ()をつけない

Use def with parentheses when there are parameters. Omit the parentheses when the method doesn't accept any parameters.

逆にメソッド定義にパラメータがある場合は明示的に()をつけます。

# 不可
def some_method()
# body omitted
end

# 良好
def some_method
# body omitted
end

# 不可
def some_method_with_parameters param1, param2
# body omitted
end

# 良好
def some_method_with_parameters(param1, param2)
# body omitted
end

Rubyではメソッド呼び出しなどでパラメータのかっこを省略できますし、呼び出しではかっこを省略するスタイルが広く使われていますが、本スタイルではメソッドの定義・呼び出しともにかっこを省略しないスタイルになっています(メソッド呼び出しについては後述)。

2-03【統一】メソッド呼び出しのかっこ()は省略しない(特に第1引数がかっこ()を使った式の場合)

Use parentheses around the arguments of method invocations, especially if the first argument begins with an open parenthesis (, as in f((3 + 2) + 1).

# 不可
x = Math.sin y
# 良好
x = Math.sin(y)

# 不可
array.delete e
# 良好
array.delete(e)

# 不可
temperance = Person.new 'Temperance', 30
# 良好
temperance = Person.new('Temperance', 30)

「特に第1引数が...」のサンプルを以下に取り出してみました。

f (3 + 2) + 1  # 読みにくい

f((3 + 2) + 1) # 読みやすい

2-03a 【例外】次の場合にのみかっこ()を省略する

  • 引数のないメソッド呼び出し
# 不可
Kernel.exit!()
2.even?()
fork()
'test'.upcase()

# 良好
Kernel.exit!
2.even?
fork
'test'.upcase
  • 内部DSLの一部となっているメソッド呼び出し

DSL(ドメイン固有言語: Domain Specific Language)にはRakeRailsRSpecなどが該当します。RubyではこうしたDSL処理系を比較的簡単に作ることができます。

# 不可
expect(bowling.score).to eq 0
# 良好
expect(bowling.score).to eq(0)
  • ステータスをキーワードで指定するメソッド

確かに、:nameなどのシンボルならかっこがなくても十分視認性が確保されそうですし、むしろかっこがあるとうるさく見えそうです。

class Person
  # 不可
  attr_reader(:name, :age)
  # 良好
  attr_reader :name, :age

  # 本文は省略
end

# 不可
puts(temperance.age)
# 良好
puts temperance.age

他の言語の経験者は最初なかなか慣れないかもしれませんが、慣れるとRubyのメソッド呼び出しにおけるかっこ省略スタイルはむしろ読みやすく感じられます(注: 個人の感想です)。

とはいうものの、込み入ったメソッド呼び出しでかっこをつけないと読みづらくなったり誤動作したりすることがあるので、そういった場合にはかっこをつけることになります。

このあたりは既存のコードや会社のコーディングスタイルに合わせて変えることもあるかと思います。

2-04【統一】オプション引数は引数リストの末尾に置く

Define optional arguments at the end of the list of arguments. Ruby has some unexpected results when calling methods that have optional arguments at the front of the list.

オプション引数(optional arguments)とは、a = 1 のようにデフォルト値を指定した引数のことであり、その引数の値を省略したときにデフォルト値が使われます。

# 不可
def some_method(a = 1, b = 2, c, d)
  puts "#{a}, #{b}, #{c}, #{d}"
end

some_method('w', 'x') # => '1, 2, w, x'
some_method('w', 'x', 'y') # => 'w, 2, x, y' #これがマズい結果になりやすい
some_method('w', 'x', 'y', 'z') # => 'w, x, y, z'

# 良好
def some_method(c, d, a = 1, b = 2)
  puts "#{a}, #{b}, #{c}, #{d}"
end

some_method('w', 'x') # => '1, 2, w, x'
some_method('w', 'x', 'y') # => 'y, 2, w, x'
some_method('w', 'x', 'y', 'z') # => 'y, z, w, x'

なかなかわかりにくいサンプルですが、「良好」の方ではa = 1b = 2というオプション引数を引数リストの末尾においていますので、引数リストは「c, d, a, b」といささか異様な順序になっています。

逆に「不可」の方ではsome_method('w', 'x') # => '1, 2, w, x'のように、引数aに"w"を渡したつもりがデフォルト値の1が使われ、渡した"w"がcに渡されるという挙動になっています。これは場合によっては望ましくない動作になると思います。

どちらもメソッド呼び出しで引数をすべて渡すと正常に動作するので、引数を減らしてみて初めて気づくということがありそうです。

この動作はRubyの仕様によるものであり、不具合を避けるためにこのスタイルを決めたと理解しました。

参考

以下は主にRubyのキーワード引数についての記事ですが、近年のRubyでよく変更されている機能なので、オプション引数について理解するときに合わせて読むとよいと思います。

2-05【統一】メソッドに論理値を引数として渡す場合はキーワード引数を使う

2018/09/16に上のコミットが追加されたので反映しました。

# 不可
def some_method(bar = false)
  puts bar
end

# 不可: キーワード引数が導入される前に多用されていたハック
def some_method(options = {})
  bar = options.fetch(:bar, false)
  puts bar
end

# 良好
def some_method(bar: false)
  puts bar
end

some_method            # => false
some_method(bar: true) # => true

2-06【統一】キーワード引数の方がオプション引数よりも望ましい

commit: Prefer keyword arguments over optional arguments · rubocop-hq/ruby-style-guide@508e506

2018/09/16に上のコミットが追加されたので反映しました。

上の「2-04【統一】オプション引数は引数リストの末尾に置く」が残されていることから、オプション引数の利用は一応認められてはいるものの、キーワード引数の利用が推奨されています。

# 不可
def some_method(a, b = 5, c = 1)
  # (略)
end

# 良好
def some_method(a, b: 5, c: 1)
  # (略)
end

2-07【統一】変数定義での多重代入は避ける

Avoid the use of parallel assignment for defining variables. Parallel
assignment is allowed when it is the return of a method call, used with
the splat operator, or when used to swap variable assignment. Parallel
assignment is less readable than separate assignment.

多重代入自体はコードを簡潔に書くのに有用ですが、変数定義では避けるようにとの指示です。

たぶん多重代入だと変数定義らしく見えないからではないかと推測しています。変数が多くなったときにもわかりにくくなりそうですね。

多重代入は以下の場合に認められます。

  • メソッドの返り値
  • splat演算子としての*
  • 変数の値の入れ替え
# 不可
a, b, c, d = 'foo', 'bar', 'baz', 'foobar'

# 良好
a = 'foo'
b = 'bar'
c = 'baz'
d = 'foobar'

# 良好 - 変数の値入れ替えに使う場合
# Swapping variable assignment is a special case because it will allow you to
# swap the values that are assigned to each variable.
a = 'foo'
b = 'bar'

a, b = b, a
puts a # => 'bar'
puts b # => 'foo'

# 良好 - メソッドの返り値
def multi_return
  [1, 2]
end

first, second = multi_return

# 良好 - splat演算子で使う場合
first, *list = [1, 2, 3, 4] # first => 1, list => [2, 3, 4]

hello_array = *'Hello' # => ["Hello"]

a = *(1..3) # => [1, 2, 3]

関連記事



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。