Techouse Developers Blog

テックハウス開発者ブログ|マルチプロダクト型スタートアップ|エンジニアによる技術情報を発信|SaaS、求人プラットフォーム、DX推進

RubyKaigi 2025 - On-the-fly Suggestions of Rewriting Method Deprecations (Day3)

ogp

はじめに

こんにちは、株式会社Techouseバックエンドエンジニアをしています、KOKI(@pr2n1022)です。先日参加したRubyKaigi 2025で特に印象に残った、Masato Ohba(@ohbarye)さんによる「On-the-fly Suggestions of Rewriting Method Deprecations」についてご紹介します。セッションで紹介されていたスライドはこちらです。

このセッションでは、開発者なら誰もが経験する「メソッド非推奨化」に伴うコード修正を、もっと楽にするためのツールとその仕組みが提案されていました。

非推奨対応、正直つらくないですか?

ライブラリやフレームワークが進化すれば、古い機能が非推奨 (deprecated) となり、新しい機能への移行が促されるのは自然な流れです。ライブラリ作者としても、互換性を維持しつつ新しい機能を提供したい、ユーザーにはスムーズに移行してほしい、という思いがありますが、一方でライブラリユーザにとっては、作者が設定した「移行期間」内に対応しなければならないというプレッシャーと、実際の修正作業の手間が悩みの種となっています。

一つ一つ警告メッセージ (warning: Using the last argument as keyword parameters is deprecated... のような) を確認し、コードベース全体から該当箇所を探し出して修正していく地道な作業は、非常に時間がかかります。実際、私自身も他言語ですがPythonでPandasやGymといったライブラリの非推奨警告を無視し続けた結果、ある日突然インターフェースの変更でプログラムが動かなくなり、苦労した記憶があります。業務で使用されるような大規模なコードベースでは修正箇所が数百に及ぶこともあり、手作業による単純な置換ミスが新たなバグを生む危険性も孕んでいます。なにより、こうした作業は本来注力したい新機能開発やリファクタリングから開発者の意識を奪ってしまいます。

これまでも、READMEやCHANGELOG、YARDタグ (@deprecated) によるドキュメントでの告知、Kernel#warnGem::DeprecateActiveSupport::Deprecation を利用した実行時警告、そしてRuboCopやShopify製のDeprecation Toolkitのような静的・動的解析ツールなど、様々なアプローチで非推奨対応は支援されてきました。しかし、これらの方法で警告を検知できたとしても、最終的なコード修正は依然として開発者の手作業に委ねられることが多いのが現実です。

実行時に自動修正!?Deprewriterとは

そこでohbaryeさんが提案するのが、非推奨対応の理想形とも言えるアプローチです。それは、プログラムの実行時に非推奨メソッドの呼び出しを検知し、あらかじめ定義されたルールに基づいて自動的にコード変換を提案・実行するツール「Deprewriter」です。

このユニークな発想の着想元は、Smalltalk系のオブジェクト指向言語「Pharo」にあります。Pharoには同名の「Deprewriter」という仕組みが実装されており(詳細はこちらの論文で解説されています)、ライブラリ作者は非推奨メソッドに変換ルール (transformation rules) を記述しておくだけで、ユーザーがそのメソッドを実行した際に、呼び出し元のコードが自動的に書き換えられるのです。

なぜ静的解析ではなく「実行時」なのでしょうか?講演では、RubyやPharoのような動的型付け言語においては、静的解析だけではメソッドの正確な特定が難しいケースがあると指摘されました。例えば、異なるライブラリに同名のメソッドが存在する場合 (Homonymous Methods) や、多くのクラスでオーバーライドされているメソッド (Method Overriding) などです。実行時のコンテキストを利用することで、実際にどのオブジェクトのどのメソッドが呼ばれたかを正確に把握でき、より安全で適切な書き換えが可能になるというわけです。

ohbaryeさんが開発したRuby版「Deprewriter」は、この実行時解析のアイデアをRubyの強力なメタプログラミング機能とエコシステムの力を借りて実現しています。ライブラリ作者 (あるいはユーザー自身) は、以下のようなDSLで変換ルールを定義します。

# 例: メソッド名をold_methodからnew_methodに変更し、引数をそのまま渡す
deprewrite :old_method, to: "new_method({{arguments}})"

# 例: 呼び出し方を限定し、引数をキーワード引数に変更する
deprewrite :change_arg,
           from: ".call_node[name=change_arg]", # オプション: 呼び出しノードを特定
           to: <<~RULE
             change_arg(
               message: {{arguments.arguments.0}},
               to: {{arguments.arguments.1}}
             )
           RULE

