うきうきぱっちぱっち
モンキーパッチはあまりいい印象はない。そもそもなんで猿やねん?という疑問も出てくるのでまずは語源から解析しよう。
ルビーのパパ、マッツさんの2017年のルビー会議のキーノートで説明があった。よると、いきなりやれるからゲリラ豪雨のゲリラでゲリラパッチが始まって、それがどこかの段階でゴリラパッチに変わって、お猿のモンキーパッチにたどり着く。
モンキーパッチすんなに関して
よく聞くお決まりみたいなもので「モンキパッチすんな」があるけど、なんでなんだろう。「後から地獄見るぞ」とか言われても具体性一切ないし、個人的に地獄どんな風景なのかも若干気になる。
もうちょっとまじめに見ると、理由はわかる。ルビー使って誰だって一回はそういう壁にぶつかることはあるだろう。いくつかのフレームワークやライブラリの上に成り立つアプリを作る途中で、ルビー標準のメソッドを呼び出そうとした。そして全く予期せぬ違う動作が起動した。
ルビーのドキュメントみても、予想通りに動くはずのものが、なぜか ArgumentErrorなりなんなりで動いてくれない。イライラしながらググってようやく犯人がわかる。StackOverflowやQiitaでも似たような事象にぶつかった質問や記事がある。理由は、使っているあるライブラリがルビーの標準メソッドを Objectのレベルで上書きして、アプリ全体に影響が及ぶ変更をしているからだ。
じゃやっぱりだめじゃん
確かにやらない方がいいようなモンキーパッチがある。上記のように、 Objectなどルビーのコアの機能の既存のメソッドを上書きするのは絶対ひかえた方がいい。
天下のRails様はActiveSupportのコア拡張で色々とルビーの中身をいじっているが、既存のものを上書きすることは基本的にしない。そしてRails使ってる人の中に、数値から簡単に日付が作れる 5.days のような時間表現や、 blank? や present? のような存在確認に使う拡張を便利と思わない人はいないと思う。(細かい仕様に文句あるのは置いといて。)
だからと言って、アプリの起動時に Object の、例えば、 to_s を上書きしてどのクラスにも影響が及ぶ破壊的なことをやらない方がいい。やったら依存ライブラリの深部で何がどう壊れても知らないという覚悟の上で。
でもっ
今ルビー2.6で話題になっている名称がある。既存のメソッドにエリアスをつけただけで話題になるほど。何かというと、今年のルビー会議の前日にマッツさんがコミットした then だ。
2.5で登場した yield_self のエリアスだけなのに、 if-then-else の then とかぶるので色々と意味が重なってしまう。そもそもユーザー側で定義できない名称だ。 nil.then { :hello } とかも成立するので、ES6のプロミスの then と使い方が似てるのに false や nil でも普通に動いちゃうのは違和感満載じゃない?
そこでナイーブに思っちゃうのは、「うちのアプリでそのプロミスっぽい書き方したいから、 nil と false の then上書きしちゃお」と。
Get ERDOS Balint’s stories in your inbox
Join Medium for free to get updates from this writer.
影響範囲計り知れない。2.6の新機能でまだフレームワークに使われてないからと言って、後半年で使われるようになったら何か確実に壊れる。「でもっ」じゃない。自分の Promise モジュールでも作って。
でも!
一方便利な拡張がしちゃだめと言われると、それも違う。モンキーパッチができるのはルビーの言語仕様の一つだ。ルビーのパパが意図してその仕様を作っている。ActiveSupportなどでプログラマはさらにハッピーになっている証拠もある。
モンキーパッチするなとルール付けるのはよくないと思っている。確かにむやみにルビーのコアクラスのメソッドを上書きするのは躊躇した方がいいとは思うが、だからと言って便利なエリアスとかもつけてはだめと言われると、同意しかねる。誰かの意見よりルビーの仕様が確立している。
例えば実際にやっているものとしては Hash#+ だ。 Array なら + で結合できるだろう?なのに Hash ではできないのがすごく違和感があった。わざわざ merge しないといけない。さらに、かっこまでつけないといけない。
irb(main):001:0> { a: 2} + { b: 3 }
NoMethodError: undefined method `+' for {:a=>2}:Hash
irb(main):002:0> { a: 2 }.merge { b: 3 }
SyntaxError: (irb):2: syntax error, unexpected ':', expecting '}'
{ a: 2 }.merge { b: 3 }
^
irb(main):003:0> { a: 2 }.merge({ b: 3 })
=> {:a=>2, :b=>3}大きな修正ではないが、 Hash 開けちゃって alias_method :merge, :+ だけすると一気に楽になる。
[1] pry(main)> { a: 2 } + { b: 3 }
=> {:a=>2, :b=>3}適時に適量で
モンキーパッチは何でも解決してくれる銀弾でもないし、むやみに使うのはよくない。ちゃんと考えて、影響範囲を徹底して調べた上でやるかどうか、決めるべき。お決まりなどじゃなく、その環境、その用途に適切なのか、自分の最善の判断で決めよう。
アプリケーションをメンテするチームで、何をどう拡張するのかをちゃんと考えて、理解した上で方針が決まる。どこかのお決まりだから、アプリ中使われる機能が全部コピペコードにならないといけないわけがない。
一方ジェムやライブラリなどを作る際に、より大きなコードベースの中で外部コードとして使われる前提なので、コアクラスのメソッドを上書きするのは非常に危険。いくら善意でも、影響範囲は想定し切れない。そして新しいメソッドを登録するとしても、できれば被りそうにない名称を選ぶように努力しよう。
実際にやってみて
上記でも述べた Hash#+ 以外に、少数の拡張はしている。文字列が複数行なのかをチェックする String#multiline? や、 Array の中身は全部ユニークなのかをチェックする Array#uniq? など、結構便利だと感じる。
ただし、 String や Array みたいに、大量に発生したり、大量のエレメントを含んだりするクラスの場合は、拡張したメソッドは負荷に耐えられるかをきちんと検証しないといけない。マニュアル作成管理・業務改善ツールのTeachme Bizの場合、一つのリクエストで場合によっては何千何万の要素を扱う必要もあるので、実装の際 Benchmark などを使って徹底して負荷テストしている。
高負荷にも耐えられるRailsのシステムを一緒に開発してくれる仲間は絶賛募集中。モンキーパッチはいいかどうか、宗教戦争にせず、議論して決めるフレンドリーなチームで働いてみない?少しでも興味あれば、気軽に連絡してもらえれば!








