STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

Ruby のメソッド定義時に仮引数があるとき、それをカッコでくくらないのは私だけなの?

テクノロジー部門で Ruby インタプリタの開発をしている笹田です。RubyKaigi 2024 楽しみですね。

さて、Ruby のメソッドを定義するとき、仮引数がある場合、カッコを省略することができます。

def foo(x, y)
end

def bar x, y
end

bar の定義の方法ですね。私は好んでこの書き方をしてたんですが、同僚の遠藤さんに「そんな書き方をしているのは今時笹田だけだ」と言われてショックを受けたので、ちょっと調べてみました。

ちなみに、カッコがないと使えないメソッド定義の方法があるので、その時には涙を呑んでカッコをつけます。

def foo(kw:) # 必須キーワード引数
end

def bar(&) # 無名ブロック引数
end

補足1:Ruby では「メソッド呼び出し時にカッコをつけるかどうか」にいろいろな論争がありますが、ここでは「メソッドを定義するときのカッコ」だけ述べます。あんまり聞いたことないんだよな。

補足2:仮引数がない場合はカッコをつけないのが多いと思うので、「仮引数があるときだけ」について述べます。

慣習

Rubocop には仮引数があるときカッコをつけろ、というルールがデフォルトであるそうです。Standardrb でもデフォルト(standard/config/base.yml at 8307fa8f449f896075ccad74bf6a128ed2c26189 · standardrb/standard · GitHub)。

docs.rubocop.org

コーディングルールの時によく参照される気がする Cookpad でのルールでは、カッコを付けるのが must とのことです。

github.com

[MUST] メソッド定義において、引数リストの括弧は省略してはならない。ただし、引数なしメソッドを定義する場合は括弧を省略すること。

昔よく参照されていたコーディングルールでも、カッコをつけるのが must とのこと。

shugo.net

メソッド定義の仮引数リストには括弧を付ける。 ただし、引数がない場合は、括弧を省略する。

私は Ruby でプログラムを書く仕事に就くのは難しそうです...。

実際の調査

実際の Ruby でのメソッド定義(仮引数つき)でどれくらいカッコをつける/つけない書き方があるかを調べてみました。

引数のあるメソッド定義でカッコをつける、つけないの比較

調査対象は、rubygems.org に push されているすべての gem の最新版です。Ruby 開発のために遠藤さんたちが rubygems のミラーを rubygems-mirror | RubyGems.org | your community gem host を使って管理しているので、それをありがたく使いました。

だいたい 95% の定義でカッコをつけており(w/paren)、5% のコードがカッコをつけていない(wo/paren)ようです。

「古い gem はカッコをつけないことが多いんじゃないの?」(昔よりも今のほうがカッコをつけるというルールの徹底がされてきた可能性)ということで、年別に集計してみました。

年別のカッコを使う、使わない、の比率の推移

意外にも「古いから多い、少ない」という傾向はありませんでした。真ん中あたりで盛り上がってますね。

ちょっと注意が必要なのは、ここでカウントしている年は最新リリース日であり、書いた日ではない、ということです(昔に最初のリリースをして、2024 年にもリリースしている場合は、2024 にカウントされています)。

実際の値はこちら。

年ごとの実際の値とファイル数(右軸)

ついでに、仮引数がないときにはどうかも一応調べてみました。

無引数のメソッド定義でカッコをつけるかつけないかの比較

誰もつけてないかと思ったけど、3% ほどあるんですね。なお、2023年に異常な値があったので、そこは除外しています(何が異常だったかは調べていません)。

Rails のコードを見てみると、def foo() end のように1行で書きたいときに無引数のカッコを使っている、というのを見つけました。そういうのは一行メソッド定義(def foo = nil)で消えそうですね。いや、そうでもないのかな?(def foo() = nil って書く?)

調査方法

調査には Prism を使ってみました。

require 'prism'

class ParenFinder
  TYPES = {
    true => {
      true => :paren_w_params,
      false => :paren_wo_params,
    },
    false => {
      true => :noparen_w_params,
      false => :noparen_wo_params,
    }
  }

  def self.reset
    $defs = TYPES.map{_2.values}.flatten.map{[_1, 0]}.to_h
    $defs[:files] = 0
    $defs[:time] = Time.now.to_i
  end

  reset

  def initialize file
    @file = file
  end

  def defs_rec node
    if node.type == :def_node
      paren = node.lparen_loc ? true : false
      params = node.parameters ? true : false
      type = TYPES[paren][params]
      $defs[type] += 1 # [@file, node.slice.lines.first.chomp]
    else
      node.child_nodes.compact.each{|n|
        defs_rec n
      }
    end
  end

  def defs
    ast = Prism.parse_file(@file)
    defs_rec ast.value
  rescue Exception => e
    # ignore any errors
  end
end

if ARGV.empty?
  ParenFinder.new(__FILE__).defs
  name = __FILE__
else
  process_file = -> f do
    case
    when FileTest.symlink?(f)
       # ignore (depends on data)
    when FileTest.directory?(f)
      Dir.glob(File.join(f, '*')){|f| process_file.call f}
    when /.*\.rb$/ =~ f
      $defs[:files] += 1
      ParenFinder.new(f).defs
    else
      # ignore other files
    end
  rescue Exception => e
    # ignore
  end

  name = ''.dup

  ARGV.each{|f|
    name << f
    process_file.call f
  }
  $defs[:time] = Time.now.to_i - $defs[:time]
end

pp({ name => $defs.map{|k, v|
  if Array === v
    [k, v.size]
  else
    [k, v]
  end
} })

Prism いじってる遠藤さんに使い方を聞けたということもあるんですが、簡単に調査スクリプトが作れてよかったです。というか lparen_loc メソッド(仮引数をくくるカッコの位置)とかすごいな。

Symlink の扱いは、調査対象ごとにいじる必要がありそうでした。

カッコなしでのメソッド定義が許されている理由

まつもとゆきひろさんに雑談がてら「なんでカッコがないメソッド定義を許してたんですか?」と聞いてみたところ、次のような理由とのことでした。

  • Ruby では「コマンドっぽいメソッド」の呼び出しではカッコが省略できる。例えば attr :ivar など。
  • そういうコマンドっぽいメソッドを定義するときは、同じくカッコがないように定義できたほうがいいんじゃないかと思って導入した。

雑談の場では、「そもそもコマンドっぽいメソッドなのか、書いてるほうは判断できない」、「コマンドっぽいメソッドを書く機会がそんなになさそう」などの意見があり、メソッド定義時にカッコを省略する機能は、無引数のときを除いて忘れられていそうだなぁ、ということがわかりました。

まとめ

「Ruby のメソッド定義時に仮引数があるとき、それをカッコでくくらないのは私だけなの?」という疑問から始めた調査ですが、「私だけではないけどやっぱり少ない」、ということがわかりました。よく知られたコーディング規約に反しているのが 5% もあるということで、それはそれで面白い結果なのかな?

カッコつけてるのを見ると、逆にちょっとカッコ悪いというか、そういう感じがするので、そういうコードを見かけたときにはどうぞ優しくしてあげてください。

ちなみに、STORES にはコーディング規約はないそうです。ですが、STOERS 社内のソースコードの .rb ファイルを1万ファイルくらい見てみると、なんと仮引数があるときカッコをつけないメソッド定義 0 でした!(ちなみに、仮引数がないときにカッコをつける定義も0) 皆さん社会規範にちゃんと則ってる。そんな行儀の良い(?)プロダクトに携わっていただける方は 採用サイト | STORES 株式会社 をご参照ください。