Working Effectively with Legacy Code を読んで重要さうな点や気になったことなどをメモっていきます。(随時更新)
お断り: 英語の原著を読んでゐます。和訳版 (『レガシーコード改善ガイド』) とは訳語が異なることがあります。
The Mechanics of Change
1. Changing Software
変に弄って壊れたら嫌なので、必要最低限のコード変更しかしない → 設計の崩壊 → コードが理解できなくなってくる → 始めに戻る\(^o^)/
2. Working with Feedback
Edit and Pray vs. Cover and Modify
システムテストが無駄だとは言はないが、システムテストに数時間以上掛かるとか手動でやらないといけないとかだと、仮にバグを入れてしまっても直ぐに修正できないね。あと、システムテストではバグが「ある」ことは判るけど「どこがバグってるか」は判らない。また、網羅率を上げるために何を試験するコードを足せばいいのかも分かりにくい。
定義: テストハーネスとは、ソフトウェアの一部を試験するために書くコード (を実行するために必要なコード)
0.1 秒以内に実行できないユニットテストは、遅い。(数百・数千のユニットテストを実行するのにどれだけ掛かる?)
レガシーコードのジレンマ: コードを変更するにはテストが必要。テストを書くためにはコードの変更が必要。
レガシーコードの変更手順:
- どこを弄る必要があるのかを突き止める
- どこをテストする必要があるのかを突き止める
- 依存をなくす
- テストを書く
- コードを変更し、リファクタリングする
3. Sensing and Separation
依存をなくす理由:
- テスト対象が出力する結果を捉へる (sensing)
- テスト対象を、ソフトの他の部分がなくてもテストできる様にする (separation)
定義: フェイクオブジェクトとは、テスト対象が実行時に必要とするオブジェクトになりすますオブジェクト。 モックオブジェクトとは、内部でアサーションを行ふフェイクオブジェクト。
4. The Seam Model
定義: Seam とは、その部分のコードを直接編集すること無く、ある部分のコードの挙動を変更できるところ。 Enabling point とは、seam の挙動を選択・変更する箇所。
Seam の例:
- プリプロセッサマクロ (主に C/C++ のみ)
- リンク時の差し替へ
- Java の場合、例へばクラスパスを変更する
- 仮想関数
5. Tools
定義: リファクタリングとは、ソフトウェアを理解しまた既存の挙動を変へることなく修正することを容易にするための内部構造の変更。
自動リファクタリングツールは安全ですか? コードの挙動を変へる様なリファクタリングツールは使ってはいけない。同じリファクタリングを行っても、リファクタリング対象によって挙動が変はったり変はらなかったりする。(ツールが信頼できるなら、テスト無しでリファクタリングも可能だが……)
Changing Software
6. 時間が無いんだけどどうすりゃいいの
既存コードにテストがないにしても、せめてこれから追加・変更する部分についてはテストを書かう。
依存をなくす変更をしてテストを書いてから本来やりたかった変更に取り掛かるか。いきなり変更をやり始めるか。付けを今払ふのか、後で払ふのか。
Typically, changes cluster in systems. If you are changing it today, chances are, you'll have a change close by pretty soon.
新芽メソッド (Sprout method)
追加すべき新機能が新しいメソッドとして実装できるなら、(既存のメソッドの途中にコードを挿入するのではなく) 新しいメソッドとして実装すべし。もちろんその新メソッドにはテストを付ける。
新芽メソッドがインスタンスメソッドだと、そのクラスのインスタンスがないとテストが出来ないが、依存が多いとコンストラクタがそもそも呼べなかったりするかもしれない。その時は、新芽メソッドを静的メソッドにするといふ手もある。インスタンス変数を静的メソッドの引数として渡さないといけない場合もありうるが、同じ様な引数を複数の静的メソッドに渡す様になったらそれらの引数を別のクラスに抽出するきっかけになる。
pro: 書き換へない古いコードと追加する新しいコードが明確に分離される。
con: 既存のコードにテストが足されることはない。
新芽クラス (Sprout class)
新芽メソッドと似てゐるが、メソッドではなくクラスをテスト駆動開発で作成して追加する。新機能を必要とする既存のコードの箇所で、新クラスをインスタンス化して必要な機能を得る。
pro: 既存のコードをやたら変更する必要がない。
con: 謎の細かいクラスが増えて設計がわかりにくくなることがある。
クラスを追加することが設計上悪いことだとは一概には言へない。例へば複数の新芽クラスが同じ様な中身を持つようになれば、それらを共通化するきっかけになる。すなはち、短期的な設計の良し悪しはさておき、新芽クラスの存在は設計について熟慮を促すきっかけにはなる。
ラップメソッド (Wrap method)
新機能を新しいメソッドとして追加でき、かつその新機能を既存のあるメソッドが呼ばれた時に一緒に実行させたいとする。この時、既存メソッドと新メソッドの両方を呼び出すだけのメソッド (ラップメソッド) を追加し、既存メソッドを呼び出してゐたコードをラップメソッドを呼び出す様に書き換へる。
ラップメソッドは、既存メソッドを置き換へる形 (つまり、既存メソッドを別の名前に変更する) で入れる場合と、既存メソッドは変へずに新しいメソッドとして入れる場合とがある。
pro: 新芽クラスや新芽メソッドを入れるには既存の実装コードを変へる必要があるが、ラップメソッドを入れるには既存メソッドの中身を変へる必要はない。また、新機能は既存実装の中に入り込むのではなく独立したコードになる。
con: 追加・変名するメソッドの名前がダサくなることがある。
ラップクラス (Wrap class)
ラップメソッドを新しいクラスに置く。さらに新クラスを既存実装に対するデコレーターにすることもできる。
基本的な使ひどころは、
- 新機能の内容が既存クラスの責務と関係が薄い時 (一つのクラスに複数の責務を持たせるのは良くないよ!)
- 馬鹿でかいクラスが既にあって、それ以上でかくしたくない時
7. いつまで作業しても終はんないよぉ
コードの理解
コードに変更を加へるには、対象となるコードを理解しなければいけない。理解に要する時間はゼロには出来ないが、きちんと整備されてゐるほうが理解しやすい。
フィードバックまでの時間
コードを変更して、ビルドして、インストールして、動作を確認して、うまく動かなかったらまたコードを変更して……。ビルドやテストに何分も掛かる様な状態では、開発効率は落ちる。各ユニットテストが 5 秒や 10 秒でビルド・実行できるなら、開発速度を上げられる。
依存の解消
既存のクラスをユニットテスト際のよくある障壁は、依存。すなはち、クラスのインスタンスを作るのに必要なコンストラクタへの引数がテストハーネス内で用意できないといふ問題。また、依存するクラスを変更したらそれもビルドし直さないといけないのでビルド時間が長くなるといふ問題。
具象クラスではなく抽象クラスやインタフェースに依存する様にすべし。さうすれば、依存する抽象クラスやインタフェースが変はらない限り具象クラスの実装が変はっても依存先をビルドし直さなくて良い。
8. 機能を追加したい
試験駆動開発 (TDD)
(省略。みんな知ってるよね?)
差によるプログラミング
既存のクラスの特定のメソッドの挙動を変へるために、そのメソッドのみをオーバーライドするサブクラスを作ること。
でもこれ、メソッド A をオーバーライドするサブクラス X とメソッド B をオーバーライドするサブクラス Y の機能の両方を使ひたくなったとき死ぬよ? (少なくとも Java には多重継承とか無いし……)
→ とりあえず TDD で差によるプログラミングをやっといて、後からリファクタリングして設計を変へる。
リスコフの置換原則 (LSP)
(詳細省略。これもみんな知ってるよね?)
差によるプログラミングは、メソッドの挙動を変へるために別の具象メソッドをオーバーライドする。これはリスコフの置換原則を侵す原因になりやすい。親クラスのメソッドをオーバーライドする子クラスの存在に気付かずに、親クラスでの実装だけを見てゐるとそのメソッドの責務を取り違へてしまふ。
一般論として
- 具象メソッドをオーバーライドするのは避けよ
- 具象メソッドをオーバーライドするにしても、子クラスでの実装から親クラスの実装を呼ぶ様に出来ないか検討せよ
- 抽象メソッドを各子クラスがそれぞれオーバーライドする様に設計し直せ (normalized hierarchy)
(かういふ話をすると、Java の「final と明示しないメソッドは全てオーバーライド可能とする」仕様がいかに馬鹿げてゐるかわかるね…)
9. クラスをテストハーネスに入れ込めない!
コンストラクタの引数が多過ぎる
まづとりあへずインスタンスを作るだけのユニットテストを書いて様子を見る。コンパイラに引数が足りないと怒られるので、引数のインスタンスも作って渡すようにする。引数のインスタンスを作るのも難しい場合は? インタフェースの抽出をしてフェイクオブジェクトと取り替へる。あるいは、その引数がそのテストでは使はれないのなら、null を渡す。
製品コードで null を渡したり返したりする API を定義するのはあまり良くない。あくまでも null を渡すのはテストを書くため。製品コードでは null オブジェクトパターンなどを使った方が良い。
隠れた依存
コンストラクタが (引数を取るのではなく) 中で厄介な依存オブジェクトを生成してそいつが色んな副作用を起こす場合は、その依存オブジェクトを外から渡す様にするか、その箇所をメソッド化してサブクラスでオーバーライドすることによって別の依存オブジェクトに取り替へられる。
依存オブジェクトの生成が複雑な場合
依存オブジェクトを作るためにコンストラクタ内で色色なことが行はれてゐる場合は、その依存オブジェクト生成の流れ全体をコンストラクタ外に取り出すことは難い。Java や C# では、コンストラクタから抽象ファクトリメソッドを呼ぶことでそれを差し替へることができるかもしれない。別のやり方としては、コンストラクタを変へずにセッターメソッドを追加して、依存オブジェクトを後から差し替へるといふ方法もある。ただしこの方法では、元のオブジェクトを破棄する際に変な副作用が起きないか注意する必要がある。
グローバル変数への依存
同じシングルトンのインスタンスを弄るコードがたくさんの場所に散らばってゐる場合、それらを全て外からインスタンス差し替へられる様に書き換へるのは難しい。代はりに、getInstance 静的メソッドが返すインスタンスを差し替へられる様に、setInstance の様なメソッドを追加するとよい。
C++ での依存
テスト対象が依存してゐるものが再帰的にいろいろな他のものに依存してゐるばあい、ビルドに時間がかかったりそもそもテストハーネスをビルドできなかったりする。テストコードの中でテスト対象が依存するシンボルを定義することで、そのシンボルが本来依存してゐるものへの依存をなくすことができる。
(原文では include 依存と書いてあるが、自分にはリンク時の依存の問題であるやうに見える。)
玉葱引数
テスト対象のインスタンスを作るためにコンストラクタに渡す引数を作るために必要な引数を作るために必要な引数を作るために……。
- 使はれない引数は、null として渡す
- フェイクオブジェクトを渡す
エイリアスした引数
引数に親クラスやその親クラスがある場合。クラスごとに一対一でインタフェースを抽出するのは避けたい。更に子クラスを作ってメソッドをオーバーライドすることでフェイクオブジェクトとして振る舞ふ様にする。
10. メソッドをテストハーネスに入れ込めない!
隠れたメソッド
Private メソッドはテストハーネスから直接呼べない。Public メソッド経由でテストできればそれでよし。しかしそれが難しい様ならば、そのメソッドを public にすることを検討する。
そのメソッドがユーティリティ的なものであれば、public にすることは特に問題ないはず。(別のクラスに移動することを検討してもいいかもしれんが)
さうではなくて、そのメソッドを public にすることでクラスの内部状態を外から破壊されたりするなどの問題がある場合は、そのメソッドを新しいクラスに移動するリファクタリングを検討する。別解としては、メソッドを protected にしてサブクラスからそのメソッドを呼んでテストする。
リフレクションによって private メソッドを呼んでテストするのはおすすめできない。コードベースの汚さが表面上わかりにくくなり、後でリファクタリングをやらうとする意欲を殺ぐ。
ライブラリや言語の機能と癒着したメソッド
例えばテストしたいメソッドの引数が言語処理系の提供するクラス (java.lang.String とか) の場合。サブクラスを作ったり、新たなインタフェースを実装させたりすることは出来ない。ラッパーを作ることでフェイクオブジェクトを差し込む。
(著者は final や sealed なクラスといふものに否定的なやうだ)
11. どのメソッドをテストすればいい?
変更の影響範囲を知る
影響の依存関係を示す「影響図」を書く。
クラス内のオブジェクトへの参照がクラス外と共有されていたりすると辛い。(クラス外からクラス内の挙動に影響を与へることができるかもしれないから)
影響範囲を予測する
変更したい機能 (メソッド) を開始点として影響図を書く。影響範囲をテストする。
情報隠蔽 vs リファクタリング
テストを書くためにリファクタリングをする時、しばしば情報隠蔽を破壊するなどして影響範囲を増やすことになる。かといって、テストのための変更をやたらためらふのも良くない。最終的には、きちんとしたテストと設計によって十分な情報隠蔽も達成される。
12. 変更の数が多い時
影響範囲が複数のクラスにまたがる時、全部のクラスにユニットテストをまづ用意すべきなのか? 影響範囲全体を取り囲む結合テストを先に作った方が良いこともある。
傍受点 (interception point) とは、プログラムへの変更による動作の変化を捉へることが出来る箇所のこと。変更の影響範囲は全て傍受点となりうるが、テストでは最適な傍受点を選ぶべし。
一般的には、コードの変更箇所により近い傍受点が好ましい。
一方、影響が複数のクラスにまたがる変更・リファクタリングをする時は、全ての影響が集約される様な箇所を傍受点とするとよい。これを 絞り (pinch point) といふ。ただしあまりにたくさんの影響をまとめようとするとはまるので、その場合はもっと局所的に変更を捉へること。
絞りは隠されたカプセル化境界を見付け出す良い手掛かりになる。
絞りに頼りすぎて結合テストばかりにしないこと。最終的には、リファクタリングによって各クラスを独立してテストする様な方向に持ってゆく。
13. どんなテストを書けばいいの?
(テストを追加することによって) レガシーコード内のバグを見付け出すことよりも、レガシーコードを弄る時にバグを足さないことに注力した方がいい。
本来の仕様書を基にテストを追加するのはあまり良くない。レガシーなシステムでは「実装が仕様」であることがままある。必要なのは、「システムに変更を加へる際に従来の挙動を壊さないこと」を確認するテスト、すなはち 特性テスト (characterization test)。
特性テストは、変更を加へる前に、プログラムの実際の出力を直接アサーションにする形で作成する。
どれだけの特性テストを書けばいいのか? まづ対象の挙動が自分で理解できる様になる程度に書く。そして、加へようとしてゐる変更の影響が捉へられさうになるまでテストを増やす。
クラス全体を理解するためのヒント:
- ロジックが複雑に入り組んでゐるところをよく見る。観測変数 (sensing variable) を使っても良い
- どのような異常系があり得るか考へる。
- 入力の変化による影響を考へる。入力が極端な値の時どうなるか?
- クラスの状態の不変条件を見付ける。
14. ライブラリへの依存がどうにもならなくて死ぬ
ライブラリの API 自体をフェイクオブジェクトで置き換へることはできないので、せいぜいラッパーを作るくらゐしか……。
15. API を呼んでばかりゐるコード
ライブラリの API を呼んでばかりで、ライブラリに依存してゐないところがほとんど無いコードはどうすればいいのか。おそらく問題領域の分割が足りてゐない。API に近い低レベルの問題とそれに依存する高レベルの問題への分割を試みよ。問題領域の分割は、すなはちクラス・インタフェースの分割を意味する。
API をラップしてテスト可能にする方法もあり得る。
16. コードが理解できない!
- 関係性を理解するために簡単な図を書いてみる
- コードを印刷してマーキングしてみる
- リファクタリングを試みる (ただしバージョン管理システムにコミットしないこと)
- 不要なコードは消す
17. プログラムに構造と呼べるものがない……
たとへ初めはよく設計されたシステムでも、長年のメンテナンスの過程で構造が崩壊してゆく。アーキテクトといふ役割の人を用意して設計を監視・管理させるといふ方法もあるが、アーキテクトがコードに対して十分に関与できてゐないとアーキテクトの考へとコードの実体が乖離してゆく。
特定の人だけではなく、コードをいぢる全ての人が設計の全体像を理解すべきである。でないと、設計を知る人が知らない人を軌道修正させるコストを払ひ続けることになるから。
システム全体の設計を、もっとも大局的で重要な部分からだんだんブレイクタウンするやうに、ゆっくりと短い文で説明していってみよう。短い文で全体を説明するには、細かい部分を無視して本質的な設計の構造を明確にしなければならない。機能追加などで設計を変へる際も、設計の本質部分に逆らはないやうに考慮しよう。
裸 CRC: 白紙のカードをオブジェクトインスタンスに擬へて、机上にカードを並べて指さしたり動かしたりして設計を説明してみよう。
普段プログラマ同士で相談するときの用語と、コード内のクラス名等に使はれる用語とは一致してゐるだらうか?
プログラムを保守し続ける限り、設計は終はらない。
18. テストコードがむしろ邪魔
たくさんのソースファイルのうち、何がテスト対象で何がテストコードなのかごっちゃになる? テストコードやテストに使ふフェイクオブジェクトのコードなどについて、クラス名やファイル名の命名規則を決めよう。規則に慣れれば、ごっちゃになることはほとんど無くなる。
テスト対象のコードとテストコードは同じディレクトリに入れる方が良い。それぞれのテスト対象とテストコードは強く関連し合ってゐるので、離れたディレクトリにあると作業効率が落ちる。
19. オブジェクト指向ではないコードを安全に変更するには?
オブジェクト指向でない手続き型のコードでは、seam が少ないためにテストしにくい。絞りを見付けてリンク時にバイナリを差し替へるのがやりやすい。C ならマクロを使って差し替へることもできる。
バイナリを差し替へると言っても、テストごとに異なる振る舞ひをする様に毎回別のバイナリとリンクするとか、テストごとに異なる値を返す様にスタブ関数の動作を毎回変へるとかいったことをするのは骨が折れる。
仮想関数の代はりに関数ポインタを使って処理を差し替へる。ある関数が別の関数に結果を渡す様になってゐる場合、結果を戻り値として返す様な関数を抽出できないか検討する。仮想関数を通じたオブジェクト seam を適用可能にするために、言語を C から C++ に移行する。
20. このクラス、すごく大きいです……
プログラムに機能を追加する時、毎回「このメソッドにちょこっとコードを足せばいいや」「このクラスに 2, 3 個メソッドを加へればいいや」をやってゐると、どんどんメソッドやクラスが大きくなってゆく。
でかすぎるクラスの問題:
- どの部分をいぢるとどこに影響するのかわからない
- 皆が同じコードをいぢるのでマージが衝突しやすい
- テストしづらい (大きなクラスは情報を隠蔽しすぎる)
とりあへず、問題を悪化させないために、新芽クラスや新芽メソッドを使ふやうにする。
根本的には、単一責任則 (SRP) に基いてクラスをリファクタリングする必要がある。しかしでかいクラスをいきなり数個 (数十個?) のクラスに分割するのは難しくリスキーだ。以下のクラスの責任を見出す方法論によってコードを理解しつつ漸進的にコードを変更したい。
#1 メソッドのグループ化
関係の近そうなメソッド動詞をまとめる。メソッドの名前やアクセス権に注目する。
#2 隠しメソッドを見る
private/protected なメソッドが多いとしたら、そのクラスは情報を隠蔽しすぎてゐないだらうか。private/protected なメソッドを試験したいという欲求に駆られたら、そのメソッドは private/protected たるべからず。別なクラスに分割し public メソッドにしよう。
#3 過去の判断を振り返る
これまでの実装で、「これらの public メソッドはいづれもこのデータベースにアクセスするはず」のやうな、クラスの内部の様子に関する仮定を置いたりしてはゐないだらうか。もしさうなら、その「データベースにアクセスするコード」などをメソッドとして抽出してみよう。メソッドは増えてしまふが、メソッドをグループ化したりクラスを分割したりするのに良いヒントとなりうる。
#4 内部の関係性を見る
特定の private インスタンス変数が特定のメソッドからしか使はれないなどの関係性はなからうか。 機能スケッチ を書いて調べてみよう。機能スケッチを書くには、まづクラスのインスタンス変数を一つづつ紙に書いて丸で囲む。メソッドも同様に書いたら、メソッドからそのメソッドが使用してゐるインスタンス変数への矢印を書いてゆく。
機能スケッチの中に塊があれば、そこを新しいクラスとして抽出してみるといいかもしれない。ただし、その新しいクラスが意味のある「責任」を持ってゐるかも検討すること。
#5 クラスの最も中心的な責任を探す
SRP に従へばクラスの責任 (役割) は一文で書き表せるはずである。一つのクラスにたくさんの責任があるなら、重要でない責任は他のクラスに移さう。
#6 困ったら試しにリファクタリングしてみる
ただし結果はコミットしない。
#7 いまやらうとしてゐることに集中する
既存のでかいクラスが持つたくさんの責任に圧倒される前に、まづ自分が今加へようとしてゐる変更点がどういふ責任の元に存在するのか考へよ。
戦術
一つのクラスに色色な種類のメソッドがある「インタフェースレベルでの SRP 違反」と、色色な機能が一つのクラス内で実装されてゐる「実装レベルでの SRP 違反」とがある。後者は機能を別のクラスに移して、元のクラスは新しいクラスの機能を呼び出すだけにすれば改善される。さらに前者も改善するには、利用者がその別のクラスを直接操作できるようにリファクタリングをする必要がある。
まづ、リファクタリングしたい部分を取り囲む様にテストを作成し、リファクタリング中のミスに備へる。どうしてもテストを入れ込むことが難しい場合やリファクタリングツールが使へない場合は、慎重に、なるべく間違ひを犯しにくい方法でリファクタリングする。
リファクタリング時にありがちな間違ひは、クラスの継承に関するものが多い。あるメソッドが親クラスのメソッドをオーバーライドしてゐる場合、そのメソッドを別クラスに移動すると元のメソッドの挙動が変はってしまふ。親クラスと子クラスが同名のインスタンス変数を持ってゐる場合も、同様のことが起こり得る。
21. いつも同じ変更ばかりしてる……
レガシーコードにはコードの重複がありがちである。
コードの重複をなくすことのメリットは、各クラス・メソッドが固有の直交した責任を持つ様になること。共通部分を抽出する際に名前を与へることによって、隠されてゐた役割が見えてくる。
22. モンスターメソッドとの戦ひ
モンスターメソッドの種類:
- 箇条書きメソッド: だらだらとたくさんの命令が続くメソッド
- もつれたメソッド: ループや条件分岐が何重にもネストしてゐるメソッド
リファクタリングをするときは、(安全なリファクタリングをすると分かってゐる) ツールを出来るだけ使へ。手動リファクタリングは常にミスの危険性をはらんでゐる。自動と手動のリファクタリングを交ぜるな。
メソッド抽出のやり方
リファクタリングの間だけ観測変数を導入して、テストに援用する。特にコードフローが変はってゐないかをテストするのに役立つ。
カップリング数が少なくなるやうにメソッドを抽出する。カップリング数とは、メソッドへの入力変数 (引数) と出力変数の数の和。
巨大メソッドを、一個のクラスに変換する: ローカル変数を新クラスのインスタンス変数にして、テストで参照しやすくできる。
「ここらへんのコードってここよりもむしろあっちのクラスにあるべきなんぢゃないの?」と思っても、すぐにコードを別クラスに移動しない方がいい。全体のあるべき姿を見据ゑてから、別クラスに動かすべきコードを動かさう。
小さな部分から抽出する: いきなりでかいメソッドを二つに分割したりしようとか考へないこと。事故る可能性が高い。理解が可能な断片的な部分を抽出することから始めること。
23. リファクタリングしながらバグを入れてないか不安です
一つのことに集中する
メソッド A を変更してゐる → メソッド B も変へる必要があることに気付く → そのまま B を書き換へ始める → さらにメソッド C も修正する必要があることが分かる → メソッド C をいぢり始める → …… → カオス!!
脇道に逸れさうになっても、ひとまづ後回しにして、最初にやるべきだったことを終はらせること。常に「自分は今何をしてゐるか」を自問せよ。
シグネチャを保つ
メソッドを抽出したりするとき、できるだけ引数のリストをコピペしよう。
抽出したメソッドを呼ぶ時に数値の単位を変換したり一部の引数だけ引数オブジェクトに入れたりといった小細工といっしょにすると、ミスりやすい。
コンパイラに頼る
わざとコンパイル時エラーを引き起こしてどこを修正する必要があるか手早く調べることが出来る。ただし、コンパイラが常に期待したエラーをもれなく吐いてくれるとは限らないから注意。
ペアプログラミング
ミスをしてもすぐ気付いてもらへる。テスト無しでリファクタリングする時に特に有効。
依存解消テクニック
- Adapt parameter: 引数の型を A から A のラッパーに変へる。直接 extract interface しにくいときに。
- Break out method object: メソッドをクラスにする
- Definition Completion: テスト時に別のオブジェクトにリンクする
- Encapsulate global references: グローバル変数をクラスに入れ、他のリファクタリング手法の適用につなげる。
- Expose static method: private なインスタンスメソッドの一部を public static なメソッドに抽出する
- Extract and override call: メソッドの一部を protected メソッドに切り出し、それを seam とする。
- Extract and override factory method: ファクトリメソッドを抽出する。Java や C# のコンストラクタ内で直接内部オブジェクトを生成してゐる場合などに。
- Extract and override getter: インスタンス変数へのアクセスをゲッター経由にして、ゲッターを仮想関数にする。対象となるオブジェクトの生成を遅延評価することで、C++ ではコンストラクタから仮想関数が呼べない問題を回避する。
- Extract implementer: クラスを抽象インタフェースにし、実装を別クラスに抽出する。
- Extract interface: クラス A がクラス B に依存してゐる時、A が使用してゐる B のメソッドだけを新しいインタフェース I に抽出し、A は B ではなく I に依存する様にする。
- Introduce instance delegator: static メソッドを直接呼ぶのではなくて、インスタンスメソッド経由で呼ぶ様にする。
- Introduce static setter: static メソッドを追加しテスト時にだけ呼ぶことで、 static 変数等を別のものに差し替へる。
- Link substitution: 本番用とは異なるテスト用のバイナリにリンクする
- Parameterize constructor: コンストラクタの中で別のインスタンスを作る代はりに、外で作ったインスタンスを引数で受け取る新しいコンストラクタのオーバーロードを追加する。
- Parameterize method: メソッドの中でインスタンスを作る代はりに、外で作ったインスタンスを引数で受け取る新しいメソッドのオーバーロードを追加する。
- Primitive parameter: 巨大なクラスなどをインスタンス化することがとても難しいとき、計算に必要な内部情報をプリミティブ型で取り出して無理やりテストする。あまりおすすめできない。むしろ新芽クラスを作る方が良い。
- Pull up feature: クラス内の面倒な依存を断ち切るためにテストしたい部分だけを抽象親クラスに分離する。
- Push down dependency: クラス内の面倒な依存を断ち切るために、依存がある部分を子クラスに分離する。
- Replace function with function pointer: シンボルを関数から関数ポインタに変更することで呼び出し先を変更可能にし seam とする。
- Replace global reference with getter: グローバル変数 (静的記憶域変数) への直接の参照 (に等しい静的メソッド呼び出し) をインスタンスメソッド呼び出しに変更し seam とする。
- Subclass and override method: テスト用の子クラスでメソッドをオーバーライドすることでテスト時のコードの挙動を変へる。
- Supersede instance variable: テスト専用のセッターを作ることで、テスト時にインスタンス変数を別のオブジェクトに取り替へる。コンストラクタ内で取り替へることが難しい場合に使ふ。
- Template redefinition: テンプレート型引数によって依存クラスを注入する。
- Text redefinition: (Ruby などの動的結合言語で) テスト時にメソッドを再定義して動作を変へる。