こんにちは、グループ経営ソリューション事業部の米久保です。
はじめに
「リファクタリングをする時間がない」「リファクタリングの必要性を関係者に説得しなくてはならない」という悩みをよく聞きます。
リファクタリングという用語が広く普及した結果、意味の希薄化が発生し、元来の意味と異なる使われ方を目にすることもあります。また、似通った用語としてリアーキテクティングというものがあり、混同されがちです。
リファクタリングの課題と向き合い、それらを解消するための正しいアプローチを取れるようになるには、リファクタリングに対する理解を深めることが重要です。本稿では、最初にリファクタリングの定義を確認します。そしてリファクタリングを必要とする技術的負債がどのように生まれるのか、それを計画的に返済するにはどうすべきかについて述べます。
リファクタリングとは
リファクタリングの定義
Martin Fowler 氏の有名な著書[1]では、リファクタリングは以下のように定義されています。
リファクタリング(名詞) 外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること。
リファクタリングする(動詞) 一連のリファクタリングを適用して、外部から見た振る舞いの変更なしに、ソフトウェアを再構築すること。
リファクタリングという用語のあいまいな使用については氏も以下のように述べています。
長年にわたって、業界では「リファクタリング」という用語を、コードをきれいにするあらゆる作業を指すものとしてあいまいに使ってきました。しかし、上記の定義では、コードをきれいにするための特定の手法であることを示しています。リファクタリングは振る舞いを保ちつつ小さなステップを適用していくことであり、ステップを積み重ねていくことで大きな変化をもたらしていくものなのです。
Kent Beck 氏も最近の著書[2]で以下のように述べています。
「リファクタリング」という言葉は、機能開発の長い中断を指す言葉として使われ始めたときに致命傷を負った。「振る舞いを変更することなく」という条項さえもなきものにされ、「リファクタリング」は簡単にシステムを破壊できるようになった。
氏の言うように、リファクタリングという言葉の不用意な使用がリファクタリングに対する共通認識を歪め、健全なリファクタリング活動を阻害してしまうリスクとなり得ます。次項以降では解像度を上げてリファクタリングを捉え直したいと思います。
振る舞いのサイズ
外部から見た時の振る舞いを変えないことはリファクタリングにおける必須要件です。注意すべきは、一口に振る舞いと言ってもそのサイズはまちまちであるということです。ソフトウェアは大小さまざまな構成要素から成り立ちます。設計の抽象度で捉えると、下図のように4つの抽象レベルに分けて考えることができます[3]。
アーキテクチャ、あるいはアーキテクチャを土台として実現されるソフトウェアは、求められる機能要求や品質特性を包括的に提供します。モジュールは、ユースケースやその一部を実現する振る舞いを提供します。ユーザー目線で何らかの意味をなす振る舞いだと言えるでしょう。コンポーネントは、モジュールにおける部分的な振る舞いを提供します。税計算処理のようにドメイン知識に対応する振る舞いもあれば、データの永続化のように技術的な振る舞いもあります。クラス(関数型言語であれば関数)が提供するのは最小単位の振る舞いです。
振る舞いと自動テストとの対応
外部から見た振る舞いを変えないという条件を満たす上で重要な役割を果たすのが自動化されたテストです。コードを直しても変わらずテストが通過(Pass)することで、振る舞いが壊れていないことを確認し、安心してリファクタリングを行うことが可能となります。
さて、振る舞いにサイズがあるように、自動テストにもサイズがあります。どのサイズのテストをどの程度行うべきかの方針立てをテスト戦略と呼びますが、テストピラミッドはその代表的なパターンです。よりサイズが小さく、実行コストの低いテストの比率を高くするという考え方です。
振る舞いを実現するソフトウェア構成要素と、テストピラミッドの対応付けを次の図に表します。
テストピラミッドはテストタイプ(ユニットテスト/インテグレーションテスト/E2E)を用いて表現することもありますが、ここではテストサイズ[4]を用いて表現しました。モジュールに対するテストは、データベース等のプロセス外通信を実際に行うか、テストダブルで代替するかによってSmallにもMediumにもなり得ます。
テストピラミッドをベースとしたテスト戦略を前提とすると、図の上位に行くほどテストによって固定できる振る舞いの量は少なくなります。Largeサイズのテスト(主にE2Eテスト)だけでは、ソフトウェア全体としての包括的な振る舞いに対して機能退行(リグレッション)を漏れなく検知することはできません。下位のテストによってより詳細な振る舞いが担保されることを前提とします。
アーキテクチャ/ソフトウェアの抽象レベルでは、定義どおりにリファクタリングを行うことは実質困難なので、リアーキテクティングという用語を用いるのが妥当でしょう。たとえば、モジュール境界の引き方や相互作用の見直し、一部モジュールのマイクロサービス化などがリアーキテクティングに該当します。
リファクタリングテクニック
本稿において、リファクタリングとは設計の抽象レベルのうちクラス、コンポーネント、モジュールのレベルにおいて適用可能なものであると考えます。次に、リファクタリングテクニックについて考えてみましょう。
書籍[1]には多くのリファクタリングテクニックがカタログ化されています。その多くは、「関数の抽出」、「ループの分離」のような単一クラス内またはメソッド内に閉じたものです。「ポリモーフィズムによる条件記述の置き換え」、「サブクラスによるタイプコードの置き換え」のような複数のクラスにまたがるものも一部あります。前者のテクニックの中には、IDEの機能を使ってワンクリックで実施できるものも多くあります。
書籍[5] では、デザインパターンを適用してコードを改善する、パターン指向のリファクタリングテクニックが紹介されています。そのほとんどがコンポーネントレベルのものです。
モジュールレベルのリファクタリングについては、私が知る範囲では体系立てられたテクニック集は見当たりません。この抽象レベルでは複数コンポーネントの協調によってユースケース相当の振る舞いを実現しますが、パターン化できるテクニックが少ないからかもしれません。中核ロジックと処理フローロジックの分離[3]や、SOLIDなどの原則の適用によって設計を洗練させることは可能ですが、小さなテクニックの機械的適用を超えたより高度な知的作業が求められます。
なお「リファクタリングしたくてもそもそもテストコードが存在せず、テスト容易性も低い」レガシーコードにどう立ち向かえばよいかについては、書籍[6]が参考となるでしょう。
リファクタリングサイズ
以降の議論のため、リファクタリングのサイズを下記表のとおり3つに分類します。
リファクタリングサイズ | 設計の抽象レベル | テストサイズ | リファクタリングテクニック | 所要時間 |
---|---|---|---|---|
Small | クラス | S | 小さなステップ | 短い |
Medium | コンポーネント | S | パターン指向 | 比較的短い |
Large | モジュール | S または M | - | 長い |
技術的負債はどうして生まれるのか
仮に熟練したプログラマーがきれいなコードを書き上げたとしても、時間経過とともに内部品質が劣化することは不可避です。なぜでしょうか。
コードの守備範囲
最初に完成したコードは、その時点で判明している振る舞いに適合した設計となっています。その振る舞いの中にある類の可変性が存在するなら、その軸において柔軟性を持っているはずです。たとえば、「サブクラスによるタイプコードの置き換え」のリファクタリングを適用していれば、サブクラスやEnum列挙子によって新たな振る舞いを追加可能となっているでしょう。SOLID原則のひとつであるOCP(オープン・クローズドの原則)による拡張性です。
このように、コードには変更に対してコスパよく柔軟に対応可能な範囲、いわば守備範囲があります。
変更への対応
守備範囲内の変更であれば、変更を実現するにあたってリファクタリングは不要か、最小限で済ませることができます。問題は、守備範囲外の変更が発生したときです。
その場合、既存の設計はその変更に対する柔軟性を持ち合わせていないので、取りうる選択肢は以下の2つです。
- リファクタリングにより柔軟性を持たせた上で変更を行う
- その場しのぎのパッチワーク的な対応を行う
プログラマー倫理に従えば前者の一択なのですが、実際にはトレードオフが発生し判断を求められます。守備範囲外ということは、既存の設計では想定できていなかった類の変更だということなので、リファクタリングには一定のコストが生じます。それに変更そのもののコストを加えたトータルのコストが、変更によって得られる便益の向上と比較して正当化できるかという話になるのです。
もちろん個々の変更案件毎ではなくプロダクトやサービス全体として中長期で費用対効果があるかという判断基準も大事です。しかし、その変更を素早くリリースすることがビジネス的に重要である場合に、以下のような判断は果たして間違っていると言えるでしょうか?
技術的負債を返済する
ソフトウェア開発におけるあらゆる設計判断は、最終的にビジネス上の何らかの価値につながるべきです。そう考えるならば、一時的に技術的負債を許容することも判断としては正当化されます。重要なのは、技術的負債の発生を認識することと、それを管理していくことです。
早い返済が吉
パッチワーク的な対応を行うと、設計にほころびが生じます。このほころびは容易に積もり積もるだけでなく、やっかいなことに相互干渉する場合もあります。10回のパッチワーク的な対応を行ったら、技術的負債の量は10 ではなく50や100にもなり得るということです。技術的負債は複利であると考え、手遅れになる前に計画的な返済が必要です。
新規開発時
新しい機能を新規に開発する際は、可能な限りコードをきれいにし、技術的負債を生まないことが前提となります。新規開発時点で乱雑なコードが、その後きれいにリファクタリングされる可能性は残念ながら非常に小さいです。
最も効果的な方法はテスト駆動開発(TDD)のプラクティスを採用し、Red - Green - Refactor のサイクルの中で高頻度にリファクタリングを実施することです。とは言え、TDDの習得に一定の修練が必要なのも事実です。最低限、プルリクを出す前には時間を取ってリファクタリングしましょう。
変更時
変更に対応する際は、リファクタリングの要否を検討し、リファクタリングが必要な場合はそのコストを見積もります。コストが想定を上回る場合は、開発リーダーやプロダクトオーナーと相談しましょう。今すぐリファクタリングを行うのか、それとも先送りにするかの意思決定が必要です。
先送りにする、すなわち技術的負債の発生を受け入れる判断をしたならば、その返済プランとしてバックログアイテムにリファクタリングのチケットを登録します。リファクタリングチケットの解消方針はいくつか考えられます。チームとして戦略を定めて、継続的な負債返済を実現しましょう。
開発チームの裁量で返済する
リファクタリングに関して、プロダクトオーナーやビジネスサイドへの説得が必要、という課題を耳にします。
私は、サイズがSmallやMediumのリファクタリングは開発チームが自分たちの裁量で実施できるのが望ましいと考えています。そのためには、技術的負債の発生をなるべく小さく抑えつつ、定期的に解消していくプロセスや文化が欠かせません。
次の図は、JVMなどの処理系においてGC(ガベージコレクション)によってメモリが自動解放されるイメージです。アプリケーションに割り当てるメモリが不足すると、参照が切れてゴミとなったオブジェクトが回収され、割り当て可能なメモリが増えます。いよいよメモリが足りなくなると、広範囲を対象としたフルGCが実行されます。フルGCは実行時間が長くなりアプリケーションへ与える影響も大きいため、なるべく回避したいものです。
リファクタリングによる技術的負債の返済は、GCによる割当可能メモリの回収に似ています。長い時間を要し、その間開発を止めてしまうような大きなリファクタリング(Largeサイズ)やリアーキテクティングは、なるべく避けたいものです。そのためには、小さいリファクタリング(Smallサイズ、Mediumサイズ)を継続的に実施し、技術的負債を膨らませないことが大切です。
説得方法
とは言え、現時点ではそのような文化が醸成されておらず、リファクタリングの必要性を認めてもらうためにステークホルダーの説得が必要な場合はどうしたらよいでしょうか?
まずはファクトを示す必要があるでしょう。たとえば変更リードタイム、リリース後の欠陥発生率などのメトリクスです。時間経過に伴う推移を示し、緊急かつ重要な課題であることを認識してもらうとよいでしょう。コストとリターンを明確にすることも効果的です[7]。
大規模なリファクタリング
Largeサイズのリファクタリングや、リアーキテクティングが必要なときはいずれやってきます。これには相当のコストと期間を要するため、ステークホルダーを巻き込んだ上でプロジェクトやタスクフォースとして取り組むべきです。プランニングや社内調整、コミュニケーション設計などが包括的にまとめられた書籍[8]は大いに参考となるでしょう。
まとめ
本稿では、リファクタリングの定義を再確認した上で、リファクタリングのサイズを3つに分類しました。「リファクタリングが必要」と漠然と言うのではなく、どの粒度のリファクタリングを指しているのか解像度を上げてコミュニケーションを取ることが、開発業務の計画や進行において重要ではないかと思います。
後半では、技術的負債が生み出される仕組みや、負債に対する取り組み方について述べました。ソフトウェア開発の現実においては一定量の技術的負債は発生を回避できないという前提で、戦略的に立ち向かわなければなりません。
参考文献
- [1] Martin Fowler 著、児玉公信・友野晶夫・平澤 章・梅澤真史 訳『リファクタリング(第2版) 既存のコードを安全に改善する』オーム社(2019)
- [2] Kent Beck 著、吉羽 龍太郎・永瀬 美浦・細澤あゆみ 訳『Tidy First? 個人で実践する経験主義的ソフトウェア設計』オライリー・ジャパン(2024)
- [3] 米久保 剛 著『アーキテクトの教科書 価値を生むソフトウェアのアーキテクチャ構築』翔泳社(2024)
- [4] Simon Stewart 氏による記事『Test Sizes』Google Testing Blog(2010)
- [5] Joshua Kerievsky 著『Refactoring to Pattens』Addison-Wesley(2005)
- [6] マイケル・C・フェザーズ 著『レガシーコード改善ガイド 保守開発のためのリファクタリング』翔泳社(2009)
- [7] 松岡 幸一郎 氏による記事『「リファクタリングの時間」を確保する技術』株式会社ログラス テックブログ(2025)
- [8] Maude Lemaire 著『Refactoring at Scale: Regaining Control of Your Codebase』O'Reilly Media(2020)
執筆:@tyonekubo、レビュー:@nakamura.toshihiro
(Shodoで執筆されました)