matarillo.com

The best days are ahead of us.

31日間ReSharper一周 Day 25: パラメータの導入

2012-12-30 18:17:12

元記事

31日間ReSharper一周」の25日目にようこそ。

導入系リファクタリングは3つのうち2つ話した(「変数の導入」と「フィールドの導入」)。今日は3番目、「パラメータの導入」。

「型」コンボボックスのてっぺんのピクセルが欠けてたり、「定数の導入」チェックボックスが無かったりするのを無視すれば、かなり「変数の導入」とそっくりだ。動きもほとんど同じ。青いハイライトは邪魔だったり、そんな感じ。

代わり映えしないように聞こえるだろうけど言う。これは効く。びっくりするぐらい賢い。

以下はつまらない例だ。

_tilesWide = GetTilesWide(_previewer);
...
private int GetTilesWide(Control control)

    return (control.Width + TileWidth - 1) / TileWidth;
}

この関数に対するユニットテストを書きたいが、テスト内でControlのインスタンスを作りたくはないとする。単にcontrol.Widthをハイライトし、「パラメータの導入」ができた。

おかしなことに、最初に提案されるパラメータ名は、ありそうなwidthじゃなく、iだ(widthはコンボボックスをドロップダウンすれば使えるけど)。controlWidthを提案してくれないのはちょっとサムい。

変数名としてcontrolWidthを入力したとすれば、結果のコードは以下のようになる。

_tilesWide = GetTilesWide(_previewer, _previewer.Width);
...
private int GetTilesWide(Control control, int controlWidth)
{
    return (controlWidth + TileWidth - 1) / TileWidth;
}

そうすると、controlパラメータが灰色になる。もう使ってないからね。なので、カーソルをそこに動かしてAlt+Enterを押し、未使用のパラメータを削除できる。するとコードから恐ろしい依存性が消え、よりユニットテストがやりやすくなる。

_tilesWide = GetTilesWide(_previewer.Width);
...
private int GetTilesWide(int controlWidth)
{
    return (controlWidth + TileWidth - 1) / TileWidth;
}

(メソッドがpublicだったならパラメータは灰色で表示されなかっただろう。ReSharperは臆病だからpublicメソッドを見てくれないんだ。個々の警告を抑制する#pragmaがサポートされるようになればこれは直るかもしれない。その機能は次のリリースに含まれているそうだ。でもあきらめないように。ReSharperにはパラメータを削除する方法を知っている別の機能もある。)

パラメータを追加することがすごいわけではない。実際、局所変数を追加するのよりすごいことは何もない。すごいのは、呼び出し元に対して自動的に「正しいことを為す(Do The Right Thing)」ことだ。もし呼び出し元に、Conotrolインスタンスを返すようなあいまいで複雑な式を渡すようなものがあったとしても、結果は素晴らしい。あいまいで複雑な式の末尾には.Widthが追加されるだろう。

もっと複雑な式を抽出していてもうまくいく。他のパラメータだけでなくスタティックメソッドも使えるし、現在のクラスが持つインスタンスメソッドやプロパティであったとしても問題ない。ReSharperはちゃんと正しいことを為すだろう。

もう少し現実に近い例を挙げよう。コード中で実際にやるようなことに近いかもしれないし、近くないかもしれない。でもこの記事の残りの部分がフィクションだとしても、すごさが減るわけではない。

processor.ProcessFile(FileType.Foo);
...
public void ProcessFile(FileType fileType)
{
    string fileName = this.Configuration.GetFileName(fileType);
    ...
}

僕らはConfigurationプロパティを取り除こうとしていた。テストハーネス内で何かをインスタンス化するのは怖いし大変だからだ。だからまずはthis.Configurationをパラメータとして抽出するところから始めた。

processor.ProcessFile(FileType.Foo, processor.Configuration);
...
public void ProcessFile(FileType fileType, Configuration configuration)
{
    string fileName = configuration.GetFileName(fileType);
    ...
}

僕が本当にうなってしまったのは(君からすれば単純なことかもしれないが、僕は昔パーサーを書いてみたことがあるから、この手のことは評価するんだ)、呼び出し元に対してちゃんと正しいことを為したことだ。呼び出し元はprocessor.Configurationを渡している。追加したパラメータは、自動的に正しいオブジェクトの正しいインスタンスを使って渡されている。

いやー、すごい。


31日間ReSharper一周 インデックスへ戻る