RuboCop 1.24.0 と Ruby 3.1 対応

RuboCop 1.24.0 がリリースされました。

github.com

このリリースでは Ruby 3.1 のリリースに先行して、いくつかの Ruby 3.1 対応が入っています。

RuboCop の Ruby 3.1 対応の主だったものについて、ユーザー影響のありそうなものをピックアップして記しておきます。なお、現状で Ruby 3.1 サポートは experimental という位置づけです。

既存の Style/HashSyntax による hash value omission サポート

既存の Style/HashSyntax で、Ruby 3.1 の hash value omission をサポートするようにしています。

github.com

bad / good は以下。

# EnforcedShorthandSyntax: always (default)

# bad
{foo: foo, bar: bar}

# good
{foo:, bar:}


# EnforcedShorthandSyntax: never

# bad
{foo:, bar:}

# good
{foo: foo, bar: bar}

デフォルトではハッシュの値を省略できるケースはすべて省略する always オプションが適用される。これは ESLint のデフォルトに準拠させたことが理由のひとつ。

今月の Rails/OSS パッチ会 (2021年12月) で、この辺りを話題にしたところ、osyo さんの「ハッシュのキーと値が同じ場合は書かず、異なる (意図のある) 値は明示的に書く」というのは、考え方としてすっきりした。Ruby 3.0 から Ruby 3.1 への移行によって「省略可能できるようになったか」ではなく、もし最初から hash value omission を備えた言語だったら?という考え方と言えるのかもしれない (システム開発でのあるべき姿への考え方っぽい) 。

なお、最近の Ruby の構文変更では大きなもののようで、私が仕事で見ている Rails アプリケーションのひとつで、この Style/HashSyntax をドッグフィーディングしたところ、3,000 以上が hash value omission の対象になりました。

このようにインパクトはそれなりにありそうなものの、いかんせん周辺で事例が少なくて「Hash の書き方が、こうなるのどうですか?」と EnforcedShorthandSyntax: always 適用後の感想をヒアリングしても、「 (シンタックスへの) 慣れなのかな?」みたいな感じで戸惑いがまだありそうな感じなので、Cop 自体に調整は入っていくかもしれないです。 (3値での調整の余地を残せるように、UseShorthandSyntax: true のような真偽値ではなく、EnforcedShorthandSyntax: always といった文字列というデザインにもしているのだった)

そのほか、Layout/HashAlignment cop と Layout/SpaceAfterColon cop について、hash value omission を使った際にレイアウト崩れが起きないような対応を入れています。

新規 Naming/BlockForwarding cop による anonymous block forwarding サポート

既存の Naming/BlockForwarding で、Ruby 3.1 の anonymous block forwarding をサポートするようにしています。

github.com

bad / good は以下。

# EnforcedStyle: anonymous (default)

# bad
def foo(&block)
  bar(&block)
end

# good
def foo(&)
  bar(&)
end

# EnforcedStyle: onymous

# bad
def foo(&)
  bar(&)
end

# good
def foo(&block)
  bar(&block)
end

デフォルトでは anonymous block argument syntax が適用されます。これはブロック変数が概ね &block&proc に準ずる名前で、構文上 & にしても情報落ちはないだろうという理由から。反対に従来の記法に強制したい向けに explicit というオルタナティブを用意しています。なお、こちらも Style/HashSyntax 同様に、今後フィードバック次第で調整が入っていくかもしれません。

これらの変更は、RuboCop 1.24.0 で TargetRubyVersion: 3.1 の設定で試してみれます。それぞれの Cop にオートコレクトを実装しておいたので、新しい構文の世界を見てみたい方、お試しください。

なお、執筆時点の、Ruby 3.1 への構文アップデート自体については以下をどうぞ。

koic.hatenablog.com

Offers Magazineに技術職のキャリアとコミュニティに関する記事を寄稿した

少し前に Offers さんより、キャリアをテーマにしたアドベントカレンダーというオファーをいただいて Offers Magazine に執筆した。ちょうど1年ぶりの文筆物。

offers.jp