このルール定義には、ASTベースのコード変換ツールであるSynvertの構文・ライブラリ (node-query-ruby, node-mutation-ruby) が活用されています。内部的には、非推奨メソッドが呼び出されると、Gem::Deprecate と同様の手法で呼び出しをインターセプトし、caller情報 (Gem.location_of_caller) から呼び出し元のファイルパスと行番号を特定します。次に、そのソースコードをPrismパーサーでAST (抽象構文木) に変換し、Prism::Visitorとnode-query-rubyを使って該当する呼び出しノード (Prism::CallNode) を正確に見つけ出します。最後に、node-mutation-ruby を使って定義された to ルールに基づきASTノードを書き換え、修正後のコードを生成するという流れになっています。

Deprewriterで開発はこう変わる

もしライブラリにDeprewriterの変換ルールが同梱されていれば、私たちライブラリ利用者は、特別な準備をすることなく、そのメリットを享受できます。普段通りにテストコードなどを実行するだけで、非推奨コードが使われている箇所が検出され、修正案や適用可能なパッチファイルが自動的に生成されるようになります。

「実行時にコードを書き換える」と聞くと、その安全性について気になる方もいるかもしれません。プロダクション環境で意図せずコードが書き換わってしまうのは避けたい事態です。この点について、Deprewriterは本番環境での自動書き換えを推奨しているわけではありません。むしろ、開発環境やテスト環境で開発者が意図的に有効にし、その結果を確認しながら利用することを想定しています。 安全性を確保し、開発者が状況に応じて挙動を選択できるように、Deprewriterは3つの動作モードを提供しています。

  • Logモード: 実行時に検出した非推奨箇所と、提案される修正内容を警告としてログに出力する。コードは変更されない。ステージング環境での確認などに適している。
  • Diffモード: 修正内容をdiff形式のパッチファイルとして生成する。開発者は生成された差分を確認し、問題なければ手動でパッチを適用できる。ローカル環境やテスト環境で、安全性を確保しつつ修正を進めたい場合に最適である。内部ではDiffy gemが利用されている。
  • Rewriteモード: 検出した非推奨箇所を、直接ソースコードファイルに書き込む。ローカル環境やテスト環境で、十分に信頼できる変換ルールに対して利用することが想定される。

これらのモードを使い分けることで、非推奨対応にかかる時間と労力は劇的に削減されるでしょう。手作業によるミスの心配や、大量の警告を前にした心理的な負担からも解放され、開発者はより創造的で本質的なタスクに集中できるようになるはずです。

Deprewriterのこれから

Deprewriterは非常に有望なツールですが、講演では現状の課題と今後の展望についても触れられました。

技術的な課題としては、まずメタプログラミングを多用した複雑なメソッド呼び出しの解析・変換はまだ難しい場合がある点が挙げられます。また、対応できる非推奨パターンにも限界があり、例えば引数の型を変更する、メソッドのレシーバを変更する、メソッド自体を完全に削除するといったシナリオには、対応が難しかったり、追加の工夫が必要になったりします。パフォーマンス面でも、現状では非推奨メソッドが呼び出される度に解析処理が実行されるため、オーバーヘッドが懸念されます。さらに、Synvertの内部ライブラリやDiffyといった外部gemに依存しており、スタンドアロンではない点も挙げられます。

しかし、それ以上に大きな課題は、このDeprewriterというツールと「変換ルールを記述する文化」を、Rubyのエコシステム全体、特に多くのライブラリ作者コミュニティに受け入れてもらえるか、という点です。理想はライブラリ作者が変換ルールをgemに同梱してくれることですが、現実的には、当面はユーザー側が使いたいライブラリに対してモンキーパッチなどを利用して独自に変換ルールを記述する、という「妥協案」的な使い方から広まっていくのかもしれません。

今後のツール自体の改善としては、対応パターンの拡充、パフォーマンス最適化、依存関係の削減などが挙げられています。これらの課題を克服し、もしDeprewriterのような仕組みが広く普及すれば、ライブラリやRuby本体のバージョンアップに伴う追従作業が格段に楽になり、Rubyエコシステム全体の進化を加速させる力となるでしょう。発表では、将来的に言語コアや標準ライブラリレベルでのサポートといった可能性についても触れられていました。

おわりに

ohbaryeさんの講演は、開発者が日々直面する非推奨対応の課題を取り上げ、それを自動化によって解決しようとする「Deprewriter」の仕組みと可能性を示唆するものでした。 実行時の情報を用いてコード変換するというアプローチは、Rubyの持つ柔軟性を活かした興味深い試みです。このツールが今後の開発でさらに洗練され、普及していくことに期待が高まります。 素敵な発表、ありがとうございました!興味を持った方は、ぜひ公開スライドGitHubリポジトリもチェックしてみてください。

Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。 jp.techouse.com