Ruby: 「マジック」と呼ぶのをやめよう(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

文中で言及されている記事「7 Gems Which Will Make Your Rails Code Look Awesome」の日本語版は以下をどうぞ。同記事からの引用もこの日本語版を使いました。

Railsコードを改善する7つの素敵なGem(翻訳)

Ruby: 「マジック」と呼ぶのをやめよう(翻訳)

最近「7 Gems Which Will Make Your Rails Code Look Awesome」というよく書けた良記事を読んでいて、decent_exposure gemのところで次の文言がふと目に留まりました。

マジックがあまり好きでない方はこのライブラリを使わなくてもよいでしょう
(強調は原著者)

この「mで始まる単語」が指していたのはメタプログラミングを使うライブラリです。シンプルなCRUDで十分な場合にexposeメソッドをRailsコントローラクラスに追加して、重要度の低い#index/#show/#newを生成します。

訳注: 「mで始まる単語(m-word)」という英語の言い回しは、ある品の良くない単語を暗に指すことがあります(参考)。

私はこの記事の著者に苦言を呈するつもりはまったくありませんのでご了承ください。単にいくつかの重要な点に言及するきっかけとなったに過ぎません。最近RubyやRailsのコミュニティでは「マジック」と「メタプログラミング」を一種の邪悪な同義語とみなして使うことがかなり広まっているようです。そこで私はあくまで品位を保ちつつ、こうきっぱり申し上げたいのです。

「マジック」と呼ぶのをやめよう

理由についてはこの先をお読みください。

免責事項: 説明に使っているコード例は手作りであり、いかなる特定の既存ライブラリにも基づいていません。このブログで以前起きたような「ワイらの%(ここにライブラリ名が入る)%をコケにするのか」などという反応で論点が見失われないようにするためです。

そもそも、

マジックとは何か?

Wikipediaを引用します。

マジック: 儀式、象徴、振舞い、身振り、言語を用いて、超自然的な力を振るう目的で使われる
Wikipedia: Magic (paranormal)より

次は

超自然的: 自然界の法則では説明できないあらゆるものを含む
Wikipedia: Supernaturalより

上をプログラミングに当てはめると、次のように定式化できます。

コードにおけるマジックとは、何らかの振る舞いが引き起こされるとき、その言語の熟練開発者ですらその原因を追うのが難しいか不可能な状況を指す。

これは定義として完全ではないので、やや理論的な例でこの点を明らかにしてみましょう。

class LandCruiserJ200 < Car
  def type
    :suv
  end

  def engine
    '4.5 L'
  end
end

LandCruiserJ200.new.drive! # "ブルン! ブロロロロロ!"

これはマジックでしょうか?#driveメソッドは上のコードのどこにも見当らないのに、魔法のように動作している???

まともな開発者ならここで苦笑するところでしょう。

馬鹿馬鹿しい、もちろんマジックなんかではありません。単なる継承です。#driveメソッドが親のCarクラスで定義されていることは見当がつきますし、クラスやメソッドのコードを探るのも簡単です(何らかの外部gem由来などの場合は、ドキュメントを探してもよいでしょう)。

もうひとつ例を出しましょう。

class LandCruiserJ200
  include Drivable

  def type
    :suv
  end

  def engine
    '4.5 L'
  end
end

LandCruiserJ200.new.drive! # "ブルン! ブロロロロロ!"

奇っ怪な!継承していないのに、どこから#drive!メソッドが来たのか?マジックだ!

…というか単なる普通のmixinです。Drivableのドキュメントを調べれば、これが使われる理由や詳細はもちろん、#drive!メソッドが何なのかもわかるでしょう。

もうひとつ。

class LandCruiserJ200
  drivable type: :suv, engine: '4.5 L'
end

LandCruiserJ200.new.drive! # "ブルン! ブロロロロロ!"

むむ、今度こそ説明不能な禁断の魔術に違いない!凡人には理解不能ではないか?

…というかたぶんdrivableが実はメソッドで、Classに直接定義されてるか、でなければ何らかのモジュールで拡張されているのでしょう(これはベストプラクティスではありませんが、それでもジュニアを卒業したRubyistなら簡単に見当がつくでしょう)。

LandCruiserJ200.method(:drivable) # => #<Method: Class(Drivable)#drivable>
LandCruiserJ200.method(:drivable).source_location # => /some/gem/drivable/lib/drivable.rb:123

ではどんなものがマジックなのか?

マジックをお見せしましょう。

class LandCruiserJ200Car
end

car = LandCruiserJ200Car.new
# => #<Car model="Land Cruiser J200">
car.drive! # "ブルン! ブロロロロロ!"

上のコードが動くとお考えください。なぜ動くのでしょうか。

私がこの事実と取り組まなければならなくなったら、こんなふうに推測するかもしれません。おそらく現在の何らかのスコープが規約を提供している、それは…うーん、名前がCarになっているものをすべて特別扱いするとか?それともapp/carsフォルダにあるものすべてを特別扱いしている?でなければ、carのクラスの全リストを持っているYAML設定か何かを使っている?

この振る舞いを追加する責務を持つのは一体何だろう、必要になったらドキュメントから探せるのだろうか、期待どおりに動作しない場合はどうやってデバッグすればいいんだろうか。

これは「超自然的な」振る舞いの例になっています(言語の自然な力とは明らかに無関係に何かが起きているなど)。そしてマジックは、「自然なツールや直感が通用しないので、魔導書を丸暗記しなければ魔法のコードを扱えない」というまさにその理由によって、邪悪なものです。

マジックでないものとは何か

ジュニア開発者が理解できないものはマジックに該当しない

「これはマジックが強すぎて経験の浅い開発者には理解できない」という言い方をよく見かけます。(私の記憶ですが)Matzの言うとおり「Rubyは、シンプルに開発するための複雑な言語」であって、その逆ではありません。

自動車のエンジンもコンピュータのCPUも相当複雑ですが、だからといってマジックだということにはなりません。これらを使うべきではないということにはなりませんし、マジックをなるべく使わないようにすべきということにもなりません。

モンキーパッチはマジックに該当しない

そう、モンキーパッチにはいろいろと疑問の余地があります。コアクラスへのモンキーパッチは重大な罪であると考えている人もいるほどです。

しかし別の言語からRubyにやってきた人の中には5.daysを見て「マジックだ」と呼ぶ人もいたりしますが、なぜこの人たちの言説を復唱しなければならないのでしょうか。

これは他の言語の開発者にとっては直感に反することもありますが、Rubyistにとってはそこで何が起こっているか、メソッドの由来の理解やドキュメントや実装の調べ方はおおよそ明らかです。

追記になりますが、Rubyの「オープンクラス」は(コアがオープンであることも含めて)Rubyを進化させるための重要な機能です。それによって新しい概念を実験することもできますし、必要に応じてbackportspolyfillといったgemを使って後方互換性を提供することもできます。

メタプログラミングはマジックに該当しない

そろそろ本記事の冒頭に立ち返ることにしましょう:)