昨今の自分のキャリア形成は「技術コミュニティ」と不可分なところがあるので、そのあたりにフォーカスを当てた記事になります。良ければご一読ください。

また前回に続き、編集の峪さんには、執筆のオファーから編集多岐に渡りお世話になりました。ありがとうございます。

Ruby 3.1の構文アップデート

この記事は「ESM Advent Calendar 2021」の1日目の記事です。

adventar.org

RuboCop のバックエンドでは、Ruby を解析して AST (抽象構文木) の Ruby オブジェクトとして扱えるようにする Parser gem を使っています。Parser gem のメンテナンスは、Ruby の parse.y に入った構文のアップデートを元に行っています。本エントリでは parse.y の観察を元にして、クリスマスのリリース予定までカウントダウンとなっている Ruby 3.1 構文のアップデート (2021年12月1日時点) をまとめてみます。

所感としては、Ruby 3.1 ではいくつかの構文への省略記法を中心に導入されるといった印象です。

新構文

Hash リテラルの値省略の構文が追加される

ruby/ruby@c60dbcd で導入されました。

値を代入した変数を Hash リテラルの要素に記す際に、変数名を Hash のキーとして、値を省略できるようになります。

a = 1
b = 2

{a:, b:} #=> {:a=>1, :b=>2}

以下と同意です。

a = 1
b = 2

{a: a, b: b} #=> {:a=>1, :b=>2}

個人的には、ES6 のプロパティのショートハンドという例もあり、キーと値を "DRY" にできるので良さそうという印象を持っていますが、明示的に書くという見方の好みもありそう?といった印象です。

匿名ブロック移譲の構文が追加される

ruby/ruby@4adb012 で導入されました。

ブロック引数を移譲する際にブロック変数名を省略できるようになります。

def foo(&)
  bar(&)
end

Ruby 3.0 までは引数に何か名前が必要です。

def foo(&block)
  bar(&block)
end

個人的には、&block&proc 以外ほとんど名前をつけることなさそうなので、そういった情報落ちがない (たぶんほとんどの場合) はこの & という象徴で十分良さそうに思っています (移譲専用というのが少し残念に思うくらい良さそう) 。

なお、匿名ブロック移譲のメソッド定義の外で bar(&) 単独で使おうとすると no anonymous block parameter エラーが発生します。

パターンマッチに pin オペレーターが追加される

ruby/ruby@2186347ruby/ruby@fa87f72 で導入されました。

パターンマッチの新構文となる pin オペレータをサポートするようになります。大元のサンプルから持ってくると ^ を使った式をこんな感じでパターンマッチ中で取れるようになります。

Prime.each_cons(2).lazy.find_all { _1 in [n, ^(n + 2)] }.take(3).to_a
#=> [[3, 5], [5, 7], [11, 13]]

既存構文の拡張

endless メソッド定義の本体メソッド呼び出しの括弧が省略可能になる

ruby/ruby@31794d2 で導入されました。

endless メソッド定義の本体メソッド呼び出しの括弧が省略可能になります。

def foo = puts 'Hello'

Ruby 3.0 までは引数にカッコが必須です。

def foo = puts('Hello')

1行パターンマッチのメソッド呼び出しの括弧が省略可能になる

ruby/ruby@ecb6d6a で導入されました。

1行パターンマッチのメソッド呼び出しの括弧が省略可能になります。

[1, 2] => a, b

Ruby 3.0 までは右辺にカッコが必須です。

[1, 2] => [a, b]

Arguments Forwarding のメソッド仮引数のカッコが省略可能になる

ruby/ruby@13a9597 で導入されました。

Arguments Forwarding のメソッド仮引数のカッコが省略可能になります。

def foo a, ...
end

Ruby 3.0 までは仮引数にカッコが必須です。

def foo(a, ...)
end

またデフォルト引数と Arguments Forwarding を一緒に定義できるようにもなっていました。これはカッコの有無に関わらず、Ruby 3.0 では構文エラーだったものです。

def foo(a, b = 42, ...)
end

