モンスターメソッドを防ぐために

モンスター この私のかわいいひと〜♪

モンスターメソッドとは

行数の多いメソッドのことです。

モンスターメソッドを修正したり、新機能を追加したりするには、時間がかかるようになります。

モンスターメソッドを小さいメソッドに切り分けることも、とても難しい作業で、これも時間がかかります。

ところが、リファクタの効果を実感できるのは開発者だけで、上層部は実感できません。また、費用対効果の説明が難しいので、上層部はリファクタのスケジュールをとりたがりません。モンスターメソッドのリファクタの機会はないと思ったほうがいいでしょう。

つまり、モンスターメソッドに追加修正するたびに、開発者は苦労して理解しなければなりません。

そうならないために、日頃からモンスターメソッドにしないように努力するしかありません。

「モンスターメソッド」という言葉を広めたのは、「レガシーコード改善ガイド 原著2004」と言われています。

第22章 モンスターメソッドを変更する必要がありますが、テストを書くことができません

レガシーコード改善ガイド 原著2004

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

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

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

もしそうなったら、1行に全部つめこんじゃおう😜

(100行以上のメソッドに)
メソッド長すぎ!
メソッドの処理内容が多すぎるね。
ヘルパーメソッドに分割して、行数を小さくしよう。

コードチェッカー phpmd

我々は1行2行でもヘルパーメソッドに分ける。
そのドメインの言葉でメソッド名をつけることで、
一種のDSL(ドメイン固有言語)を作っているのだ

実践テスト駆動開発 原著2009

(メソッドではなく)クラスは最大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行程度の簡単に名前が付けられるコードのかたまりです。

レガシーコード改善ガイド

このとき、結合カウント(引数の個数 + 返り値の個数)が小さくなるように切り分ける範囲を選ぶといい、とも言っています。

これを繰り返して、元のモンスターメソッドの行数を少しづつ短くします。

ローカル変数の個数が減り、メソッドの処理について理解しやすくなります。

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