
モンスター この私のかわいいひと〜♪
モンスターメソッドとは
行数の多いメソッドのことです。
モンスターメソッドを修正したり、新機能を追加したりするには、時間がかかるようになります。
モンスターメソッドを小さいメソッドに切り分けることも、とても難しい作業で、これも時間がかかります。
ところが、リファクタの効果を実感できるのは開発者だけで、上層部は実感できません。また、費用対効果の説明が難しいので、上層部はリファクタのスケジュールをとりたがりません。モンスターメソッドのリファクタの機会はないと思ったほうがいいでしょう。
つまり、モンスターメソッドに追加修正するたびに、開発者は苦労して理解しなければなりません。
そうならないために、日頃からモンスターメソッドにしないように努力するしかありません。
「モンスターメソッド」という言葉を広めたのは、「レガシーコード改善ガイド 原著2004」と言われています。
第22章 モンスターメソッドを変更する必要がありますが、テストを書くことができません
レガシーコード改善ガイド 原著2004

17年前にモンスターメソッドとの戦い方が体系的にまとめられたのね

だけど、今でもモンスターメソッドを根絶できないんだね

コンパイラやインタープリタが100行以上のメソッドを文法エラーにしてほしいわ

もしそうなったら、1行に全部つめこんじゃおう?
(100行以上のメソッドに)
コードチェッカー phpmd
メソッド長すぎ!
メソッドの処理内容が多すぎるね。
ヘルパーメソッドに分割して、行数を小さくしよう。
我々は1行2行でもヘルパーメソッドに分ける。
実践テスト駆動開発 原著2009
そのドメインの言葉でメソッド名をつけることで、
一種のDSL(ドメイン固有言語)を作っているのだ
(メソッドではなく)クラスは最大50行まで
ThoughtWoksアンソロジー 原著2008
100行に及ぶメソッドを再利用できる機会はない
ThoughtWoksアンソロジー 原著2008
さまざまな文脈で利用できるのは、3行のメソッド
ThoughtWoksアンソロジー 原著2008
リーダブルコード 原著2011の10章「無関係の下位問題を抽出する」では、長いメソッドから汎用コードを分離する過程を説明しています。しかし、汎用コードになっていなくてもかまいません。メソッドの行数の上限を100行と決めて、機械的にメソッドを短く維持するだけでも、その価値があります。
また、10.8「やりすぎ」の節は無視してください。やりすぎるくらい、メソッドを小さくしていいと思います。
PhpStormのリファクタ機能
- メソッドに分けたい範囲を選択します。
- メニュー>Refactor>Refactor this...
- Extract Method...

- メソッド名を入力します。
- ローカル変数の値を変更していると、返り値で返すか、参照渡しか、を選びます。特に理由がなければ、返り値を選びます。
- Refactorボタンをクリックします。
選択した範囲が、新メソッドの呼び出しに置換されます。
ファイル末尾に新メソッドが追加されます。
モンスター化を防ぐために
モンスターメソッドにコードを追加するとき、2つの方法があります。
まず、新メソッドを作る方法
1つめの方法は、まず新メソッドを追加し、新メソッドに必要なコードを実装します。モンスターメソッドに新メソッドを呼び出すコードを書きます。
新しく追加するコードが、元のモンスターメソッドのコードと比較的独立しているときは、最初から新メソッドに実装します。
元のコード
public function monster()
{
...
...
}Code language: PHP (php)
新メソッドを作って、コード追加
public function monster()
{
...
...
}
ptotected function fooFeature($params)
{
// 追加コード
}
Code language: PHP (php)
元のメソッドに、新メソッド呼び出しを追加
public function monster()
{
...
// 元のメソッドには、新メソッドの呼び出しだけ
$this->fooFeature($params);
...
}
ptotected function fooFeature($params)
{
// 追加コード
}
Code language: PHP (php)
モンスターメソッドに実装してから、新メソッドに切り出す方法
2つめの方法は、モンスターメソッドに必要なコードを実装します。次に、追加した部分をIDE(PhpStorm)のリファクタ機能を使って、新メソッドに分けます。
新しく追加するコードが、元のモンスターメソッドのローカル変数と密接な関係があるときは、2つめの方法がいいでしょう。
元のコード
public function monster()
{
...
...
}Code language: PHP (php)
元のメソッドにコード追加
public function monster()
{
...
if (...) {
// 追加コード
}
...
}Code language: PHP (php)
リファクタ後のコード
public function monster()
{
...
// 元のメソッドには、新メソッドの呼び出しだけ
$this->fooFeature($params);
...
}
ptotected function fooFeature($params)
{
// 追加コード
}
Code language: PHP (php)

順番が違うけど、どう違うのかピンとこないよ

元のモンスターメソッドのローカル変数のうち、
$a、$bだけが必要だと思って、
新メソッド myFeature($a, $b)
を書き始めたとするでしょ。

うん、1つめの方法だね

途中で、$cが必要と気づいて、
myFeature($a, $b, $c)と追加。

途中で、$dが必要と気づいて、
myFeature($a, $b, $c, $d)と追加。

っていうことが何回もあると、少しうんざりするの

それよりは、元のモンスターメソッドに実装してから、PhpStormのリファクタ機能で、新メソッドに分けたほうが、簡単なこともあるのよ

2つめの方法だね。これで完成?

違うわ。1つめの方法も、2つめの方法も、まず動くコードを作ることが大事よ。動いたら、もっといいコードにリファクタできないか試してね。
スプラウト(発芽)メソッド
追加した新メソッドは、レガシーコード改善ガイドでは「スプラウト(発芽)メソッド」と呼んでいます。
元のファイルに新メソッドの座りがよくないと感じたら、新クラスにします。レガシーコード改善ガイドでは「スプラウト(発芽)クラス」と呼んでいます。
できるだけ、新メソッドや新クラスのユニットテストを書きます。
元のメソッドがそれほど長くなくても、コードを追加するときは、新メソッドや新クラスに実装するようにすると、モンスター化を防ぐことができます。
また、元のメソッドに1行しか追加しないので、他のブランチとコンフリクトしたとき、解決しやすくなります。
既存のモンスターメソッドを小さいメソッドに分ける
PhpStormのリファクタ機能は、かなり便利です。
ただし、完全ではありませんので、過信しすぎないようにしましょう。
とても長い行数の範囲を選択して、メソッドに切り分けると、動かないコードになっていることがあります。
理解している2〜5行の範囲を切り出す
メソッドに切り分けても大丈夫と自信のある短い範囲を選択して、新メソッドに切り分けます。
小さいものから手を付けて、テストがなくても自信を持って抽出できる小さなコードを探します。
私が言う「小さい」は、ほんの2、3行、せいぜい5行程度の簡単に名前が付けられるコードのかたまりです。
レガシーコード改善ガイド
このとき、結合カウント(引数の個数 + 返り値の個数)が小さくなるように切り分ける範囲を選ぶといい、とも言っています。
これを繰り返して、元のモンスターメソッドの行数を少しづつ短くします。
ローカル変数の個数が減り、メソッドの処理について理解しやすくなります。

