【iCARE Dev Meetup #32】OSSとの向き合い方に登壇した

@ogijun からオファーをもらって、【iCARE Dev Meetup #32】OSSとの向き合い方に登壇しました。

icare.connpass.com

@ogijun とはゼロ年代Java の勉強会なんかでお互いによく遭遇していたのがはじまりで、かつて高橋会長でかい文字のプレゼン本を深夜の大阪の JAL ホテルラウンジでゲラハックしたりと古くからの繋がり。そんな @ogijun からの「OSS をテーマにしたイベントをするので、RuboCop でひとつ」となったのがきっかけ。

私の登壇について、昨年の RubyKaigi Takeout 2021 の登壇で話の展開と尺の都合で収まらなくて削った秘蔵パートを、まるっと全面改訂して再構成したもの。なので、スライドが英語は何かを狙っていたというより、ベースの流れでそのまま作っていた部分が大きいです。

タイトルは『パターンハッチング』と『Test Driven Development: By Example』をモチーフにしたもので、ウォードのサンプルではなく、RuboCop を元にしたコントリビューションへの発見といったものをテーマにした。

当日のスライドは以下です (時間の関係上、凝ったことはしていませんが、スライドの表紙も『TDD by Example』を意識して、それっぽくしていた) 。

技術的な何かを持ち帰ってもらう話の全体像としては ghq, gem-src と git の tips を交えつつ、最後はサピア=ウォーフの仮説を含めた OSS への向き合い方みたいな話をした。

ghq もろもろの tips について、弊社から参加していたメンバーの反応はこんな感じでした (弊社に入社するとよろこんで OSS メンタリングします) 。

こんな感じで今日からはじめることができるので、まだの人はおすすめの環境構築です。また、もし既存のローカルリポジトリghqディレクトリスタイルに移管するのが手間ということであれば、ghq_transfer という gem など用意していますのでよければどうぞ。

github.com

特に今回のイベントでは、他の登壇者方の OSS 開発者の開発観を聞くことで、自分の過ごしている OSS の世界を再認識できたことでした。何となく OSS 開発者の考え方は似通ってくるようなところを感じたのは、印象的でおもしろかったです。登壇者の皆さん、iCARE さんありがとうございました!

最後に余談ですが、本発表をとおした「OSS と人生」について、X.Y.Z.→A のサードアルバム『LIFE』収録曲の『生きるとは何だ』を聴きながらストーリーを練っていました。ジャパメタ好きの人におすすめです。What is life all about?

『プログラミング言語Rust入門』を読んだ

AmazonKindle Unlimited で読めたので『プログラミング言語Rust入門』を読んでみた。いまのところ Kindle Unlimited で読める唯一の Rust 本っぽい。

書評にもあるように、誤字脱字やコーディングのコピペミス、用語ミスなどが多々あって、レビューが一度でもあればかなりブラッシュアップできたのでは、、、というものだった。また、クロージャの章に出てきた参照の参照の引数など、ここにこそ説明が欲しかったところなんかで、説明が割愛されていたのが惜しいところだった。

なので、これから読んでみようという人は、以下の The Rust Programming Language (The Book) や Rust By Example なんかを一読して基礎知識を付けた2冊目としてみるのが良さそうです (つまり、自分は The Book なんかを先に読んでいた) 。

一方で、Rust での Web やデータベース、並列処理、FFI の基本を crates.io のクレートを交えて説明されているので、言語仕様の先の活用を示した入門として分かり易かったのと、move と借用をポインタで確認する「付録A」はよかったです。

Rust 2021 に対応した『プログラミングRust 第2版』はいま読んでいるところだけれど The Book と類型の書籍ということもあって、『プログラミング言語Rust入門』ではそれらとは異なるエコシステム観点の後半が特に楽しめました。

株式会社永和システムマネジメントのエンジニアリングマネージャーをはじめていた

ちょうど先日 (2022-03-02) 、勤務先で岡島さんとエンジニアリングマネージャ職について社内ラジオ配信する機会があったので、かつて下書きしていたエントリを公開しておきます。

なので、これは勤務先を移籍したとかそういった話ではなく、昨今の社会背景に対する事業課題を解決していく必要性から、勤務先での立ち位置を変えることにした話です。

受託開発企業でのエンジニアリングマネージャーというのはあまり聞かないものですが、その実、事業課題においてエンジニアのキャリアと事業成長が有機的に繋がっている点など、サービス開発企業が抱える課題とも重複する部分の方が多いと思っています。42期に突入した弊社でも、その役割によって解決できる問題を顕在化させて解決していくぞと、主に事業部長の @m_pixy と考えてこれまで勤務先に明確に存在していなかったポジションとしたのが今回。あと、世の中にないパスなら作れば良いので、そういったキャリアパスを切り拓いていくという気持ちもあったりします。

