RuboCop Oracleをリリースした

RuboCop Oracleを実装してリリースした。

github.com

長年 RDBMSOracle (Active Record Oracle enhanced adapter) を使った運用をしているが、これは無停止リリースを行うにあたってのマイグレーションの tip を cop にして含んでおいたもの。

最初の v0.1.0 リリースで含んでいるのは Oracle/OnlineIndex cop 単品。これは CREATE INDEX の際に ONLINE オプションをついていないマイグレーションファイルを検知する cop です。

docs.oracle.com

Oracle/OnlineIndex cop

データベースのインデックス追加を行う際は ONLINE オプションを付けないと OLTP (オンライントランザクション処理) が有効にならず、オンライン稼働のままインデックス作成を行おうとするとオンライン処理とそれぞれ処理待ちになりうる (結果としてオンラインサービス影響が起きうる) 。

この Oracle/OnlineIndex cop の bad と good は以下。options: :online を付与することで OLTP が有効になる。

# bad
add_index :table_name, :column_name

# good
add_index :table_name, :column_name, options: :online

マイナス面としては OLTP を有効にしたときよりも処理が遅くなるといったことがありうるが、無停止でサービス影響なしで進めるのを第一に ONLINE オプションを付けておこうという判断をするプロジェクトには有効だと思う。

また Oracle/OnlineIndex cop のオプションとして MigratedSchemaVersion というものがある。たとえば、RuboCop Oracle 導入以前に db/migrate/202104130150_add_title_index_to_articles.rb まで本番環境に適用済みであれば、それ以前のマイグレーションファイルへの ONLINE オプションの付与を検知したくない場合に、適用済みのマイグレーションバージョンを記すことで、適用済みのマイグレーションファイルへの検出を防ぐことができる。

Oracle/OnlineIndex:
  MigratedSchemaVersion: '202104130150'

git による並列開発で、必ずしもマイグレーションバージョンのタイムスタンプが過去から未来へと流れているわけではないが、だいたいはうまくいくだろう。bin/rails db:migrate:status あるいはその内部 API を使えばさらに誤検知を防げるだろうが、静的解析の域を超えるので実装はしないことにしている。そのあたりはコードレビュー掛け合わせになるだろう。

開発雑記

RuboCop Oracle の開発構想自体はずいぶんと前からあって、一番の悩みは名前だった。Active Record Oracle enhanced adapter をもう少し前面に出した名前にするか悩んだが、gem で公開するときの名前をシンプルにしたかったのと、gem + RuboCop の世界で Oracle を冠して困るようなことはなかろうとやや大きめの名前だが RuboCop Oracle にした。 また、RuboCop Rails に含めた cop にすることも少しだけ考えたが、Oracle adapter 特化なので独自の gem にした。

そのほか、RuboCop Rails でもマイグレーションファイルへのルール検知をしたいという PR がいくつかあるが、マイグレーションファイルは適用済みかどうかの観点も重要なため取り込めていないものがある。今回の MigratedSchemaVersion はその課題に対するひとつの習作となっている。デフォルトで db/schema.rb のスキーマバージョンを活用するなど、もう少しリッチにするかもしれない (など、まだ改善の余地がある) 。

あとプロダクト自体のドキュメンテーションはもう少し整えておく予定。

Oracle x Rails でのサービス運用をしているプロジェクト向けですが、ご活用ください。

Ruby 3.0.1, 2.7.3, 2.6.7, 2.5.9 がリリースされた

Ruby 3.0.1, 2.7.3, 2.6.7, 2.5.9 がリリースされた。安定版へのメンテナンス、リリースありがとうございます。

脆弱性対応

Ruby 3.0 系から、このたび EOL になった 2.5 系まで含めると、以下3つの脆弱性への対応となるようです。

使っている Ruby のバージョンと環境によって対象となる脆弱性が異なるため、各自ひととおりのリリースと CVE アナウンスを見て対処すると良いと思います。

