LogSubscriberのflush判定を毎回の`respond_to?`呼び出しから一度限りのキャッシュに変更
LogSubscriber.flush_all!が呼ばれるたびに実行されていたrespond_to?(:flush)をキャッシュすることで、繰り返しの不要なメソッド探索を排除しました。
背景
LogSubscriber.flush_all! はRailsのログをフラッシュするための汎用インターフェースで、ログの書き出しタイミングを制御する際に繰り返し呼び出されます。従来の実装では、このメソッドが呼ばれるたびにlogger.respond_to?(:flush)を実行してロガーがflushをサポートするかどうかを毎回確認していました。
ロガーの実装は通常、アプリケーション起動後に変わることはありません。それにもかかわらず、flush_all!の呼び出しごとにrespond_to?によるメソッド探索が走るのは無駄なコストです。この変更はその冗長性を取り除きます。
技術的な変更
@supports_flush というインスタンス変数をキャッシュとして導入し、respond_to?の結果を一度だけ評価して再利用するように変更されました。
変更前:
attr_writer :logger
# Flush all log_subscribers' logger.
def flush_all!
logger.flush if logger.respond_to?(:flush)
end
変更後:
def logger=(logger)
@supports_flush = nil
@logger = logger
end
# Flush all log_subscribers' logger.
def flush_all!
@supports_flush = logger.respond_to?(:flush) if @supports_flush.nil?
logger.flush if @supports_flush
end
@supports_flushがnilのときだけrespond_to?を呼び出し、その結果をインスタンス変数に保持します。2回目以降のflush_all!呼び出しでは、キャッシュ済みの真偽値をそのまま参照するだけです。
設計判断
attr_writer :loggerを明示的なlogger=メソッドに置き換えるアプローチが採用されました。
キャッシュの有効性はロガーの同一性に依存するため、ロガーが差し替えられた際にキャッシュを無効化する仕組みが必要です。logger=メソッドを明示的に定義することで、代入のタイミングで@supports_flush = nilにリセットできます。attr_writerのままでは代入フックを差し込む余地がなく、このアプローチは必然的な選択です。
またnil?を判定に使うことで、false(flushを持たないロガー)とまだ評価していない状態(nil)を区別しています。||=やunless @supports_flushではfalseを正しくキャッシュできないため、この実装の細かさは意図的です。
まとめ
この変更は、ロガーの交換という稀なケースを正確にケアしつつ、flush_all!の繰り返し呼び出しにおけるrespond_to?のコストをゼロに近づけたものです。小さな変更ながら、キャッシュの無効化タイミングをどこに置くかという設計の明快さが際立っています。