また、個人的にはもっとも現場の開発者の気持ちが分かるのは現場であろうという考え方から、これまで現場での開発に主軸を置いていたのですが、特定の現場に軸足を置いているとどうしても事業全体の課題に対して手を動かす時間を作るのが難しかったので、事業の全体最適の観点をとったことになります。とはいえ現場のエンジニアリングから手が離れているわけではなく、パートタイム技術顧問的に関わりながら、諸課題に対する戦略の立案と遂行をしています。

といったことを、昨年8月に勤務先の第42期事業計画共有会で話していて、それを進めているのが現在です (以下、表紙のみ) 。

f:id:koic:20210807185344j:plain

連続した時間の中で様々な利害関係があるのが現実社会という枠組みということもあり、いきなりドラスティックに立ち位置を変えていたわけではなく、方向性を定めたのちに昨年から徐々にシフトしていました。

最後に、会社をプロダクトと見做す Basecamp 社の考え方が好きで、少し立場を変えて「情報化技術を通じて社会と共生する」プロダクトを良い感じにしていく気持ちです。

これは RubyKaigi Takeout 2021 より前に公開しようと思っていたエントリだったので、半年越しでようやく日の目を浴びました。

引き続きよろしくお願いします。

`Lint/InheritException` copにテコ入れした

Standard gem でのイシューについてジャスティンからメンションをもらったのがきっかけで、Lint/InheritException がいろいろと🤔だったのでテコ入れした。対象となる以下の PR に書いていることのサマリーとなる。

Lint/InheritException cop は、カスタム例外クラスの親クラスに Exception クラスを指定している場合、StandardError クラスか RuntimeError クラスに変えるように警告をする cop (RuboCop 1.25.1 時点では RuntimeError がデフォルト) 。とりわけ Java だとカスタムの例外クラスを作る際に java.lang.Exception を継承するので、Java から Ruby へスキル転換で不慣れな人のコードでかつて何度か見た覚えがあった。Lint として存在しているのは、そんなところが始まりかなと思っている (調べたら自分が RuboCop にパッチを送るようになる以前に導入されていた) 。

StandardError を継承しない、組み込みの例外クラスを許容するようにした

ひとつ目のテコ入れ。組み込みの Interrupt 例外クラスに対しても RuntimeError にするよう警告するのは、継承ツリーのまったく異なるものへの置換でおかしいのではというのが個人的な見解。以下、PR に書いた AA から抜粋。

        ---------------
        |   Object    |
        ---------------
               ^
               |
        ---------------
        |  Exception  |
        ---------------
               ^
               |
      |------------------|
---------------  -----------------
|StandardError|  |SignalException|
---------------  -----------------
      ^                  ^
      |                  |
---------------  -----------------
|RuntimeError |  |   Interrupt   |
---------------  -----------------

Interrupt と同様に、SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, SignalException, SystemExit にも同様のことが起きるようになっていて、通常これらを直接拡張することはないものの、ライブラリなどで意図して拡張したいのであれば良いのではということから許容するようにした (実際手元にある OSS リポジトリでも対象となるものがあった) 。Style ならともかく Lint としては厳しすぎる気がするし、The Open-Closed Principle の観点からも拡張に対して開けていて良かろうという判断。

Exception の変わりに RuntimeError ではなく StandardError をデフォルトで継承するようにした

ふたつ目のテコ入れとして、Exception の置換先のデフォルトが RuntimeError だったのを StandardError にした。これも PR に書いた AA を抜粋しておく。

---------------
|  Exception  |
---------------
      ^
      |
---------------
|StandardError| (default for `rescue`)
---------------
      ^
      |
---------------
|RuntimeError | (default for `raise`)
---------------

おさらいとして、StandardError (とそのサブクラス) は rescue に例外クラスが指定されていないときにデフォルトで補足するクラス、RuntimeError は例外クラスを指定せずに raise する際のデフォルトのクラスとなる。

カスタム例外クラスの親クラスをデフォルトで RuntimeError の継承とするとクラス指定なしの raise と、クラス指定ありの raise のいずれも is-a として違いがでなくなる。コンテキストによるので、RuntimeError の継承が間違っているわけではないが、抽象度の高い StandardError の方が良かろうと Exception のデフォルトの置換先を StandardError に変更した。

安全でない自動修正としてマークした

最後のテコ入れ、そもそもこれらのクラスについて振る舞いが変わるため、安全でない自動修正とマークした。

次のリリース (1.25.1 の次) でこのあたりの変更がざっと入る予定です。

mrubyにパッチを送ったきっかけ

作品を出せるアイデアが浮かぶかどうかは置いておいて、今年の TRICK に向けて『あなたの知らない超絶技巧プログラミングの世界』を読んでいます。それがきっかけで、巡り巡って mruby にパッチを送る機会があったのを書き残しておきます。新しいことを学ぶ際にいろいろと迂回する良い (?) 事例かもしれません。

