初めに
こんにちは。「マネーフォワード クラウド勤怠」のエンジニアをしています katuo です。自分はマネーフォワードに約10ヶ月前に入社し、同時にRubyを初めて業務で触ってから約10ヶ月が経ちました。
サーバーサイドの言語でいうと静的型付け言語であるGoを約3年ほど書いていたのですが、業務で使う動的型付け言語はRubyが初めてで諸々新鮮でした。そんな中、Rubyの勉強をしていくうちにRuby自体の言語仕様を活用したテクニックなどを知る場面が幾度もあり、今回はその中の1つの「ゴーストメソッド」について書いていきたいと思います。
ゴーストメソッドとは
例えば以下の図のような、Class Aを基底クラスとした継承関係を持つClass達が存在するとします。
ObjectからClassA, ClassB, ClassC が〇〇メソッドを定義していない場合、Callerが〇〇メソッドを呼び出そうとしたとき、NoMethodError等の例外が吐かれます。(メソッド探索で対象メソッドを発見できなかった場合に吐かれる例外)
メソッド探索の最中にmethod_missing
メソッドを定義するとCallerによって呼び出されたメソッドが継承元まで遡っても見つからなかったときにmethod_missing
メソッドが呼び出されます。(method_missing
メソッドは派生クラスから基底クラスに向かって検索されます)
例えば↑のように継承チェーンの中のClass Bにmethod_missing
が定義されており、Caller側から〇〇メソッドを呼び出した場合、method_missing
で処理を拾うことができます。この原理を活用することで継承チェーンの中に定義されていないメソッドをCaller側(利用者側)が呼び出した時にあたかも定義されているかのように見せることができます。このような実際には存在しないが呼び出すことができるメソッドをゴーストメソッドと呼びます。
クラウド勤怠での利用例
では次にゴーストメソッドがクラウド勤怠でどのように利用されているのかについての一例をお話しします。クラウド勤怠が提供する機能の中に打刻、従業員などの様々なデータをCSVファイル形式で出力する機能があります。以下の図はCSVファイル出力を行う処理の一部を視覚化したものです。(※ 厳密なクラス図ではありません。矢印の向きは参照方向を表しています)
大雑把な処理の流れとして、MonthlyAttendanceItemCsvOption
またはDailyAttendanceItemCsvExporterOption
でCsvBuilder::Item
(CSVファイルのデータ構造を規定したクラス)のインスタンスを集め、CsvBuilder
にインスタンスに渡し、CsvBuilder
のbuild
メソッドからCsvBuilder::Evaluator
のevaluate
メソッドを呼び出し、CsvBuilder::Item
のlambdaであるvalue
を実行します。
今回解説するゴーストメソッド(method_missing
)はCsvBuilder::Evaluator
クラスに定義されています。(※コードの一部抜粋)
class CsvBuilder class Evaluator < BasicObject # @param locals [Hash] evaluateで渡されたblockがアクセス可能な変数を定義する def initialize(locals = {}) @locals = locals end # lambdaを評価する # # @param block [Lambda] # @return [any] def evaluate(&block) instance_exec(&block) end private def method_missing(method_name, *args) if @locals.key?(method_name) && args.size.zero? @locals[method_name] else super end end ...<略>
↑のmethod_missing
が呼ばれるメカニズムとして、lambdaのvalue
がevaluate
メソッド内の instance_exec を使って実行されたとき、lambda内に記述されているメソッドを見つけられず例外が吐かれ、これをmethod_missing
がフックすることで呼び出されます。
※ lambdaのvalue
のイメージが付きやすいように、実際のソースコードの一部添付しておきます。
CsvBuilder::Item.new( ...<略> value: -> { employee.employment_position_at(to)&.name }, // ※ employeeが見つからないと言う例外が発生 & method_missingでフックされる ...<略> )
定義されているmethod_missing
メソッド内部で行なっていることはシンプルで@local
にlambdaであるvalue
のメソッド(例: employee
)が存在する場合はそのメソッド(例: employee
)を返し、存在しない場合はsuperを実行します。(引数をつけずにsuper
を実行した場合、NoMethodError
の例外が吐かれます)
これらの構成により、実行時に呼び出されるvalue
内のメソッドをCsvBuilder
のbuild
メソッドのlocal
を通じて動的に差し込むことができるようになり、将来的にクラウド勤怠がユーザーが設定した項目をCSVファイル出力できる動的な機能を追加する場面が来たときに比較的対応しやすい、拡張性&柔軟性が高い設計になっています。
最後に
ゴーストメソッドを利用すると、潜在的なバグを埋め込んでしまったり、可読性が下がってしまう(特に入門者などがコードを読む時)などのデメリットもあります。一方で記述するコードの量を減らせたり、柔軟性のあるコードも書けたりするメリットもあるのでその辺も各場面で判断して利用していきたいなと思います。 最後までお読みいただきありがとうございました。
またマネーフォワードではエンジニアの採用活動を続けております。もし興味があればこちらをどうぞ。
参考文献
マネーフォワードでは、エンジニアを募集しています。 ご応募お待ちしています。
【サイトのご案内】 ■マネーフォワード採用サイト ■Wantedly ■福岡開発拠点 ■京都開発拠点
【プロダクトのご紹介】 ■お金の見える化サービス 『マネーフォワード ME』 iPhone,iPad Android
■ビジネス向けバックオフィス向け業務効率化ソリューション 『マネーフォワード クラウド』
■だれでも貯まって増える お金の体質改善サービス 『マネーフォワード おかねせんせい』