Ruby 2.7.3 への arguments forwarding 構文への拡張のバックポート

Ruby 2.7.3 には Ruby 3.0 から arguments forwarding 構文への拡張のバックポーティングがありました。以下のように ... の前の引数にも対応します。

% cat example.rb
def foo(arg1, arg2, ...)
  puts('## arg1')
  puts(arg1)

  puts('## arg2')
  puts(arg2)

  puts('## ...')
  puts(...)
end

foo('foo', 'bar', 'baz', 'qux')

% ruby example.rb
## arg1
foo
## arg2
bar
## ...
baz
qux

Parser gem に対応 PR を開いておいたので、近いうちに RuboCop でも解析可能になる予定です。

github.com

2021年4月6日同日追記

Parser gem 3.0.1.0 としてリリースされました。ご活用ください。

rubygems.org

RuboCop 1.12.1 がリリースされた

先月のパッチ会で @osyo-manga さんが、Fukuoka.rb 200回 LT 大会 (だったと思う) あたりで話題に上がっていたらしい RuboCop のバグに関するパッチ話を持ってきてくれて、その流れで後日 PR を開いてくれた以下の修正パッチが目玉。

github.com

問題としては、日本語のようなマルチバイト圏で、RuboCop の警告ハイライト範囲に誤差が出るというものだった。問題のサンプルとして、以下のコード例に対する修正される前と後の振る舞いです。

% cat example.rb
{
  "あいうえお" => value,
  "かきくけこ"   => value
}

Before

マルチバイト文字が関わった警告ハイライトがずれている。

% rubocop -v
1.12.0

% rubocop example.rb
Inspecting 1 file
C

Offenses:

