Ruby: ヒアドキュメントの引数やメソッド呼び出しは「開始行」に置こう


github.com/rubocop-hq/rubocopより

「Rubyスタイルガイド」記事の元であるRubocopスタイルガイドをチェックしたところ、いくつか追加更新があったのですが、今更のようにこの書き方に気づきました。

Rubyのヒアドキュメントでは引数やメソッド呼び出しを「開始行」に置ける

スタイルガイドのコード例のままでは芸がないので、別記事の正規表現を使って試してみました。

1. ヒアドキュメントに対するメソッド呼び出し

regex = <<~MOBILE_SUITS.tr("\n", "")
\b(
YMS-(15|14|07B)|
MSN-(X2|02)|
MSM-(10|07S?|04F?|03)|
MS-(X16|X10|R09|14S|14A|14|11|09R?|07B?|06S|06J|06F|06|05B)|
MAX-03|
MAN-(X8|X3|08|07|03)|
MAM-07|
MA-(08|05H?|04X)
)\b
MOBILE_SUITS

#» "\b(YMS-(15|14|07B)|MSN-(X2|02)|MSM-(10|07S?|04F?|03)|MS-(X16|X10|R09|14S|14A|14|11|09R?|07B?|06S|06J|06F|06|05B)|MAX-03|MAN-(X8|X3|08|07|03)|MAM-07|MA-(08|05H?|04X))\b"

1行目のregex = <<~MOBILE_SUITS.tr("\n", "")というメソッドを適用して、正規表現の改行を取り除いています。

もちろん、今までどおり以下のようにメソッド呼び出しをヒアドキュメントの末尾に書くことも一応できますが、追加されたRuboCopスタイルガイドではこの書き方は「エラーを誘発する」として非推奨になっています。

regex = <<~MOBILE_SUITS
\b(
YMS-(15|14|07B)|
MSN-(X2|02)|
MSM-(10|07S?|04F?|03)|
MS-(X16|X10|R09|14S|14A|14|11|09R?|07B?|06S|06J|06F|06|05B)|
MAX-03|
MAN-(X8|X3|08|07|03)|
MAM-07|
MA-(08|05H?|04X)
)\b
MOBILE_SUITS
.tr("\n", "")

#» "\b(YMS-(15|14|07B)|MSN-(X2|02)|MSM-(10|07S?|04F?|03)|MS-(X16|X10|R09|14S|14A|14|11|09R?|07B?|06S|06J|06F|06|05B)|MAX-03|MAN-(X8|X3|08|07|03)|MAM-07|MA-(08|05H?|04X))\b"

たしかに見た目にも末尾の.tr("\n", "")メソッドがぶらぶらしていて落ち着きがありませんし、これを見落としてコードを書き換えてしまいそうです。さらに、後者の非推奨の書き方はirbやpryでは使えません(実行するとエラーになります)。

# pryで実行(プロンプトは変更しています)
» regex = <<~MOBILE_SUITS
» \b(
» YMS-(15|14|07B)|
» MSN-(X2|02)|
» MSM-(10|07S?|04F?|03)|
» MS-(X16|X10|R09|14S|14A|14|11|09R?|07B?|06S|06J|06F|06|05B)|
» MAX-03|
» MAN-(X8|X3|08|07|03)|
» MAM-07|
» MA-(08|05H?|04X)
» )\b
» MOBILE_SUITS
#» "\b(\nYMS-(15|14|07B)|\nMSN-(X2|02)|\nMSM-(10|07S?|04F?|03)|\nMS-(X16|X10|R09|14S|14A|14|11|09R?|07B?|06S|06J|06F|06|05B)|\nMAX-03|\nMAN-(X8|X3|08|07|03)|\nMAM-07|\nMA-(08|05H?|04X)\n)\b\n"
» .tr("\n", "")
sh: -c: line 0: syntax error near unexpected token `"\n",'
sh: -c: line 0: `tr("\n", "")'
Error: there was a problem executing system command: tr("\n", "")
»

2. ヒアドキュメントを引数で囲む場合

こちらのコード例はRuboCopスタイルガイドそのままです。ヒアドキュメントそのものを引数にする場合、ヒアドキュメント全体を丸かっこ()で囲まなくても、以下の「good」のように、ヒアドキュメントの開始部分だけを丸かっこ()で囲んでも同じ動作になります。かつ、RuboCopスタイルガイドでもこの書き方が推奨されています。

# bad
foo(<<~SQL
  select foo from bar
SQL
)

# good
foo(<<~SQL)
  select foo from bar
SQL

一瞬「それってありなの?」という気持ちにさせられるシンタックスシュガーですが、メソッド呼び出しや引数の丸かっこを1行目にまとめて書く方がpryでも安定して使えますし、すっきりすると思えてきました😋。

ヒアドキュメントの区切り文字列には「意味のある言葉」を使おう

RuboCopスタイルガイドではSUMMARYなどの意味のある言葉を区切り文字列に使うことというスタイルが追加されていました。たしかに、ヒアドキュメントのサンプルコードで十年一日のごとく使われているEOSなどをそのまま使うよりも意味が明確ですね。

message = <<~GREETING
  こんにちは、[hachi8833](/author/hachi8833)です。
GREETING

上の追加スタイルはRubyスタイルガイドにも反映します。

3. ヒアドキュメントの式展開を無効にする方法

この項はRuboCop作者のブログで知りました。

参考: Weird Ruby: Single-quoted Heredocs | Meta Redux

ご存知の通り、Rubyの変数の式展開#{}は、二重引用符" "では効きますが、一重引用符' 'の中では効きません。

ヒアドキュメントを使って、式展開そのものを含むメッセージを渡そうとしても、そのままでは展開が発動してしまいます。

name = "hachi8833"
message = <<~GREETING
  こんにちは、#{name}です。
GREETING

p message  # "こんにちは、hachi8833です。\n"

これも2.と同じ要領でできます。ヒアドキュメントの開始の区切り文字列を'GREETING'のように一重引用符で囲めば、期待どおりに式展開を無効にしたままヒアドキュメントを渡せます。

name = "hachi8833"
message = <<~'GREETING'
  こんにちは、#{name}です。
GREETING

p message  # "こんにちは、\#{name}です。\n"

ところで、本記事で紹介した一連のヒアドキュメントのシンタックスシュガーは、もしかするとBashの*展開をエスケープするときの記法の影響ではないかという話も聞きました。

参考: sh での変数とワイルドカードの落とし穴|てくめも@ecoop.net

# 同記事より
foo="SELECT * FROM table"
echo "$foo"
#=> SELECT * FROM table

関連記事

Rubyの式展開(string interpolation)についてまとめ: `#{}`、`%`、Railsの`?`

【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ

BPSアドベントカレンダー