
モンスター この私のかわいいひと〜♪
モンスターメソッドとは
行数の多いメソッドのことです。
モンスターメソッドを修正したり、新機能を追加したりするには、時間がかかるようになります。
モンスターメソッドを小さいメソッドに切り分けることも、とても難しい作業で、これも時間がかかります。
ところが、リファクタの効果を実感できるのは開発者だけで、上層部は実感できません。また、費用対効果の説明が難しいので、上層部はリファクタのスケジュールをとりたがりません。モンスターメソッドのリファクタの機会はないと思ったほうがいいでしょう。
つまり、モンスターメソッドに追加修正するたびに、開発者は苦労して理解しなければなりません。
そうならないために、日頃からモンスターメソッドにしないように努力するしかありません。
「モンスターメソッド」という言葉を広めたのは、「レガシーコード改善ガイド 原著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行程度の簡単に名前が付けられるコードのかたまりです。
レガシーコード改善ガイド
このとき、結合カウント(引数の個数 + 返り値の個数)が小さくなるように切り分ける範囲を選ぶといい、とも言っています。
これを繰り返して、元のモンスターメソッドの行数を少しづつ短くします。
ローカル変数の個数が減り、メソッドの処理について理解しやすくなります。