eval s=%q(puts %(eval s=%q(#{s}))) のようなコードはふだん使わないプログラミング脳の使い方のトレーニングになるのですが、複雑性が上がっていくと処理系なしで検証するのはなかなか難しかったりします。そこでスマホでも通信なしで Ruby を実行できると便利ということで、最近は rubyist.app という iOS アプリで実験することがあり、その処理系が mruby となっています。

rubyist.app

主題の mruby へのパッチですが、CRuby と mruby の処理系の違いで、超絶技巧プログラミングにおけるアスキーアート定石の %w(...)*'' が動かず、差分としては Array#* に文字列の引数を渡したときの挙動の違いが原因でした。

mruby の振る舞いということで、パッチを書く前に規格を調べてみます。『X 3017:2013 (ISO/IEC 30170:2012) 』規格の『15.2.12.5.2 Array#*』からの抜粋となりますが、引数 num が文字列のときは以下のように未規定でした。

動作: a) numがIntegerクラスのインスタンスでない場合,このメソッドの動作は未規定とする。

kikakurui.com

ここで CRuby の挙動が参考となるのですが、CRuby では Array#* の引数に文字列を渡したときは、Array#join と同様の振る舞いになります。

% ruby -ve "p ['a', 'b', 'c']*''"
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-darwin19]
"abc"

このあたりの流れで mruby に送ったパッチが以下で、このエントリの時点でまつもとさんにマージしてもらっています。ありがとうございます。

github.com

パッチを書くにあたって、CRuby と mruby で API は異なるものの CRuby の rb_ary_times 関数は参考になりました。

本業の Web+DB の世界では直接的に関わりのなかったことから mruby をあまり見てこなかったのですが、デフォルトで Regexp クラスが組み込まれていなかったり、RubyGems とは異なる mrbgems という仕組みに則ったりと異なる世界観がおもしろく、超絶技巧への知識を身に付ける副産物になりました。

ちなみに処理系の検証で毎回スマホで打ち込むのはめんどうなので PC の方で mruby を試したりする際、正規表現を使うには mruby/mruby リポジトリの build_config/default.rb の conf.gem に mrbgems を指定するようです。ここでは mattn さんの mruby-onig-regexp を指定していますが、正規表現の mrbgems にはいくつかあるようです (調べていないですが rubyist.app の Regexp はまた違う実装かも) 。

diff --git a/build_config/default.rb b/build_config/default.rb
index f5e2cbb71..d64e92730 100644
--- a/build_config/default.rb
+++ b/build_config/default.rb
@@ -13,6 +13,8 @@ MRuby::Build.new do |conf|
   # conf.gem :github => 'mattn/mruby-onig-regexp'
   # conf.gem :git => 'git@github.com:mattn/mruby-onig-regexp.git', :branch => 'master', :options => '-v'

+  conf.gem :github => 'mattn/mruby-onig-regexp'
+
   # include the GEM box
   conf.gembox 'default'

今後 mruby を使う機会があるかもしれない人へのご参考まで。

もうひとつ余談ついでに書き残すと、[1, 2, 3] * '' という書き方に対して RuboCop を適用してみたところ Style/ArrayJoin cop が既に存在していて [1, 2, 3].join('') にするよう警告がでました。

ActiveRecord Oracle enhanced adapter 7.0.1 がリリースされた

ActiveRecord Oracle enhanced adapter 7.0.1 がリリースされた。yahonda さんありがとうございます。

rubygems.org

Rails 7.0 系統に対応している ActiveRecord Oracle enhanced adapter のパッチバージョンとなる。

MySQL に対応する Mysql2Adaptermysql2 gemPostgreSQL に対応する PostgreSQLAdapterpg gem といったように、各アダプターはそれぞれのドライバーを介して、データベース接続している。Oracle enhanced adapter の場合は、MRI では ruby-oci8 gem に依存し、JRuby では JDBC の Jar に依存するつくりになっている。このあたりの話は 2018 年に沖縄 Ruby 会議02 で話したので、興味があればどうぞ。

speakerdeck.com

そういうわけで、MRIOracle enhanced adapter を使う場合は ruby-oci8 のインストールが別途必要だったのが Oracle enhanced adapter 7.0.0 までで、今回リリースされた 7.0.1 からは ruby-oci8 が Oracle enhanced adapter のランタイム依存 gem として gemspec 記載されたので、別途のインストールが不要になった。特段の理由がなければ、このバージョン以降は Gemfile への gem 'ruby-oci8' 指定が不要になる。

一方、JRubyOracle enhanced adapter を使う場合は、むしろ ruby-oci8 に依存をしないようにする必要があるため、今後 MRIJRuby 向け個々にパッケージがリリースされることになる。

なお、JRuby は現行の最新安定版である 9.3 が Ruby 2.6 互換で、開発版となる 9.4 は Ruby 3.0 互換となるらしい (Ruby 2.7 互換はスキップされるとのこと) 。

github.com

そのため、Ruby 2.7 以上を要求する Rails 7.0 に対応した JRuby の安定版はまだ存在しておらず、今後 JRuby 9.4 が正式リリースされたのちに、Oracle enhanced adapter は JRuby 向けのパッケージリリースがされる予定。

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