🚀

今更聞けないリファクタリングのタイミングとやるべきこと

2024/12/22に公開

新たなプロジェクトにアサインした際に、プロダクトの品質向上が開発テーマとしてあったので、その一環としてリファクタリング施策に取り組むことになりました。

日常的に行うリファクタリングと施策としてのリファクタリングの違いなども感じたので、リファクタリングが必要なポイントを整理してみます。

リファクタリングを行わないとどうなるのか?

引用:リファクタリング-既存のコードを安全に改善する (第2版)

Martin Fowlerの『リファクタリング-既存のコードを安全に改善する (第2版)』にあるデザインスタミナ仮説にあるように、まずい設計を積み上げると開発が進むほど新たな開発に時間がかる悪循環に突入します。(書籍の内容はこの記事にはほぼ関係ありません)

実際、ルールが定まっていなかったり過剰な依存関係を含んでいるコードがあったりすると、参照するコードの場所が分かりにくくなる、テストが複雑になったりで、コードの改修や新たな機能開発が進めにくい状況になっていきます。

逆に、すぐれた設計であればソフトウェアが複雑化しても長期的に高い開発生産性で実装が進みます。初期段階はリファクタリングをしない方が実装自体は早く済みますが、先のことを考えるとリファクタリングしてコードを健康な状態に保つことは必要不可欠です。

リファクタリングしないことで発生する技術負債は、開発的な妥協の積み重ねです。都度リファクタリングを行ってソフトウェアやチームの規模が大きくなっても高い開発生産性を叩き出せる環境を用意していきましょう。

開発チームにリファクタリングが根付かないとこのような問題につながります。

大体、慢性的にリファクタリングができていないコードベースは、以下のような症状を出しています。
しょっちゅうバグが起きる
バグ調査に時間がかかる
開発の効率が致命的に悪化している
エンジニアの不満が続出
パフォーマンスが悪い=UXが悪い
誰も直せないバグがある=バグ修正に思いの他時間がかかる

https://qiita.com/tronicboy/items/24467c72de0bf6292cca

リファクタリングを実施する5つのタイミング

具体的にいつリファクタリングをするのかを整理します。基本的にコードを書くすべてのプロセスでより良い設計やより良いコードにできるのであれば改善していくことが好ましいはず。

とはいえ、時間の問題や技術的な課題もあったり、そもそも開発段階によってより良いコードのあり方が変わったりするので、気づいたときに直すという習慣をつけることが最も大事です。(気づいているのに放置してしまうと負債を他人に押し付けるという話になってしまうので)

    1. コードを書いている時
    1. コードレビュー時(レビュワー視点)
    1. コード改修時
    1. 保守運用として
    1. 設計変更時

それぞれのタイミングでやることについて確認していきます。

1. コードを書いている時

最初から完璧なコードを書ける人はいません。なのでコードを書き進めていく中で、コードをリファクタリングしてより良いコードに書き換える作業でより良いコードを書くようにします。

特にPRを出す前にはセルフレビューを行って、自分で直せる部分は事前に直した状態でレビュワーにレビューをリクエストするようにしましょう。自分で気づける改善はなるべく自分で行うべきです。

この段階でやること

  • 冗長なコードの削除
    最初に書いたコードは冗長であることが多いため、重複している部分を抽出し、共通化することでコードをシンプルにします。ただし、過剰な共通化には気をつけたいです。異なる進化を遂げそうな要素であれば、似ていたとしてもそれを維持した方が保守性が高くなります。

  • 関数やメソッドの短縮
    大きな関数やメソッドは複数の責任を担っていることが多いため、それぞれの責任を分割して小さな関数にすることで、可読性と再利用性が向上します。コードを書くことに集中していると一つのファイルが追っている責務が多くなりがちなので、コードが動くようになってそのままPRを出すのではなく責務分離などしてからPRに出しましょう。

  • 命名の見直し
    変数名や関数名が直感的でわかりやすいかを見直します。命名が不適切だと、後からコードを読んだ際に理解するのが困難になるため、命名規則に従いつつ分かりやすさ重視で命名します。短さよりも分かりやすさが大切。

  • 設計の見直し
    コーディング・ルールに沿っていることを確認します。慣れればルール通り書けるようになるので最初は時間をかけて馴染ませていきます。ルールも常に改善の余地はあるので、より良い形があれば改善も提案しましょう。

