PR

リファクタリングとは?動作を変えずにコードを改善する考え方

システム
スポンサーリンク

はじめに

既存コードへ機能を追加し続けると、小さな修正でも影響範囲が分からず、変更するたびに広い確認が必要になることがあります。このような状態を、機能を増やさずに改善する活動がリファクタリングです。

リファクタリングでは、利用者や呼び出し側から見える動作を変えずに、コードの内部構造を整理します。この記事では、リファクタリングの意味、機能追加やバグ修正との違い、必要になるサイン、安全な進め方を解説します。

この記事で分かること

  • リファクタリングとは何か
  • 機能追加やバグ修正との違い
  • リファクタリングが必要になるサイン
  • 小さく安全に進める手順

リファクタリングとは

リファクタリングとは、外部から見える動作を維持したまま、既存コードの内部構造を改善することです。同じ入力に対して同じ結果を返し、公開しているインターフェースやデータ更新などの約束を保ちながら、コードの役割や依存関係を整理します。

目的は、コードを短くすることや、見た目を整えることではありません。変更箇所を見つけやすくし、修正の影響を必要な範囲へ限定し、次の変更を安全に行える状態へ近づけることです。変更しやすい設計を重視する理由は、良い設計より「変更できる設計」が重要な理由で説明しています。

また、リファクタリングはコード全体を一度に作り直す作業ではありません。確認できる小さな単位で変更し、そのたびに動作が変わっていないことを確かめながら進めます。

リファクタリングと似た作業の違い

同じコード変更でも、作業の目的と変更する対象は異なります。

作業主な目的変更するもの
リファクタリング内部構造を改善する外部から見える動作は変えない
機能追加新しい要求を実現する利用者が使える機能や結果を増やす
バグ修正誤った動作を正す外部から見える動作を正しいものへ変える
性能改善速度や資源利用を改善する機能結果を保ちながら性能特性を変える
全面的な書き直し実装を大きく置き換える広い範囲を変更し、全体を再確認する

実務では、機能追加の前に必要な範囲だけリファクタリングすることがあります。ただし、内部構造を整える変更と、外部動作を変える変更を同時に行うと、不具合の原因を特定しにくくなります。作業単位やコミットを分けることが重要です。

リファクタリングが必要になるサイン

一つの変更で多くの箇所を修正する

同じ業務ルールや計算条件が複数箇所へ散らばっていると、一つの仕様変更でも修正箇所が増え、漏れが起きやすくなります。ただし、似たコードをすべてまとめるのではなく、同じ知識や変更理由を持つかを確認します。共通化の判断基準は、共通化とは?「とりあえず共通化」が設計を壊す理由を解説で整理しています。

クラスやメソッドの役割を説明しにくい

一つのメソッドに計算、保存、通知などが混在していたり、dataprocessmanagerのような曖昧な名前が増えていたりする場合は、責務の境界が崩れている可能性があります。名前で役割を表現する考え方は、命名が設計品質を左右する理由で解説しています。

小さな修正でも影響範囲を予測できない

一部を変更するだけで多くの機能を確認しなければならない場合は、責務や依存関係が整理されていない可能性があります。SOLID原則は、このような問題を変更理由や依存方向から確認する判断材料です。詳しくは、SOLID原則とは?5つの原則と設計での使い方をわかりやすく解説を参照してください。

リファクタリングを安全に進める手順

  1. 現在の動作を確認する:自動テストを実行し、テストがない場合は代表的な入力、結果、データ更新などを記録します。既知の不具合を直す場合は、リファクタリングとは別の変更として扱います。
  2. 改善する問題を一つに絞る:名前が曖昧、メソッドが複数の仕事をしている、同じ知識が散らばっているなど、今回改善する問題を明確にします。
  3. 一つの小さな変更を行う:名前を変更する、処理の一部をメソッドへ抽出する、条件式を分かりやすくするなど、結果を確認できる単位で変更します。
  4. 動作を再確認して記録する:同じテストや確認を行い、外部から見える動作が変わっていないことを確かめます。問題がなければ、小さな単位でコミットや変更記録を残します。

改善したい場所が多くても、一度にまとめて変更する必要はありません。小さな変更と確認を繰り返すことで、問題が起きた箇所を特定しやすくなります。

注文金額計算をリファクタリングする例

次のメソッドは、商品の小計を計算し、会員かつ小計が1万円以上であれば10%割引を適用します。計算結果は正しくても、変数名の意味が分かりにくく、小計計算と割引判定が一つの処理へ並んでいます。

int calculateOrderTotal(Order order) {
    int x = order.getPrice() * order.getQuantity();

    if (order.isMember() && x >= 10000) {
        x = x - (x / 10);
    }

    return x;
}

変数名を役割が分かる名前へ変更し、小計計算と会員割引を別のメソッドへ抽出します。

int calculateOrderTotal(Order order) {
    int subtotal = calculateSubtotal(order);
    return applyMemberDiscount(subtotal, order.isMember());
}

int calculateSubtotal(Order order) {
    return order.getPrice() * order.getQuantity();
}

int applyMemberDiscount(int subtotal, boolean member) {
    if (member && subtotal >= 10000) {
        return subtotal - (subtotal / 10);
    }

    return subtotal;
}

例えば、価格5,000円、数量2、会員という入力では、変更前も変更後も結果は9,000円です。一方で、変更後は「小計を計算する」「会員割引を適用する」という二つの役割をコードから読み取れるようになりました。

この例で行ったのは、名前を変更するRenameと、処理をメソッドへ抽出するExtract Methodです。メソッドを分けること自体を目的にせず、分割後の名前が役割を説明でき、異なる変更理由を分離できたかで判断します。

リファクタリングで避けるべき進め方

  • 機能追加と同時に進める:構造変更と動作変更が混在し、問題の原因を判断しにくくなります。
  • 一度に広い範囲を変更する:変更前後を比較しにくくなり、問題が起きても戻す範囲が大きくなります。
  • 確認手段がないまま変更する:変更後に動作を維持できたか判断できません。先にテストや確認手順を用意します。
  • 抽象化やパターンの導入を目的にする:クラスやインターフェースを増やしても、変更の影響が小さくならなければ改善とはいえません。パターンは問題に合う場合だけ解決候補として使います。詳しくは、デザインパターンとは?GoFの3分類と設計での使い方をわかりやすく解説で説明しています。
  • 古いという理由だけで書き直す:実際の変更で困っていないコードまで広く触ると、改善効果より確認コストや不具合の危険が大きくなることがあります。

まとめ

リファクタリングとは、外部から見える動作を変えずに、既存コードの内部構造を改善することです。機能追加やバグ修正とは目的が異なり、次の変更を安全に行える状態へ整える役割を持ちます。

安全に進めるには、現在の動作を確認し、改善する問題を一つに絞り、小さな変更と確認を繰り返します。名前を変えることやメソッドを分けること自体ではなく、変更箇所を見つけやすくし、影響範囲を必要な範囲へ限定できたかで判断することが重要です。

関連記事


コメント

タイトルとURLをコピーしました