「リファクタリングの時間」を確保する技術
はじめに
ソフトウェア開発において、リファクタリング、つまりコードの保守性を高める活動は、ソフトウェアの価値を高める上でとても大切ですよね。
しかし、「リファクタリングの時間が確保できない」「リファクタリング実施のための同意が得られない」という話を耳にすることがあります。
リファクタリングは「絶対やった方がいいのは感覚としてはわかっている、でもその必要性ををうまく伝えられない」となりがちな性質があるのです。
この記事では、リファクタリングの時間を確保するために、どんなことを考え、何をステークホルダーに伝え、具体的にどのようなタイミングで実施していくといいのか、について解説します。
ポイントまとめ
リファクタリング時間確保のポイントを端的に説明すると、以下の通りになります。
- リターンとコストを明らかにする
- 複数の実施パターンを選択肢として持ち、柔軟に選べるようにする。
- その中でも、日頃の小さいリファクタリングで成功体験を積み、徐々に規模を大きくする
これについて、順を追って解説していきます。
リファクタリング時間確保の難しさ
リファクタリング時間確保が難しいのはなぜでしょうか。その理由を一言で表現すると、
リファクタリングは、かけるコストに対して得られるリターンがわかりにくいから
に尽きます。
「10時間エンジニアの時間を投資すると、翌月から5時間ずつ開発工数が削減できますよ」
と言われたら、非技術者でも「じゃあやろう、いつやる?」となるわけです。
では、そうならないのはなぜでしょうか?
リターンとコストのうち、コストに関しては、延べ時間である程度計算できるのでわかりやすいです。ここには不確実性があるので、不確実性を織り込んだ見積もりができれば議論することができます。
一方、難しいのはリターンですね。特に、定量的に価値を聞かれて答えに困る、という経験をお持ちの方はいらっしゃるのではないでしょうか。
ここで大切なのは、
「定量的に価値を測定できない」≠「価値がない」
ということです。
価値はあるが、測定しにくい。
それがリファクタリングに本質的につきまとう課題なので、その前提でどうするかを考えていく必要があります。
リファクタリングの定義とその目的
まず、リファクタリングの定義と目的について確認しましょう。
リファクタリングとは、「ソフトウェアの内部構造を改善しつつ、外部から見た動作を変えない修正を加える行為」です。これにより、ソフトウェアの内部品質を向上させます。ソフトウェア品質特性で言うと、主に保守性を向上させることになります。
その結果として、以下のような効果の発生を目指します。
-
開発速度向上
- 可読性の向上: コードの複雑性を下げ、理解を容易にします。
- 機能拡張性の向上: 機能追加や修正に必要なコストを下げ、速度を向上させます。
-
外部品質向上=バグの発生確率を低減
- 開発難易度の低減: ロジックを簡素化することにより、開発難易度を下げ、外部品質向上につなぎます。
- テスト容易性向上: テストすべき内容が明確になり、テスト設計が容易になります。ユニットテストなど小さなテストも実行しやすくなります。
つまり、目的を端的に表現すると 「リファクタリングとは、生産性と品質改善のための投資」 です。
この目的が最も大切です。生産性と品質を改善したくない人はいないはずなので、ここに同意できれば、あとはリターン・コストの大きさと、コストの支払い方法の問題に分解することができます。
(厳密には、前述の通り「内部品質向上により生産性と外部品質を向上させる」ですが、長くなるのでここは諸々込みで「品質改善」と表現しています)
リファクタリングの必要性
もう一つ重要な、押さえておくべきことをお伝えします。
「リファクタリングはどんなに優れたチームでも必要になるものである」 ということです。
わざわざ後からリファクタリングするよりも、最初から保守性の高いコードを書けばいいではないか、と思いますよね。しかし、それは以下のような不確実性や制約があるため、困難です。
-
技術的な不確実性:
- 要件の変更や新技術の登場により、初期の設計が最適でなくなる可能性がある
- 開発チームのスキルや知識が向上し、より良い実装方法が見つかることがある
-
要件/仕様の不確実性:
- プロジェクトの初期段階では、最終的な製品像が完全に固まっていないことが多い
- ユーザーフィードバックや市場の変化により、要求が変わる可能性がある
-
時間的な制約:
- 納期やリソースの制限により、最初から理想的な設計や実装を行う余裕がない場合がある
- 短期的な目標達成を優先するあまり、コードの品質が犠牲になることがある
-
技術力の制約:
- 保守性の高い設計や実装を行うためのスキルや経験が、チーム内で十分でない場合がある
- 新しい技術や設計パターンの導入時に、チーム全体が同じペースで習得し、適切に活用することが困難な場合がある
これらの要因により、保守性は放っておくと自然と下がっていき、生産性や品質が低下します。リファクタリングの文脈では3、4の「制約」が特に注目されがちですが、1、2の「不確実性」の観点も重要です。
不確実性や制約がないプロジェクトはありません。 そのため、適切に保守性を向上するための手を打たなければ、長期的には開発チームの生産性、品質は低下するものなのです。
リファクタリングが必要な状況を恥じる必要はありません。大切なのはその状況にどう対応するか、ということです。
リファクタリングを行うために重要なこと
以上を踏まえ、リファクタリングを実施するために重要なことは、コストとリターンを明確にすることです。
「リファクタリングとは、生産性と品質改善のための投資」だと書きました。となると、そのリターンにコストが見合えば実行した方がよく、見合わなければ実行すべきではないと考えられます。そのため、「どんなリターンを期待して、どれだけの時間をかけるか」を常に意識することが重要です。
リファクタリングのリターンをどう考えるか
前述の通り、リファクタリングのリターン(価値)は、定量化が難しいことが多いです。そこで、まずは定性的な価値を言語化することが重要です。シンプルに「リファクタリングすると何が嬉しいの?」に答えられるようにしましょう。
それを押さえた上で、できる範囲で定量的に表現できないか検討する、という順番で考えます。
2つの例を具体的に紹介します。
例1:ファイルインポート機能のリファクタリング
状況:
既存のファイルインポート機能に新しいファイル形式を追加するのに、現在2日かかっている。今後も新しいファイル形式の追加は想定される。
リファクタリング案:
インポート処理をリファクタリングによって汎用化し、新形式の追加コストを下げる。
リターン:
定性的な価値は「将来の機能拡張のコストを下げる」とまず考えます。
それを定量化すると、以下の通りになります。
- リファクタリング後は、新形式の追加が0.5日で可能になる(1.5日の削減)と想定
- リファクタリングには5日かかる
リターンとコストが釣り合う点を計算すると、「1.5日の削減 × 4 = 6日」なので、今後新形式の追加が4つ以上見込まれるのであれば投資に対してリターンが見合うから実施しましょう、と論理的に提案、判断ができます。
例2:バグが多発しているコードのリファクタリング
状況:
特定の機能に機能拡張する度にバグが頻繁に発生し、対応工数に多くの時間を費やしている。この機能の拡張は今後も継続することが見込まれる。
リファクタリング案:
既存コードをリファクタリングして可読性を上げ、ユニットテストを充実させる。
リターン:
定性的な価値は、バグ発生を防ぎ、それによる対応コストを削減することです。
これを定量化するならば、今後の見込みのバグの対応コスト(アラートの対応、調査、お客さま対応など含む)を計算し、それを削減すると考えられます。
なお、これは多少強引な定量化ではあります。バグの発生見込みは確率論通りに発生するわけではありませんし、バグの問題は対応コストだけでなくステークホルダーに対して迷惑をかけてしまうことなどもあります。対応コストによる定量化は、あくまで「価値はあるけど定量化が難しい」ことへの対策の一つだと考えてください。
必要性の同意におけるコツ
これらの例のように、リファクタリングのリターンを具体的に示すことで、その必要性をより明確に伝えることができます。
この際に役立つコツとして、「問題解決の5階層」があるので、併せてご紹介します。
ここで重要なのは、下の階層から順番に認識を合わせる必要があるということです。
例えば、「今後の品質向上のためのリファクタリングが必要(4.施策)」の同意を得るためには、その下の階層の認識を合わせる必要があります。
つまり、
- 事象: 機能拡張の度にバグが○件発生し、それぞれ平均○分時間をとられるので、月に○時間の対応工数がかかっている
- 問題: 新規機能開発工数がバグ対応工数で圧迫されている
- 原因: 該当コードの可読性やテスト容易性が低いため、バグを生みやすいコードになっている
- 施策: 該当コードをリファクタし、可読性やテスト容易性を向上させる
- 効果: 今後のバグ発生リスクが削減される
と整理をすると、「4.施策」の必要性の同意が得られない場合、「1.事象」「2.問題」の認識があっていない可能性があるので、そこから確認していくことが必要ということになります。
詳細は次の解説記事をこちらをご覧ください。
リファクタリングの実施タイミング
リファクタリングを効果的に行うためには、適切なタイミングを組み合わせることが重要です。ここでは、リファクタリングの主要なタイミングを4つ紹介します。①→④の順にコストが低く、頻繁に実施を検討できると良いものになります。
①機能開発中
ブランチを切って、mainブランチにマージをするまでは常にリファクタリングチャンスです。Martin Fowlerも"Refactoring is a part of day-to-day programming(リファクタリングは日々のプログラミングの一部だ)"と言っています。
参考:
リファクタリングされた状態のコードでプルリクエストを作成することにより、レビューやテストのコストが削減できますし、保守性が高い状態で今後の開発を実施できます。
実施のポイント
実施するべきタイミングは、新しいコードを書きながら修正案が思いついた時、関数を定義してユニットテストを書いた後、プルリクエストを作成して見直している時…など、いつでもです。常にリファクタリングできる余地はないか検討しましょう。
ただし、これは日々の開発の中で自然に実施しやすいので、「リファクタリングの時間を確保できない」と話題になるのは、次のパターン以降かと思います。
②機能拡張をする"直前"
機能拡張する直前に、修正対象のコードをリファクタリングすることで、目前の開発の拡張容易性、テスト容易性を高めます。
メリットとしては、リファクタリングのコストに対して、定性的なリターンが明確ですぐに得られるため、必要性がわかりやすいという点です。③④に比べて、開発作業の一環とみなしやすいので、いちいち許可を取らなくても実施しやすいかと思います。(これが難しい現場もあもしれませんが…)
実施のポイント
リファクタリングの修正だけで先にテストし、マージする ようにしましょう。
これは、修正の差分を小さくしてレビューしやすくするため、そしてテスト範囲を限定するためです。
この事前のリファクタを「必要な手順」と考え、その前提で見積もり・計画するようにできると理想的です。
時間短縮のために、テストしないでいいですか?とはなりませんよね。リリースのために必要な手順だからです。リファクタリングも同じで、それが必要な手順だと強い気持ちで思えるかが大切です。そう思うためには、このパターンのリファクタリングの成功体験を積み、「リターンとコストが見合っている」と自分の中で納得しておくことが必要です。
③リファクタリングをバックログアイテムとして積む
リファクタリングや、少しスコープを広げて技術的負債解消等をバックログアイテムとして蓄積し、優先順位をつけて対応していきます。
必要コストを扱いやすくするために、規模の相対見積もりでざっくりと数値化するのがおすすめです。コスト数値化の表現はストーリーポイントのような形でもいいですし、Tシャツサイズ見積(S/M/L)でも良いでしょう。
参考:
実施のポイント
このパターンでの実施時には、プロダクトオーナーやプロダクトマネージャー(以下POと表記)と同意が必要になることが多いでしょう。この同意を取るパターンとして2つ考えられます。
- バックログアイテムごとに価値を説明して、都度POと同意を取る
- 一定期間ごとにどれくらいのコストをリファクタリングに時間を投資するかをPOと合意し、あとはエンジニア内で決定する
まずは小さく1から始めると良いでしょう。リターンとコストを意識して議論をする習慣づけにもなります。
そこからステップを進め「月当たりこれくらいのコスト(時間やストーリーポイント等)を投資します」とPOと同意できると、あとはエンジニア内で解決ができます。ここは大きなハードルがありますが、「リファクタリングしたら、それ以降の開発の速度や品質が上がった」という成功体験をPOと一緒に積み、「必要性はわかった、あとの時間の使い方は任せる」となれると理想的です。
リターンを非エンジニアにわかるように示して議論するのはコストがかかるので、パターン2までいけると全体としての効率は良くなりますし、リファクタリングが開発サイクルに組み込まれるので大きな進化だと言えます。
この詳細は、以前にスライドを作成したのでこちらをご覧ください。
④リファクタリングをプロジェクト化する
リファクタリングを独立したプロジェクトとして計画し、リソースを割り当てて集中的に取り組みます。大きくなると、リアーキテクチャと呼ばれるようなものにもなる場合もあります。
コードの複雑度が高まりすぎて、こまめな改善では追いつかない、と言った場合にこの手段が必要になることがあります。影響範囲が大きい分、大きなコストで大きなリターンが得られることがありますが、その分難易度も上がり、失敗リスクも高まります。
実施のポイント
「リターンとコストを見合うかを検討」は同じなのですが、規模が大きくなる場合は ビッグバンリリースにしないようにする (=最後に1回だけの大きなリリースにしないようにする)ことが非常に重要になります。
リファクタリングの修正範囲や期間が大きくなると、その分影響するコードを修正できなくなり、その範囲の開発が止まってしまいます。また、修正が大きくなるほど、バグを混入するリスクは高まります。
さらに、期間が伸びるほど遅延リスクも増え、期間に間に合わせようとまた保守性の低いコードを書くという悪循環も生まれてしまいます。
そのため、とにかく小さく修正して、小さくマージ、リリースできないかを検討します。 どうしても難しいこともあると思いますが、それでも知恵を絞ります。ストラングラーフィグパターン、というのものもあるので参考にしてみてください。
実施タイミング4つパターンのまとめ
これら4つのリファクタリングタイミングは、互いに排他的ではありません。プロジェクトの状況に応じて、適切なアプローチを選択し、組み合わせることが重要です。
ですが、「リファクタリングは日々のプログラミングの一部」となるように、まずはコストの小さい①、②からチャンスを伺っていきましょう。
日々の積み重ねで保守性は維持されるとともに、リファクタリングの成功体験を組織として積むことで、より大きなリファクタリングにも着手できるようになっていきます。
まとめ
ポイントをおさらいします。
- 「リファクタリングとは、生産性と品質改善のための投資」だと認識する。
- 常にリターンとコストを見極めて判断する癖をつける。
- 4つのリファクタリングタイミングを使い分ける。
- 常にリファクタリングチャンスを探し、日々改善を積み上げる。
- 組織として成功体験を積み、大きくしていく
以上を踏まえて、より良い開発を進めていきましょう!
Discussion