ルールはそれ自体を覚えるより思想を理解することに努めると定着が早いです。時間に追われて急いでいると自分のコードを見直すことがなおざりになってしまうことがあるので注意しましょう。

2. コードレビュー時(レビュワー視点)

コードレビューはコードを健全な状態に保つ上で非常に重要なプロセスです。コードレビュー時には、単にコードが動作するかどうかだけでなく、そのコードがどれだけ効率的で、可読性が高いか、再利用性があるかも評価する必要があり、リファクタリングする良い機会でもあります。

レビュイー側は、レビューを通じて他の開発者の視点からコードを見てもらい、問題点を発見し、改善することができます。改善余地のあるコードを改善することで、エンジニアとしての成長にもつながります。レビュワーが適切なレビューを行うと開発組織の技術力の底上げにつながるので丁寧なレビューができると良いでしょう。

この段階でやること

  • 理解しにくいコードの修正
    他の開発者が理解しづらいコードは、長期的に見るとチーム全体の生産性を低下させます。コードが何をしているのかを直感的に理解できるよう調整を依頼します。

  • パフォーマンスの最適化
    コードの中に非効率なアルゴリズムや無駄な処理が含まれている場合があります。これらを改善することで、パフォーマンスが大幅に向上することがあります。

  • 不必要な依存関係
    コードが他のモジュールやライブラリに依存しすぎている場合、それを分離することで、コードの独立性を高め、テストがしやすくなります。

  • コードのダブルチェック
    コーディング・ルールに則っていることやtypoがないか等、レビュワーとしてダブルチェックを行います。とはいえ、それでも見逃すことは十分あるので気づいたときに修正していく習慣をつけることが大切です。

最近では、CodeRabbitなどのAIでレビューを部分的に任せたりもしますが、基本的に人の行動改善であればAIと人の協働作業が成果を出しやすいのでAIに補完させてより良いコードや指針を提示するようにします。

3. コード改修時

既存のコードを改修するときは、ボーイスカウト原則に則ってコードを「編集する前よりもうつくしく」リファクタリングを意識することが大切です。この原則を守ることで、コードの品質を常に改善し続けることができます。

この段階でやること

  • 当時は気づけなかったコード修正
    早期リターンなど、スピード重視で実装していると漏れることがあるので、気づいた段階で修正をかけます。小規模なチームであれば、逐一チケット化せずに気づいた人が気づいたときに改修していく形で良いと思います。

  • コードの簡素化
    複雑すぎるロジックを簡潔にまとめること。複雑な処理を1つの関数に押し込まず、役割ごとに分割して関数化することで、コードが見やすく、変更しやすくなります。

  • 変数名や関数名の変更
    既存のコードでも、変数名や関数名が適切でない場合があります。これを変更することで、より理解しやすいコードになります。

  • 重複コードの削減
    似たようなコードが複数の場所に散らばっている場合、それを共通化することで、保守性が向上します。
    完璧なコードを完璧に書けるエンジニアは存在しません。既存コードの改善ポイントを見つけたら都度改善していくと良いです。

4. 保守運用として

ソフトウェアは時間の経過とともに、ライブラリのアップデートや新しい技術の導入、コーディング規約の変更などがさまざまな要因で変更が必要になることがあります。

こうした状況では、リファクタリングを行ってコードを最新の状態に保つことが不可欠です。