example.rb:1:1: C: [Correctable] Style/FrozenStringLiteralComment: Missing frozen string literal comment.
{
^
example.rb:2:3: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
  "あいうえお" => value,
  ^^^^^^^
example.rb:3:3: C: [Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.
  "かきくけこ"   => value
  ^^^^^^^^^^^^^^^^^^
example.rb:3:3: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
  "かきくけこ"   => value
  ^^^^^^^
example.rb:3:13: C: [Correctable] Layout/SpaceAroundOperators: Operator => should be surrounded by a single space.
  "かきくけこ"   => value
            ^^

1 file inspected, 5 offenses detected, 5 offenses auto-correctable

After

マルチバイト文字を考慮した警告ハイライトになっている。 (注: デザインの関係でこの記事ではずれていますが、ターミナルで期待しているハイライトがされていることを確認できます。)

% rubocop -v
1.12.1

% rubocop example.rb
Inspecting 1 file
C

Offenses:

example.rb:1:1: C: [Correctable] Style/FrozenStringLiteralComment: Missing frozen string literal comment.
{
^
example.rb:2:3: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
  "あいうえお" => value,
  ^^^^^^^^^^^^
example.rb:3:3: C: [Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.
  "かきくけこ"   => value
  ^^^^^^^^^^^^^^^^^^^^^^^
example.rb:3:3: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
  "かきくけこ"   => value
  ^^^^^^^^^^^^
example.rb:3:13: C: [Correctable] Layout/SpaceAroundOperators: Operator => should be surrounded by a single space.
  "かきくけこ"   => value
                 ^^

1 file inspected, 5 offenses detected, 5 offenses auto-correctable

Unicode::DisplayWidth gem というキャラクターの文字表示幅を見てくれる gem があって、RuboCop ではそういったマルチバイト文字の処理を行う際に使っているのですが、ユーザーに表示するフォーマッター部分で使われていなかったことが原因でした。

rubygems.org

@osyo-manga さんパッチありがとうござました!

そのほか、RuboCop 1.12.1 の変更履歴は以下です。

github.com

それではハックを続けましょう。

社内向けに「達人プログラマーの基礎技術勉強会」を行った

5年くらい前にアジャイルジャパンの長崎サテライトで登壇した内容を、当時と組織メンバーの顔ぶれも変わっていることもあり社内向けに話した。

www.slideshare.net

参加メンバーからは「保守はバグ修正ではなく機能開発であり、テストがそれを支えることを (改めて) 知れた」「見積りによるブランチ戦略を知れた」など、「バージョン管理」「テスティング」「プロジェクト自動化」の三本柱を背景にした、見積りと計画づくりとブランチ戦略の繋がりを伝えることができて良かったのではと思う。

ネタ本を少し紹介。

『ソフトウェア開発 55の真実と10のウソ』は、ロバート・L・グラスが記したソフトウェア工学の本。書籍タイトルが若干トンデモ感あるかもしれないけれど、中身はきちんとしている。ソフトウェア工学を過去の歴史的背景を踏まえて学びたいような人におすすめ。

『達人プログラマー ソフトウェア開発に不可欠な基礎知識』は、Pragmatic Bookshelf から出版されていた「バージョン管理」「テスティング」「自動化」の3冊が一冊の訳本になったもの。git 以前のバージョン管理ツールだったり、取り扱われているツールの賞味期限は切れ気味だと思うけれど、行間のテキストは今でも通じるものがあると思う。当時の Pragmatic Bookshelf ファンたちを追体験したいような人には良いかもしれない。

『達人プログラマー』と聞いて思い浮かべるのは、こちらが多いと思うので載せておきます。

本編で話した、見積りと計画作りとブランチ戦略については密接に関連しているものの、そのあたりをリンクさせた話はあまり見かけないので、そういった実践知を共有する機会にはなったんじゃないかなと思う。

あと、相対見積りに使われるポイントには 1, 2, 3, 5, 8, 13 のフィボナッチ数を使いますが、指を立ててポイントを表現する際に「5pt より大きい見積りは手に余る」という真面目な駄洒落を言えたので満足です (実際 8pt のストーリーは手に余る) 。

Ruby のエイプリルフール新構文 2021

Downward assignments という名前で Ruby に提案された機能 (mame さん今年もありがとうございます) 。

p(2 * 3 * 7)  #=> 42
  ^^^^^var

p var         #=> 6

bugs.ruby-lang.org

エイプリルフールということを失念していて「これは!」とおもしろく思った一方で思ったのが、以下のような RuboCop のテスト機構とぶつかった場合に、テストの仕組みを考える必要があるかもしれないという点だった (がんばりましょう) 。

expect_offense(<<~RUBY)
  def func
    some_preceding_statements
    x = something
    ^^^^^^^^^^^^^ Redundant assignment before returning detected.
    x
  end
RUBY

github.com

また他の機能提案として、mrkn さんからはこんな提案が開かれていました。

irb(main):001:0> x = 3
=> 3
irb(main):002:0> 2x
=> 6
irb(main):003:0> def pi = Math::PI
=> :pi
irb(main):004:0> 2pi
=> 6.283185307179586

bugs.ruby-lang.org

エイプリルフールネタでいうと、Ruby 3.0 で正式採用された Endless Method Definition の例もあるので、気になる人はウォッチしてみましょう。

2021年4月5日追記

いずれの提案もエイプリルフール終了ということでクローズされたようです。

`String#split`と`String#chars`の小咄

最近、String#splitString#chars まわりの Cop をメンテナンスしたり実装した際の、それらメソッドに関する知見を書き残しておきます。

String#split の小咄

RuboCop Performance に string.split(/re/) より string.split('re') の方が速いので、Cop にどうかという提案があったのがはじまり。

split の引数が文字列で済む場合は、正規表現ではなく文字列を渡すのは良いとして、正規表現と文字列で振る舞いが異なるものがある。

たとえば split の引数が空白なしの // である場合は '' と結果は同じなので、安全に置換できる。

'a    b    c'.split(//)
#=> ["a", " ", " ", " ", " ", "b", " ", " ", " ", " ", "c"]
'a    b    c'.split('')
#=> ["a", " ", " ", " ", " ", "b", " ", " ", " ", " ", "c"]

しかし split の引数が空白一つの / / である場合は ' ' と結果が異なり、戻り値に互換性がなくなる。

'a    b    c'.split(/ /)
#=> ["a", "", "", "", "b", "", "", "", "c"]
'a    b    c'.split(' ')
#=> ["a", "b", "c"]

なお、空白2つ以上は、互換性があるため安全に置換できる。

'a    b    c'.split(/  /)
#=> ["a", "", "b", "", "c"]
'a    b    c'.split('  ')
#=> ["a", "", "b", "", "c"]

このあたり戻り値の違いへの考慮に関して、つい今しがたリリースした RuboCop Performance 1.10.2Performance/RedundantSplitRegexpArgument の既知の誤検知については修正しておいた。

github.com

String#chars の小咄

たとえば split の引数が空白なしの // である場合は '' と結果は同じなので、安全に置換できると前述した続き。Twitter での @onk のツイート。

Ruby 1.9 以前は戻り値に違いがあった。

% ruby -v
ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-darwin13.0.2]

% ruby -e "p 'foo'.split(//)"
["f", "o", "o"]
% ruby -e "p 'foo'.chars"
#<Enumerator: "foo":chars>

Ruby 2.0 以降は戻り値に差異がなくなったため、String#chars を使った方が明確な名前になると思う。

% ruby -v
ruby 2.0.0p648 (2015-12-16 revision 53162) [x86_64-darwin13.0.2]

% ruby -e "p 'foo'.split(//)"
["f", "o", "o"]
% ruby -e "p 'foo'.chars"
["f", "o", "o"]

こういった歴史的経緯があるとき、アンラーニングしきれず手癖で split の方を使ってしまったりするケースもあると思う。

このあたり次の RuboCop のリリース (たぶん 1.12.0) で、Style/StringChars cop として追加される予定です。

社内向けに「Rails Doctrine勉強会」を行った

Rails のテクニカルな話は、新機能がリリースされたり業務で困ったら調べたりなどで日頃から触れている一方で、そもそもの Rails の理念やコミュニティなどを焦点に触れるというのはあまりないのではと以前から思っていたので、勉強会という形で開催してみた。

本家の Rails Doctrine は以下。

rubyonrails.org

勉強会では、ちょうど takahashim さんRails Doctrine を新訳されていたので、そちらを使わせていただきました。ありがとうございます。

qiita.com

本筋の流れを踏みつつ、『設定より規約』がなぜ嬉しいのか Rails 以前の世界との対比の補完や、美しいコードそれ自体の価値、ゼロサムが難しいときは大体の人が嬉しいような決定を選ぶという考え方などを Rails Doctrine ベースで話したりしていた。

RubyRails の歴史を思い出しながら話している中で、Rubyオープンクラスを活用した Symbol#to_proc による items.map(&:to_s) という書き方は、当時 Rails から Ruby にポーティングされたすごい発明だったのを思い出したので、ひとつの事例として話したりしていた。これはわずかな変更で世界を変えたすごいパッチなので、ぜひ読んでもらいたい。

github.com

『大きな傘を広げる Push up a big tent』の締め括りのあとに話したこととして、オープンなアクティビティ以外でも、1日を過ごす仕事の時間で新しい RubyRails を使える環境にアップグレードすることはとても価値があり、新しく迎え入れる人に新しい環境を用意していけると良いですねという話で締め括った (古い Ruby, Rails を使うことになるより、新しい Ruby, Rails の方がわくわくしますよね?) 。

なお、「Rails Doctrine 勉強会」というタイトルは「DHH 勉強会」という名前でも良かったかもしれないというのが、開催してみての感想でした。もし未読であれば一読してみると面白いと思います。

ちなみに私の勤務先に入社すると、同僚の ima1zumi メンバーがまとめてくれている勉強会の esa 記事を読むことができます。

agile.esm.co.jp