どんな「コードの不吉な臭い」にどんな「リファクタリング」を用いるか

## 概要

「[リファクタリング 既存のコードを安全に改善する](https://amzn.asia/d/2z1VplT)」には「コードの不吉な臭い」と呼ばれる、リファクタリングをすべきタイミングの兆候が記されています。
巻末にはコードの不吉な臭いとリファクタリングの対照表も載っており、リファクタリング手法が参照しやすくなっています。

しかし、巻末のコードの不吉な臭いとリファクタリングの対照表では、どんなときにどんなリファクタリングを用いればよいかがひと目ではわかりません。
本文ではどんな「コードの不吉な臭い」にどんな「リファクタリング」を用いるかがきちんと書かれています。

ということで、この記事ではどんな「コードの不吉な臭い」にどんな「リファクタリング」を用いるかを分かりやすく対照表にしたいと思います。

## 「コードの不吉な臭い」と「リファクタリング」の対照表

### 不可思議な名前

変数や関数の名前からは何が起きているのか判断できず、あれこれ考えなければならないこと。

| ケース | リファクタリング |
|-------|-------------|
|  | ・関数宣言の変更<br>・変数名の変更<br>・フィールド名の変更 |

### 重複したコード

同じ構造のコードが2か所以上にあること。

| ケース | リファクタリング |
|-------|-------------|
| 同一クラス内の複数のメソッドに同じ式がある場合 | ・関数の抽出 |
| 似て入るものの完全に同じではない場合 | ・ステートメントのスライド |
| 共通のベースクラスは以下のサブクラス間に重複したコードがある場合　| ・メソッドの引き上げ |

### 長い関数

関数が長すぎること。

| ケース | リファクタリング |
|-------|-------------|
| 関数内で一つにまとめられそうな箇所がある場合 | ・関数の抽出 |
| パラメータや一時変数が多すぎる場合 | ・問い合わせによる一時変数の置き換え　|
| 長いパラメータリストの場合　| ・パラメータオブジェクトの導入<br>・オブジェクトそのものの受け渡し |
| どうしても一時変数やパラメータが残ってしまう場合 | ・コマンドによる関数の置き換え　|
| 条件文を抽出したい場合　| ・条件記述の分解 |
| 巨大なswitch文がある場合 | ・関数の抽出 |

### 長いパラメータリスト

パラメータが多すぎること。

| ケース | リファクタリング |
|-------|-------------|
| パラメータで渡されるオブジェクトに問い合わせることで、パラメータリストの他のデータを取得できる場合 | ・問い合わせによるパラメータの置き換え　|
| 既存のデータ構造から多くのデータを取り出している場合　| ・オブジェクトそのものの受け渡し |
| 複数のパラメータが常に一緒に渡される場合 | ・パラメータオブジェクトの導入 |
| パラメータが振る舞いを変えるためのフラグとして使われている場合 | ・フラグパラメータの削除　|
| 複数の関数群がパラメータで渡されている値を共有している場合　| ・関数群のクラスへの集約 |

### グローバルなデータ

コードベースのどこからでも変更できるデータのこと。

| ケース | リファクタリング |
|-------|-------------|
| プログラムのあらゆる箇所から変更されうるデータを見つけた場合 | ・変数のカプセル化 |

### 変更可能なデータ

無制限に更新可能なデータのこと。

| ケース | リファクタリング |
|-------|-------------|
|  | ・変数のカプセル化 |
| 一つの変数が別の事項を表すために使われている場合 | ・変数の分離 |
| 更新を行う箇所にそれ以外のロジックがある場合 | ・ステートメントのスライド<br>・関数の抽出 |
| APIで不必要に副作用のあるコードを呼んでいる場合 | ・問い合わせと更新の分離　|
| setterを呼んでいる側を特定したい場合　| ・setterの削除 |
| いつでも計算で導出できるのに変更可能になっている場合 | ・問い合わせによる導出変数の置き換え |
| スコープが広すぎる場合 | ・関数群のクラスへの集約<br>・関数群の変換への集約　|
| 内部構造にデータを含んでいる変数がある場合　| ・参照から値への変更 |

### 変更の偏り

一つのモジュールが異なる目的のために異なる方法で変更されること。

| ケース | リファクタリング |
|-------|-------------|
| ２つの処理が順番に現れるのが自然な場合　| ・フェーズの分離 |
| 呼び出し順序が前後する場合 | ・関数の移動 |
| 関数が２つの異なる処理を内部で一緒に行っている場合 | ・関数の抽出　|
| モジュールがクラスの場合　| ・クラスの抽出　|


### 変更の分散

変更を行う度にあちこちのモジュールがが少しずつ書き換わる状況のこと。

| ケース | リファクタリング |
|-------|-------------|
|  | ・関数の移動<br>・フィールドの移動　|
| 似たようなデータ構造を扱う一連の関数群がある場合　| ・関数群のクラスへの集約 |
| データ構造を変換したり、情報を付加したりする関数群がある場合 | ・関数群の変換への集約　|
| 不適切に分割されたロジックがある場合　| ・関数のインライン化<br>・クラスのインライン化 |

### 特性の横恋慕

あるモジュールの内部関数が、内部のモジュールよりも外部のモジュールの関数やデータ構造とやりとりをしていること。

| ケース | リファクタリング |
|-------|-------------|
| 関数が必要なデータから遠く離れている場合 | ・関数の移動 |
| 関数内の一部のロジックが横恋慕している場合 | ・関数の抽出<br>・関数の移動 |
| ある関数が複数のモジュールのデータを参照している場合 | ・関数の抽出 |

### データの群れ

オブジェクトではない数個のデータがまとまっていること。

| ケース | リファクタリング |
|-------|-------------|
|  | ・クラスの抽出<br>・パラメータオブジェクトの導入<br>・オブジェクトそのものの受け渡し |

### 基本データ型への執着

データがプリミティブ型で定義されていること。

| ケース | リファクタリング |
|-------|-------------|
|  | ・オブジェクトによるプリミティブの置き換え |
| データの振る舞いを変えるための単純なタイプコードの場合 | ・サブクラスによるタイプコードの置き換え<br>・ポリモーフィズムによる条件記述の置き換え |
| 基本データ型の同じ集まりが何度もコード中に現れている場合 | ・クラスの抽出<br>・パラメータオブジェクトの導入　|

### 重複したスイッチ文
switch/case文やネストしたif/else文の形で、コードの様々な箇所に同じ条件分岐ロジックが書かれていること。

| ケース | リファクタリング |
|-------|-------------|
|  | ・ポリモーフィズムによる条件記述の置き換え |

### ループ

| ケース | リファクタリング |
|-------|-------------|
|  | ・パイプラインによるループの置き換え |

### 怠け者の要素

本体に書かれた処理と同じ名前の関数があったり、メソッドが一つしかないクラスが存在すること。

| ケース | リファクタリング |
|-------|-------------|
|  | ・関数のインライン化<br>・クラスのインライン化 |
| 継承がある場合 | ・クラス階層の平坦化 |

### 疑わしき一般化

現在必要としていない機能を持っていること。

| ケース | リファクタリング |
|-------|-------------|
| 大した働きをしていない抽象クラスである場合 | ・クラスの平坦化 |
| 意味のない委譲を行っている場合 | ・関数のインライン化<br>・クラスのインライン化 |
| 未使用のパラメータを持つ関数の場合 | ・関数宣言の変更 |
| 将来に向けて用意したけれども決して通過しない分岐のための余分なパラメータがある場合 | ・関数宣言の変更 |
| あるクラスや関数がテストケースでのみ利用されている場合 | ・デッドコードの削除 |

### 一時的属性

値が特定の状況でしか設定されないインスタンス変数のこと。

| ケース | リファクタリング |
|-------|-------------|
|  | ・クラスの抽出<br>・関数の移動 |
| 変数が無効なときのための代替クラスがない場合 | ・特殊ケースの導入 |

### メッセージの連鎖

クライアントが受け取ったメッセージを別なオブジェクトに渡すことを繰り返すこと。

| ケース | リファクタリング |
|-------|-------------|
| 連鎖が起こっている場合 | ・委譲の隠蔽 |
| 連鎖中にあるオブジェクトの複数のクライアントが、同じメッセージを送りたい場合 | ・関数の抽出<br>・関数の移動 |

### 仲介人

クラスが過剰に委譲していること。

| ケース | リファクタリング |
|-------|-------------|
| メソッドの大半が別のオブジェクトに委譲している場合 | ・仲介人の除去 |
| 仲介人メソッドがわずかな場合 | ・関数のインライン化 |

### インサイダー取引

モジュール間でのデータのやり取りが活発なこと。

| ケース | リファクタリング |
|-------|-------------|
|  | ・関数の移動<br>・フィールドの移動 |
| モジュールが共通の興味を持っている場合 | ・委譲の隠蔽 |
| サブクラスがスーパークラスのことを知りすぎている場合 | ・委譲によるサブクラスの置き換え<br>・委譲によるスーパークラスの置き換え　|

### 巨大なクラス

インスタンス変数を多く持ち、一つのクラスが多くの仕事をしていること。

| ケース | リファクタリング |
|-------|-------------|
|  | ・クラスの抽出 |
| 新しくできたコンポーネントが継承でまとまりそうな場合 | ・スーパークラスの抽出<br>・サブクラスによるタイプコードの置き換え |
| クライアントがクラスの機能のサブセットを使っているだけの場合 | ・クラスの抽出<br>・スーパークラスの抽出<br>・サブクラスによるタイプコードの置き換え |

### クラスのインタフェース不一致

必要に応じた他のクラスへの置き換えが難しい状態のこと。

| ケース | リファクタリング |
|-------|-------------|
|  | ・関数宣言の変更 |
| 「関数宣言の変更」でうまく行かない場合 | ・関数の移動<br>・スーパークラスの抽出 |

### データクラス

属性とgetおよびsetメソッド以外何も持たないクラスのこと。

| ケース | リファクタリング |
|-------|-------------|
|  | ・レコードのカプセル化 |
| 変更されて都合の悪い属性の場合 | ・setterの削除 |
| データクラスに振る舞いを移せる場合 | ・関数の移動 |
| 関数全体が移せない場合 | ・関数の抽出 |
| 関数呼び出しの問い合わせ結果として使われるレコードがある場合 | ・フェーズの分離 |

### 相続拒否

サブクラスが親の属性と操作を必要としないこと。

| ケース | リファクタリング |
|-------|-------------|
| 継承したもののうちほんの一部しか利用していない場合 | ・メソッドの押し下げ<br>・フィールドの押し下げ |
| サブクラスがスーパークラスの振る舞いは継承するが、インタフェースは必要としない場合 | ・委譲によるサブクラスの置き換え<br>・委譲によるスーパークラスの置き換え |

### コメント

分かりにくいコードを補うためにコメントが非常に丁寧に書かれていること。

| ケース | リファクタリング |
|-------|-------------|
| コード内の処理の一部を説明するのにコメントが必要な場合 | ・関数の抽出 |
| 関数が既に細かく抽出されているが、コメントがないと処理が分かりにくい場合 | ・関数宣言の変更 |
| システムが特定の状態を必要としていることをルールによって明確に表現したい場合 | ・アサーションの導入 |

## まとめ

この記事では、どんな「コードの不吉な臭い」にどんな「リファクタリング」を用いるかをまとめました。
リファクタリングが必要なケースを把握しやすくなったので、今後のリファクタリング活動に役立てられればと思います。

間違いや改善点があればコメントいただけると幸いです。