この段階でやること

  • 技術負債の解消
    時間が経つと、古くなったコードや非推奨な方法が残ってしまうことがあります。これを定期的に解消しないと、システム全体が非効率的になり、将来的に大きな問題を引き起こす可能性があります。

  • ライブラリのアップデート
    使用しているライブラリやフレームワークがアップデートされると、古いコードが動作しなくなったり、非推奨となったりすることがあります。このタイミングでメンテナンスおよびリファクタリングを行い、最新のライブラリに対応させます。

  • コーディング規約の変更
    チームでコーディング規約が変更された場合、それに従って既存のコードを改善する必要があります。規約が変更されるたびにコードを更新することで、コードの統一性を保つことができます。

5. 設計変更時

プロダクトが成長するにつれて、最初の設計が適切でなくなることがあります。新しい機能や要求に応じて、よりスケーラブルでメンテナンス性の高い設計に変更する必要がある場合があります。

こうしたリファクタリングを必要なタイミングで行うことで、プロダクトが柔軟に成長していく良いサイクルが生まれます。

この段階でやること

  • スケーラビリティの向上
    プロダクトが拡大するにつれて、システムが抱える負荷やトラフィックが増加することがあります。これに対応するために、リファクタリングによってコードをよりスケーラブルにすることが求められます。

  • メンテナンス性の向上
    システムが複雑になると、修正や変更を行う際に影響範囲が広がります。リファクタリングを行い、モジュール化や関心事の分離を行うことで、メンテナンス性を向上させることができます。

  • 新しい技術や設計の導入
    技術が進歩する中で、より効率的な方法や新しい技術や設計が登場します。新しい技術や設計を取り入れることで、コードの効率性や保守性を改善できます。

大規模なリファクタリングは粒度を小さく進める

大規模なリファクタリングになると、まとめて修正を行うことで逆に不具合を発生させるリスクが大きくなります。修正の際にシナリオテストを走らせるのも一つですが、リファクタリング自体は本来アプリケーションの挙動を変更するものではないためあまり大きな工数はかけたくありません。(課題感や開発規模などにもよるかと思います)

[大規模なリファクタリングの進め方]

  1. 何のリファクタリングを行うのかリスト化する
  2. 個別のチケットで対応可能か、段階的に対応すべきかといった基準で各項目を振り分ける
  3. リファクタリング部分のテストも書くことで品質を担保する

機能をまたがるリファクタリングを一気に進めてしまうと、不具合の検知が困難になってロールバックが必要になるなど追加の対応も必要になる場合があります。

ソフトウェア全体に影響のある変更は段階的に、ソフトウェアの部分的な改善となる変更は一気に行うと低リスクでリファクタリングを進めることができます。

リファクタリングするときのポイント

  • 優れたコードもリファクタリングが必要になる
    どんなに優れたコードでも、開発が進むにつれて状況や要件が変化するため、リファクタリングが必要になります。最初に完璧な設計をしても、全ての段階でそのまま維持できるわけではありません。継続的な改善を意識しましょう。

  • 自分のコードを疑う
    自分にとって分かりやすいコードが、他の開発者にとっても分かりやすいとは限りません。他の視点から見ても分かりやすいコードであるかを常に意識してください。コードレビューやチームメンバーとの共有を通じて客観的に評価するのも効果的です。

  • リファクタリング時にテストも用意する
    リファクタリングは既存の動作を変えないことが前提です。そのため、リファクタリング前後の動作が一致していることを確認するためのテストが不可欠です。単体テストや結合テストを活用し、安心してリファクタリングを進められる環境を整えましょう。

  • 本当にそのリファクタリングが必要かを考える
    リファクタリングには時間とコストがかかります。本当に必要かどうかを事前に見極め、優先順位をつけて進めましょう。例えば、リファクタリングがプロジェクトの進行を妨げる場合や、すぐに解決すべき課題が他にある場合は、実施を後回しにする判断も必要です。

  • パフォーマンスチューニングは後回しで良い
    パフォーマンスの悪化がプロジェクト全体に有意な影響を与えるケースは稀であり、ごくわずかなパフォーマンスチューニングのために可読性や保守性を落としたりするのは逆効果です。
    リファクタリングはまず可読性や保守性を高めることを目的とし、パフォーマンスチューニングはリファクタリング後に必要に応じて行うと良いです。

  • コーディングルールを整備しておく
    良いコードの定義は人によって異なります。チーム全体で品質を一定に保つために、コーディング・ルールをしっかり整備しましょう。コードの命名規則やフォーマット、使用するライブラリのルールを明確にすることで、リファクタリングの方向性がぶれにくくなります。

  • 時間がかかるようであれば、アノテーションコメントを残す
    リファクタリングには時間がかかる場合があります。その場合は、一時的にアノテーションコメントを残しておくと良いです。例えば、

