モンキーパッチよりも古式ゆかしいpatchのほうが良いシーンがあるという話

労働の現場においては、gemにオレオレパッチを当てたい、ということがまれにあります(あ、いい忘れてましたがこれからRubyの話をします)。もちろん、オレオレパッチは基本的には避けるべきものですが、そうも言っていられない深遠なる事情というのが、労働においてはある……。労働にはいろいろあります……。

さて、オレオレパッチの是非については一旦おいておいて、gemにオレオレパッチを当てたいとき、オープンクラスを利用してモンキーパッチを当てることが一般的には多いのではないでしょうか(繰り返しになりますがここではオレオレパッチの是非は問いません)。Railsを利用している場合、config/initializers以下にモンキーパッチのためのファイルを入れて、Rails起動時にgemの挙動を拡張、あるいは変更する、というような手段が多くの場合取られるのではないかと思います。

しかし、Railsを利用している労働の現場において、先日、Railsのbootstrap前、というかRackサーバーの読み込み前にgemにパッチを当てたい、という状況が発生しました。当然、Railsのbootstrap前なので、config/initializersの読み込みを待つわけにはいきません。

苦肉の策として、モンキーパッチを諦め、bundle install のあとに古式ゆかしいpatchコマンドを利用してライブラリ自体を実際に書き換えてしまうという暴力スクリプトを書いて物事を解決したのですが、その暴力の中で気づいたことがあります。それが、タイトルにもある、「モンキーパッチよりも古式ゆかしいpatchのほうが良いシーンがある」ということでした。

モンキーパッチの場合、パッチ対象のライブラリのバージョンが上がった場合などに、モンキーパッチの作者の意図しない挙動を引き起こすことがあります。別の言い方をすると、パッチ対象ライブラリのバージョンが上がった場合は内部が変わっていることがありうるため、本来はモンキーパッチ側も新しいバージョンに追従してメンテナンスしていく必要がある(たまたまモンキーパッチ側は書き換えなくてもうまく動く、という可能性は充分にある)わけですが、実際にライブラリの更新とモンキーパッチの更新の足並みを揃えるのは結構たいへんです。

一方、patchを当てるスクリプトの場合、パッチ対象となるライブラリのファイルパスをなんらかの形で埋め込んでおく必要があります。bundlerによってインストールされたgemは、バージョンごとに一意なファイルパスを持つし、仮に同じパスにインストールされたライブラリであっても、バージョンが代わりパッチ対象のファイルが書き換わっていたらpatchコマンドでのパッチに失敗します。これがなにを意味するかというと、patchコマンドによるパッチは、モンキーパッチと異なり、特定のバージョンのライブラリのみに対するパッチとして機能する、ということです。これは、そもそも危険なオレオレパッチの影響範囲を絞るという点でモンキーパッチよりも優れている部分があると言えるのではないでしょうか。

常にモンキーパッチよりもpatchコマンドによるパッチのほうが優れている、と主張する気はありませんが、「このversionのときだけパッチを当てたいなあ」みたいなときには、古式ゆかしいpatchコマンドによるパッチを検討してみるのも、ありなのではないか、というお話でした。逆にいうと、対象ライブラリのバージョンが上がっていってもこのパッチは当て続けたいんや!!みたいな場合はモンキーパッチのほうが有利かもしれません(が、それってかなり地獄への道を歩んでいる感じがする)。

危険なオレオレパッチは捨てやすい方法を選んだほうがいい場合がある、という話だと言い換えてもよいかもしれませんね。