「メタプログラミング」(コード実行中にコードオブジェクトを生成するなど)はRubyの非常に強力な機能であり、Rubyという言語にとって自然なものだからこそ強力たりえます。この「Rubyにとっての自然さ」によって、多くのコード設計で最初にして最善のものとして検討されます。

単に内部の値に委譲するいくつかの数学演算子をクラスに与える必要がある場合、次のようにするでしょう。

%i[+ - * /].each { |op| define_method(op) { ...

他の似たようないくつかのクラスでもこうした振る舞いを宣言的に定義する必要がある場合は、クラスメソッドのシンプルなDSLを追加するでしょう。こんなふうにいろんなことができます。

そして後になって、パフォーマンス上の理由やドキュメントを書きにくいという理由で(最近はメタプログラミングコードのドキュメント化に使えるなかなかいいツールがありますが)、あるいは以前は同じだったコードオブジェクトが同じでなくなってしまったという理由で、決定を変更するかもしれません。

しかしメタプログラミングは、いつでも引き出しから取り出して便利に使える、そうしたツールのひとつに過ぎません。

誰かが「メタプログラミングは不自然だ」と100回繰り返したからといって、メタプログラミングが不自然になることはありません

まとめ

本記事を書いた理由は次のとおりです。

(訳注: マジックに限らず)「mで始まる言葉」はコミュニティにとって有害です。この言葉はRuby世界の内外で自由に使われすぎたために悪いレッテルと化し、「単に好きでないから」とか「単に理解できないから」という理由でさまざまな手法や機能にデタラメに貼り付けられています。こんな状況では、設計を完璧に練り上げた自然なライブラリや言語機能がほとんど「使用禁止」になってしまい、多くのアイデアが立ち腐れてしまいます。

だからこそ、単刀直入に申し上げます。

Rubyistの皆様、「メタプログラミング」の同義語として「マジック」という言葉を使うのをやめましょう。

関連記事

Rubyスタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)

Ruby: delegate.rb標準ライブラリの動作を解明する(翻訳)

[Rails5] Active Support Core ExtensionsのString#inquiryでメタプログラミング

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