// TODO: この関数を分割して可読性を向上させる

といったコメントを活用することで、今後のリファクタリング作業を計画的に進められます。

  • 情報の齟齬をなくす
    ドキュメントに記載されている情報とコードの記法に不一致があるなど、どの情報が正しいのか分からなくなることがあります。リファクタリングの際にはコーディングルールなどのドキュメントの整備も行い、ドキュメントに書かれていることがコードを書く際の指針となるようにしましょう。

適切なリファクタリングをするための準備

  • 開発方針を明確にする

  • 開発方針がチームで共有されていること

スピード重視でコードは動けばいいといった方針であれば、リファクタリングを後回しにするといった意思決定にもつながるので、開発チームで良いコードを書こうという共通認識を持っておきたいです。
リファクタリングを後回しにする場合は、技術負債が溜まっていく可能性があるといったメリデメを考慮した上での決定とすべきです。

  • コーディングルールをしっかり準備する

  • コーディングルールが整備されていること

  • 記載されているルールが最新であること

良いコードの定義は人それぞれ異なるため、プロジェクトにおける良いコードの定義をルール化してコードの品質を維持できるようコーディングルールは整備しておきたいです。

コーディングルールがあると一定の判断基準を基に良いコードを書く下地ができ、リファクタリングに関しても修正すべき部分とそうでない部分が明示化されたりします。

  • リファクタリングしたい内容を整理しておく

  • リファクタリングしたい項目が整理されていること

今すぐには解消できない改善案もあるので、そういったポイントはコメントに残したりリファクタリング候補のリストとしてどこかに整理しておくと開発チームとして改善すべき箇所を考慮した上で開発を進めることができ、手が空いたときに行う課題に困ることも少なくなります。


テンプレート置いてみました

私はこんな感じでスプレッドシートに整理していました。リスト化できればなんでも良いかなと思います。

  • テスト環境を整備する

  • テスト環境が準備できていること

  • CIでテストが通ることをチェックしていること

リファクタリングと自動テストはかなり密接に紐づいています。リファクタリング自体はアプリケーションの挙動に影響を与えないので、テストを書いて結果が等しいことを確認しながらリファクタリングを行うと安心できます。

ただ、すべてに対して細かくテストを書いてしまうとそれ自体が邪魔になることもあるので、確認が必要な分のテストを書くことに留めると良いです。

  • linterやフォーマッターを活用する

  • ESLintやフォーマッターの設定がされていること

  • 推奨環境を用意してスペルミスなどを未然に防ぐ体制にしてあること

  • CIでルールに則っていることをチェックしていること

linterやフォーマッターで自動補正できるものは最初から設定しておいて、コミット時にlintチェックを走らせるなどして無駄な確認の手間を減らしておきたいです。

タイポのチェックもCode Spell Checkerの拡張機能を活用しておけば事前に気づきやすい環境は構築できたりするので、開発環境自体をより良いコードを生みやすいものとしていきましょう。
※個々人の開発環境に過度に介入しないようにする

後回しせずリファクタリングしよう

リファクタリングは、ソフトウェア開発の中でコードの品質を高め、長期的にメンテナンスしやすくするために重要な作業です。後回しにするとだんだんと負債が増え、対応が面倒になって後回しにして...と対応が困難になっていきます。

小さなところから地道にしっかり積み上げていくことが大切だと思います。

他、リファクタリング自体は概念として定着しているので、定義や目的などこの記事のテーマ外のことはスクラップで整理しています。

https://zenn.dev/ik_takagishi/scraps/4df9e430273d27

Discussion