ここで紹介した構文アップデートは Parser gem 3.0.3.1 までで対応済みで、RuboCop で使おうとすると匿名ブロック移譲でエラーになるのが現状です。修正パッチは PR 送付済みですので、クリスマスリリースには RuboCop でもひととおりの Ruby 3.1 構文は使えるようになっていると思います。

なお、Ruby 3.1 は開発版となります。ちょっと試してみようという場合、rbenv であれば以下でインストールができます。

% rbenv install 3.1.0-dev

以上、Ruby 3.1 の先行プレビュー記事でした。クリスマスリリースが楽しみですね。

「ESM Advent Calendar 2021」の 2日目は @m_pixy のエントリです。

Ruby 3.1を使ってRuboCopでエラーが起きた場合の対処

先日、Ruby 3.1 で起きる each_conseach_slice の非互換について記しましたが、RuboCop で非互換の影響を受ける部分とその対処について記しておきます。

koic.hatenablog.com

対象となる組み合わせは Ruby 3.1 と RuboCop 1.22.2 以下です。

RuboCop 1.22.2 までは以下のパッチが当たっていません。

github.com

そのため、Layout/BlockAlignment cop が有効になっている場合は、Ruby 3.1 での rubocop コマンドの実行で以下のエラーが起きえます。

      # ./lib/rubocop/cop/layout/block_alignment.rb:97:in `start_for_block_node'
      # ./lib/rubocop/cop/layout/block_alignment.rb:82:in `on_block'
      # ./lib/rubocop/cop/commissioner.rb:100:in `public_send'
      # ./lib/rubocop/cop/commissioner.rb:100:in `block (2 levels) in trigger_responding_cops'
      # ./lib/rubocop/cop/commissioner.rb:160:in `with_cop_error_handling'
      # ./lib/rubocop/cop/commissioner.rb:99:in `block in trigger_responding_cops'

(デフォルトで有効になっている Cop なので、遭遇率はそれなりにあるかもしれません。)

この問題に遭遇した場合の対処方法を列挙します。

1. RuboCop 1.22.3 以上にアップグレードする

これが本手です。問題が解決されている RuboCop 1.22.3 以上にアップグレードしてください。

2. Layout/BlockAlignment cop を無効にする

.rubocop.yml へのワークアラウンドとして、.rubocop.yml に以下を指定してください。

Layout/BlockAlignment:
  Enabled: false

RuboCop へのユーザーフィードバック にあったように、Ruby 2.4 サポートをしたい場合は RuboCop 1.12 までしかアップグレードできません。こういったアップグレードできない事情がある場合のひとつの選択肢になります。

3. モンキーパッチをあてる

例示しないのですが upstream の修正パッチ 同様のモンキーパッチをアプリケーションで当ててください。将来的に実装が変わる可能性があるため、おすすめしないのですが Ruby 2.4 と Ruby 3.1 を同時にサポートして Layout/BlockAlignment cop を有効にしたいケースの選択肢になります。

その他

いちおう他にも CI で問題を解決するという手段もありますが、本手としては RuboCop 1.22.3 以上にアップグレードするになります。また、Ruby 3.1 での each_cons の挙動の変更についてフィードバックを重ねるというのも考えはしたものの、each_cons の挙動変更自体はユーザーにとって自然な方向に向かっていくものだと思うので、付いていける変更具合であれば付いていきたい気持ちから、私の方でフィードバックを重ねることはしないことにしました (もし困っていてお気持ちのある方がいれば、お任せます) 。

なお、この問題について、Rails/OSS パッチ会で osyo さんや amatsuda さんに相談に乗ってもらいました。ありがとうございました。

次回の Rails/OSS パッチ会は 2021年12月21日(火) です。参加は以下の Discord URL までどうぞ。

discord.gg

銀座Rails#39に登壇した

銀座Rails#39に登壇した。

主催者の morimorihoge さんからは、3年目あたりを経ったエンジニアに響くような話を題材にというオファーで頂いていた。

"3年経った" がひとつのキーワードになるものの、参加者にはベテランもいるわけで「3年目をひとつの節目に、3年目以降いつなんどき遭遇するかもしれないポイント」みたいな形で話を構成してみた。当日のスライドは以下。

静的サイトジェネレータ繋がりの yasulab さん、okuramasafumi さんから始まり、自分の方も okuramasafumi さんのライブコーディングセッションから、名付けで悩むシーン (名付けは難しい!) をメンション先とさせてもらったりと、銀座Railsとしても部分部分でリンクした話になっていたのではと思っている。

登壇中に気づいたことだけれど、意識的、無意識的に『リファクタリング』や『Patterns of Enterprise Application Architecture』といった、マーチン・ファウラーの足跡からの引用が多めだったみたいで、マーチン・ファウラーの影響力の大きさを再認識する機会にもなりました。マーチン・ファウラーは偉大。

今回発表の機会をもらって話したものの、なかなか現実は難しく私自身うまくできているところの話や失敗談も入れた一方で、現在進行形であまり手が回っておらず絶賛進行中の話もあったりしていて、本編の内容を一緒に解決していこうという方がいれば、中途/新卒、ビジネスパートナーいずれの立ち位置もエンジニア募集中ですのでお声がけください (直近ですと私や colorbox メンバーたちが関わっているプロジェクトへの参画メンバーを熱烈募集中です) 。

agile.esm.co.jp

楽しかったです。主催者の morimorihoge さんはじめ、発表者、参加者、スポンサーのみなさんありがとうございました。

銀座Rails#39に登壇します

2021年11月19日(金) に開催される「銀座Rails#39」に登壇します。

ginza-rails.connpass.com

銀座Rails#9 以来、30回ぶり2度目のゲスト登壇です。

テーマは、「動いた!」の先へ踏み出す、システムのメンテナビリティ向上の基礎技術です。

主催者の森さんからは、新卒から3年経ったくらいのタスクをこなせるようになったエンジニアに向けて、さらに成長していくためのとっかかりや抑えておくべきスキル、考え方など 「エンジニアとして働けるようになってからのレベルアップ」についての話をしてもらいたいとのオファーを頂いているので、そんな感じの話をする予定です (まだこの世にスライド1枚もないですが) 。

よければ遊びに来てください。

Ruby 3.1 での each_cons と each_slice の (コーナーケース?) 非互換変更

先日、RuboCop の ruby-head CI が落ちていたので、bugs.ruby-lang.org にフィードバックしていたイシューが以下。

bugs.ruby-lang.org

osyo さんがコメントで教えてくれた PR が以下で、each_conseach_slice の戻り値が nil から self に変わっているというものだった。

github.com

以下は破壊的変更が起きる (単純化した) サンプルです。ブロックで条件によって break を使った戻り値を使うか、each_cons としての戻り値を使うか判定するようなロジックが遭遇したケースです。

Ruby 3.0 以下

% ruby -v
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin19]

% irb
irb(main):001:0> [1, 2, 3].each_cons(2) { |i| break i if true}
=> [1, 2]
irb(main):002:0> [1, 2, 3].each_cons(2) { |i| break i if false}
=> nil

Ruby 3.1

% ruby -v
ruby 3.1.0dev (2021-10-26T00:30:42Z master 7d4c59203f) [x86_64-darwin19]

% irb
irb(main):002:0> [1, 2, 3].each_cons(2) { |i| break i if true}
=> [1, 2]
irb(main):003:0> [1, 2, 3].each_cons(2) { |i| break i if false}
=> [1, 2, 3]

たしかに振る舞いとして戻り値 nil より自然になる気もするが、Ruby 3.1 向けに進んでいる NEWS などで見かけなかったので、影響をどれくらい加味された変更かよく分かっていなかった。

Rails/OSS パッチ会で osyo さんと NEWS にあるといいですねと話していて、osyo さんがパッチを出してくれたのが以下。

github.com

ということで、こういったケースで Ruby 3.1 での非互換変更として影響を受ける可能性があるので、いちおう each_conseach_slice で同様のことを行っていないか見ておくと良いです。

(なお、最初に遭遇した RuboCop の方はそもそものロジックが 🤔 な部分もあったので、Ruby 3.1 互換のロジックに修正済みです。)