平々毎々 (Hey hey, My my)

rock and roll can never die.

カテゴリーアーカイブ: resharper

Day 31: 安全な削除

やっと終わった……

元記事

31日間ReSharper一周」の31日目にようこそ(はいはい1日遅いですよ。昨日は休んだから)。

ネタにする最後のReSharperリファクタリングは「安全な削除」だ。Ctrl+Shift+Rメニューからでも、直接Alt+Delでも利用できる。

「安全な削除」は、また別のReSharperリファクタリングウィザードを表示する。最初のページ(大抵このページしか見ない)はかなりつまんない。まあそりゃ、何かを削除するのにそんなに何種類もやり方があるはず無いよね。

「安全な削除」の基本的な考えは、コードを壊さずに削除するということだ。次の2つのことをやってくれる。

  • 問題が起きるかチェックする。メソッドを削除しようとしたときに、そのメソッドがどこかでまだ使われているならReSharperが警告するので、削除をキャンセルできる。ハイパーリンクのどれかをクリックすればコードにジャンプできるし、問題を全部「結果検索」ウィンドウに表示することもできる。
  • 変更を完遂する。パラメータを削除する場合は全部の呼び出し元が更新されるだろう。仮想メソッドかインターフェースメソッドを削除する場合は、全子孫クラスからも削除するかどうか聞かれる。

「安全な削除」は、理論的には素晴らしい。コードが使われていないと思ったときに、実際使われていないことを確認し、それを削除し、全呼び出し元を更新するまでを1つの操作でできる。未使用パラメータについては手で確認する方法だってある。なぜならReSharperは、publicメソッドの未使用パラメータを確認したりしないから。ソリューションに含まれる全メソッドの全パラメータに対して「安全な削除」を手で動かすのは嫌だろうけど、何かが不適切ではないかと思った時にはいいクリーンアップツールだ。

ただ、この投稿のために調査しながら気づいたんだけど、ReSharperのチェックは完璧とは言いがたい。変更の完遂についてはかなりよくやってくれるようだが、最初に問題をチェックし通すことからはだいぶ離れている。だから……

注意の言葉

「安全な削除」の直前および直後には、いつもコンパイルすること。コードが壊れるかもしれないからだ。

1~2分探りまわって見つけた2つのケースは以下の通り。

  • メソッドがデリゲートとして使われていて、そのメソッドのパラメータを「安全な削除」する場合は、ReSharperは止めない。その後はもう、メソッドは同じデリゲート型には明らかに一致しないから、コードはコンパイルできなくなる(メソッド全体を削除しようとしたときは警告してくれるから、少なくとも部分的にはデリゲートを意識しているんだろう)。
  • もし別アセンブリのメソッド(たとえばSystem.Windows.Forms.Control.IsInputKeyとか)をオーバーライドしていて、メソッドからパラメータを「安全な削除」するのなら、ReSharperは先祖についての警告もなしにパラメータを削除するだろう。だからそのオーバーライドがコンパイルを通らなくなる。オーバーライドするメソッドと一致するシグネチャが無くなってしまうからだ。

いつでもコードが壊れると言いたいわけじゃない。それはありえない。注意してればなおさらだ(僕はイベントハンドラメソッドのパラメータを削除してみるほどバカじゃなかった。どうなるか知りたいとは思ったけども)。ほとんどの場合、ReSharperのチェックにひっかかるだろう。でもやっぱり前後にコンパイルしておいたほうがいい。

広告

Day 30: リネーム(伝播リネームを含む)

元記事

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

今日は全リファクタリングの母について。「リネーム」だ。F2で利用できる。もちろんCtrl+Shift+Rメニューから使うこともできる。

今となっては「リネーム」リファクタリングについて言わなきゃいけないことはそんなにない。まとめて言えば、機能したりしなかったりだ。すごさにも限度はあるけど、ReSharperではすてきな技をいくつかキメることができる。

注: 技術的な問題や、計画不足や、何回かの大吹雪なんかが相まって、現在自宅でReSharperを使うことができない。だから今日の(たぶん明日も)投稿にはスクリーンショットが無いし、詳細は記憶頼みだ。もし何か間違ってたら訂正してほしい。それらはいつか反映させるから。

局所変数のリネーム: 同期編集と提案

局所変数をリネームするとき、ReSharperでは同期編集(僕がこう呼んでるだけで公式名称ではない)によりエディタ上で変更できる。変数名の提案リストもポップアップするから、リストから名前を選ぶこともできる。

僕はDelphiに慣れているが、Delphiでは同期編集モードを終了して変更を確定させるのにEscを押さないといけなかった。ここからの転向はReSharperには準備不足だったね。というのは、ReSharperの同期編集でEscを押すと、変更が取り消されてしまうんだ。ReSharoerでは、変更の確定にはEnterを押さないといけない(そう、もしDelphiでも同じ動きをするんならとても納得のいくことだったんだろうけど)。

今はEnterを押して変更を保存するのは納得なんだけど、弱点もある。もし変数名リストが表示されていたら、Enterを押すとリストで選択中のアイテムが選ばれてしまう。だから例外オブジェクトに名前をつけようとして、たとえばexとするけど、exと入力してEnterを押しても変数名はexにリネームされない――exceptionになってしまうだろう。なぜならexで始まる変数名の提案の先頭だからだ。

短い変数名を入力するための回避策は次の通り。Enterを1度押せば変数名提案は消えるけど、同期編集はまだ終わらない(提案リストがもう消えていても、もう1度Enterを押す必要がある。押さないとリネームは取り消される)。だから、Escを押して提案リストを消し、それからEnterを押してリネームを確定させよう。

クラスのリネーム: 伝播リネーム

僕は長い間、伝播リネームのリファクタリングを待ち望んでいた――関係する全コードに影響するやつ。たとえば、SongFileという名前のクラスがあったとすれば、songFileとかfileという名前の変数や、SongFileという名前のプロパティやなにかがたくさんある可能性が高い。僕が望むリネームとは、SongFileからSongDataStoreに変えたら、それら変数名やプロパティ名のすべてに影響して、そっちもリネームしてくれるものだ。

ReSharperにはその機能がある。

さて、どちらにせよ普通は、クラスをリネームするときはウィザードが表示され、最初のページでは新しい名前を聞かれる。そこまでは何も目新しくない。しかしその後、次のような変数とプロパティを探す。(1)リネームする型に一致するもの、(2)変えようとする名前の一部分に似た名前をもつもの。そして2番目のページでそれらをリストアップし、それら全部に新しい名前を提案してくる。リネームしたくないものはチェックをはずすこともできるけど、そうする意味はあるか?

唯一の弱点は、子孫クラスには同じことをしてくれないことだ。でも僕は機能要求を起こしたから、いずれきっと……

Day 29: インターフェースがらみのリファクタリング

元記事

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

インターフェースを使うのは、昔からかなりいらいらの種だ。テスト可能なコードを書くときには必要で、ファイルシステムに見せかけたヌルオブジェクトを渡すことができる。でもコードが実際にやっていることを追いかけようとするときには辛い。

ReSharperには、インターフェースを使うのをずっと簡単にする機能がいくつかある。2つはもうネタにした。「先祖クラスへナビゲート」「型階層ビュー」だ。

今日はインターフェースを使うときに便利な機能をもう1つネタにする。「ベースメソッド」プロンプトだ。

メソッドのリファクタリング――たとえばリネーム――をするときに、そのメソッドがインターフェースを満たすためのものなら、ReSharperはそのリファクタリングをインターフェースの方に適用するかどうか聞いてくる。

このダイアログボックスはポンコツ的だ。思考の流れを中断させるし、質問は簡単に理解できるようなものでもない。僕の考えるようには動かない。残念だが、改善案はひとつも思い浮かばない。

そうは言っても、この機能が嫌いなわけではない。メソッドをリネームしようと思ったときに、ReSharperが自動的に割り込んできて、インターフェースもリネームするか聞いてくる。答えは勿論Yes!コードがコンパイル可能に保たれるからだ(時には先に「はい」を押してしまい、その後で「ああー、このメソッドはインターフェースから来てたんだ!」ってなる)。

同じことは「使用箇所の検索」でも当てはまる。インターフェースを実装するメソッドについて「使用箇所の検索」をするなら、「このメソッドを探しますか、それともインターフェースを経由して使う同じメソッドを探しますか?」と聞かれるだろう。この場合、このメソッドの使用箇所を全部知りたければ2回検索しないといけないかもしれない(インターフェース経由での使用箇所だけ知りたいのなら、「はい」と答えて1回検索するだけでいいけれども)。

Day 28: スタティックメソッドの抽出(本気)とプロパティの抽出

元記事

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

今日は、「メソッドの抽出」と一緒に使えるリファクタリングを(昨日話した「パラメータの導入」を除いて)もう2個ふれる。

スタティックメソッドの抽出(本気)

ReSharperではいつでもスタティックメソッドを抽出できるわけではない(たとえば、コードがインスタンスフィールドやメソッドを参照している場合など)。でも2ステップかければできる。まずメソッドを抽出し、それから「メソッドをスタティックにする」リファクタリングを使う。

このリファクタリングはあまり使ったことがないから詳細には触れない。これがあるって事を指摘したかっただけだ。

プロパティの抽出

ReSharperには「プロパティの抽出」リファクタリングがない。でも2ステップかければできる。まずメソッドを抽出し、それから「メソッドをプロパティに変更」を使う。

もし逆をやりたければ、「プロパティをメソッドに変更」のリファクタリングもある。

Day 27: メソッドの抽出

翻訳に時間をかけている間にメジャーアップデート。

でもそんなの関係ねぇ!

元記事

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

今日のネタは「メソッドの抽出」だ。おそらく史上2番目に役立つリファクタリングだ(もちろん「リネーム」が1番だ。2~3日以内に扱うつもり)。

  • 「メソッド名」は一目瞭然。
  • 式がインスタンスフィールドや、メソッドや、プロパティのどれかを使っているなら、「static宣言」は自動的にグレーアウトされる。どうしてそうなのかはいまいち分からない。メソッドを呼び出すときには意味があるけど、フィールドやプロパティは、新しいメソッドに単に渡すだけでも良かったはずだ。ReSharperが許してくれるなら。
  • 「戻り値」には何の目的もないようだ。これが利用可能になっているのを見たことがない。「戻り値なし」か、空欄になっているかだ(ReSharperは明らかに戻り値の型を知っているはずなのに――「シグネチャのプレビュー」ダイアログボックスでは正しく表示されるんだから)。これが単にバグなのか、それとも空のコンボボックスを置くというのが慎重に決められたデザインなのか、よく分からない。
  • 「パラメータ」。どのパラメータが必要なのかはReSharperが決めて、記入してくれる。一方、グリッドは「シグネチャの変更」ダイアログのものと似ている。フル機能には程遠い。ここでできるのはリネームと並べ替えだけだ。パラメータの型を変えたりといったことは何もできない。

ReSharperは、必要だと判断したパラメータの名前を提案してくれる。でもダイアログが開いたときの1度きりだ。他の提案リストをドロップダウンすることはできない。

パラメータのチェックをはずすと渡されなくなる。その代わり、ReSharperは局所変数を生成して、3つの点(…)で初期化する。だからコンパイラは必ず、何か他のものを記入しなさいと穏やかに諭してくる。

パラメータを追加して抽出

何度か、ReSharperの(Delphiのもそう。この件に関してはね)「メソッドの抽出」ダイアログにパラメータを追加するオプションがあったらなあと願ったことがある。でも今夜このことを考えていて、必要ない――少なくともReSharperでは――ことに気づいた。

テスト用のセットアップコードがあるとしよう。こんな感じだ。

_foo = new Foo(42);

そして、このコード行を新しいメソッドに変えたいとする。メソッド名はInitializeFoo()で、int引数をとる。望みのものは、「メソッドの抽出」ダイアログがこれから書き出すコードを表示し、それが42をハイライトして、「さあ、この式に別のパラメータを追加してくれ」と言ってくることだ。

って、どんだけー。まさにそれをやってくれる機能はもうあったんだよ!な、なんだってー!実際にはまずOKをクリックしてメソッドを抽出しないといけない。でもそれからコードエディタで42をハイライトさせて「パラメータの導入」をすることができる。そうすると呼び出し元が42を渡すように自動的に更新される。全部オッケー。

Day 26: シグネチャの変更

元記事

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

3つの関数が同じ引数を持つんだけど、それぞれ順序が違う、なんてことが今までにあった?もしあったなら、「シグネチャの変更」はぴったりの機能だ。

ReSharperの「シグネチャの変更」ダイアログは、メソッドのシグネチャに関するどんなものでも変えることができる。ReSharperの他の機能にもメソッドのシグネチャを特定の手段で変更するものはある。たとえば「パラメータの導入」とか。でもこれにはショートカットが無い。以下のことができる。

  • メソッドのリネーム。「メソッドをオーバーロードして委譲」を選んだら、前の名前のメソッドを挿入するが、それは単に新しいメソッドを呼び出すだけしかしない。もしかしたらまだ使うかもしれないから。
  • 戻り値の型を変更。
  • パラメータの追加。デフォルト値を指定する必要があり、その値がすべての呼び出し元から渡されるようになる。でも固定値を毎回渡したくないときだってあるだろう。その場合は次の章を見ること。
  • パラメータの削除。ReSharperはパラメータが使われていないかどうか確認しないから、コンパイルできないコードになってしまうかもしれまない。(パラメータを安全に削除する機能もある。他のとあわせてDay 31で扱おうと思って残してある。)
  • パラメータ名のリネーム。残念、ReSharperはここでは変数名の提案をしてくれない。Ctrl+Spaceを押したとしてもね。
  • パラメータの並べ替え。

どの場合でも、ReSharperは全部の呼び出し元をちゃんと更新してくれる。

新しいパラメータに非固定の値を

新しいパラメータを追加するとき、コードをコンパイル可能に保つために、ReSharperはすでにこのメソッドを呼んでいるすべての呼び出し元を修正する必要がある。つまり、何かを渡す必要があるということだ。これは必須フィールドであり、すべての呼び出し元から渡される何かを指定しないといけない。

じゃあ、すべての呼び出し元からまったく同じ値を渡すのが嫌なら、どうすればいい?それにはいくつか選択肢がある。

  • 「シグネチャの変更」を使わないこと。その代わりに手でパラメータを追加する。そうすればコンパイラが全ての呼び出し元に移動させてくれるだろう――たぶんね(他にもオーバーロードがあると分かって痛い目にあったことがある)。ごく単純な場合以外推奨はできない。だからこういうツールが必要になるんだけど。
  • とりあえずデフォルト値を使い、忘れずに戻って変更すること。これもお勧めはできない。人の記憶力ってそんなにいいもんじゃない。だからそもそもReSharperを使っているんだし。nullのようなデフォルト値は、それを渡すことが一番理にかなったときだけ使うべきだ。
  • どの呼び出し元にも存在しない、asdfjklみたいな意味の無い式を使うこと。ReSharperは止めたりしないけど、(当然)コンパイルできないコードになる。これが実際に役立つのは次のような点だ――コンパイラはすべての呼び出し元に確実に移動させてくれるので、そこで何を渡すべきなのか、ケースバイケースの判断をすることができる。
  • ほとんどの場所で機能すると思われる式を使うこと。もしFooオブジェクトを渡しているのならデフォルト値としてFooとだけ入力してもいい。ほとんどの呼び出し元にはFooプロパティを持ったクラスがいくつかあることを期待してね。僕らはこの手でうまくいったことも多かった。
  • 代わりに「パラメータの導入」を使うこと。もし「パラメータの導入」を使えるのなら、そっちの方が「シグネチャの変更」よりいい。「パラメータの導入」は正しいことを為してくれるからだ。ただし、新しいパラメータをメソッドが知っている形で表すことができるときしか機能しない。

Day 25: パラメータの導入

モチベーション低下中……

元記事

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を渡している。追加したパラメータは、自動的に正しいオブジェクトの正しいインスタンスを使って渡されている。

いやー、すごい。

Day 24: フィールドの導入

元記事

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

昨日はReSharperの「変数の導入」リファクタリングについて話した。今日は「フィールドの導入」だ。

フィールドの追加は変数の追加にかなり似ているが、オプションが追加されている。

ここでは通常の変数名提案(ReSharper Optionsで指定した命名規約に従うもの)が動作する。

「局所変数の導入」と同じく、これも「式を全て置換する」チェックボックスを持ってる。ただ「フィールドの導入」はクラス全体に影響するから、青いハイライトやカラーバーの青い縞は式がいくつ置換されるのかを見る上でもっと便利になった(まあそれらが消えないっていう事実には相変わらず腹立つけど)。

「キャンセル」が文字通りの「キャンセル」ではなく「すべてハイライトさせる」として使われてるというまぬけなふるまいは「フィールドの導入」にもある。本当の「キャンセル」をしたければ、「キャンセル」をクリックした後にEscを押さないといけない(そしてもし本当の「OK」をしたければ、「OK」をクリックした後にEscを押さないといけない。ぎゃぼー)。

基本設定については特に言うことはない。オプションは見るだけでよく分かる。

テストコードを書くには

もしテストコードを書くなら、「××内で初期化する」系のオプションはテストクラスでは1つも使わないほうがましだと気づくはずだ。「現在のメンバー」が便利な時もあるかもしれないが、期待するようなものではないことが普通だ。(私見だが、「コンストラクタ」や「フィールド宣言」は、[TestFixture]付きクラスを書いているときには使用可能にするべきですらないと思う。テスト間の依存性をむざむざ与えてしまうことになる。)

「[SetUp]メソッド内で初期化」を付け加えるように、ReSharperに機能要求を送ったんだが、プラグインとして実装できるからと言われ、「修正しません」としてクローズされてしまった。もしそれが正しいなら、ReSharperプラグインAPIのドキュメント化を続行するという長い道のりを歩き出しているってことになるぞ。だってリファクタリングのサンプルなんてひとつも無いわけだから。書く予定があるとはビタイチ見えない。リファクタリングについては1節だってない。そういうことだから、もし誰かが、既存のダイアログボックスにラジオボタンの選択肢を追加して、その新しい選択肢は既存のリファクタリングの動作を変更するために使う、そんなReSharperプラグインを書こうと思っているならば、最大級の幸運が今すぐ訪れますようにと祈るだけだ。

これで世界の終わりってわけじゃない。「フィールドの導入」はそれでも適切に使える。ReSharperのほとんどの部分ほど良くは無い。手でSetUpメソッドのところまでスクロールしたりとか、そういったことをしないといけない。(にっこり)

もしテストクラスで「フィールドの導入」を使いたいのなら、うまくいくようにする方法は次の通りだ。もしもプラグイン用ドキュメント作成が回避されるんでなければ、それまではね。

  1. 「フィールドの導入」を使い、「現在のメンバー」を選ぶ。
  2. ReSharperが今生成した割り当て命令をカットして、[SetUp]メソッドにペーストする。

実際はそんなに大変じゃない。ただいらいらするだけだ。全てがとても簡単になったのに慣れてしまった。だから、いつもやってることを取り上げられて、簡単にするべきなのにきっぱり断られたりすると、がっくりする。

Day 23: 変数の導入(と、Ctrl+Shift+Rでリファクタリングすることの紹介)

元記事

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

今日は、そして31日間の残りは、ReSharperのリファクタリングサポートについて話そう。これはVisual Studio 2005で取り入れられたいくつかのリファクタリングと比べてずっと改善されたものだ。

Ctrl+Shift+Rで「ここをリファクタリングする」

ReSharperには、リファクタリング用のコンテキストメニューがある。このメニューには、現在のカーソル位置で意味のあるリファクタリングか、または現在の選択範囲に基づいたリファクタリングだけが含まれる。Ctrl+Shift+Rで呼び出せる。

そこで表示されるものの例はこれだ。

ReSharperの「ここをリファクタリング」コンテキストメニュー。現在のコンテキストで可能なリファクタリングを表示する。

このメニューには他のリファクタリングコマンドも含まれていて、その中には独自に定義されたキーストロークを持っているものもある(それらはれっきとしたVisual Studioコマンドだから、キー割り当てを追加変更することができる)。よく使うリファクタリングがあるのなら、他のキーストロークをいくつか覚えていくかもしれない。でも僕としてはとっかかりのコストが安いのは本当に嬉しい。覚えなきゃいけないのはCtrl+Shift+Rだけだ。

変数の導入

「変数の導入」では新たな局所変数が作成され、選択した式で初期化される。そして選択した式は新しい変数を参照するように置き換えられる。

ReSharperの「変数の導入」リファクタリングダイアログ。Ctrl+Shift+Rでアクセス。

オプションの概要は次のとおり。

  • 変数の型。このコンボボックスが使用可能になっているのを見た覚えがない。いつも式が表す特定の型に固定されている。代わりに基底型を使いたければ、まず変数を導入して、それから型を変えないといけない。
  • 変数名。もちろん、ReSharperによる変数名の提案はここでも効く。ある1つの提案名が記入されていて、他のを見るにはコンボボックスをドロップダウンすればいい。
  • 式をすべて置換。導入するのは局所変数なので、現在のメソッド中の式が置き換わるだけだ。もし同じ式が1回以上あるのなら、たいていはこのオプションにチェックしたいと思うだろう。でもこれは、副作用とか別名変数とかによって、コードの振る舞いを変えることがある。だからこのオプションはデフォルトでチェックされていないんだろう。
  • 定数の導入。これは別のリファクタリングでもおかしくなかったが、「変数の導入」に統合されている。このチェックボックスは、式をコンパイル時に評価することができる場合だけ使用可能になる。

青色を取り除く

このダイアログを表示するとき、式がメソッド中に1回以上現れているなら、ReSharperは使用部分をすべて青でハイライトする。そしてカラーバーに青い目盛りも表示する。

「変数の導入」ダイアログをキャンセルした後でも式が青くハイライトされたままになるReSharperの「機能」。

「変数の導入」ダイアログをキャンセルしても、青いハイライトは消えないし、カラーバーの青い縞も消えない。これをバグ報告したが、いいえバグではありませんと言われた; これには目的があって、この式が現れている全ての場所を見ることができるようにです、だと。キャンセルボタンを使って式を探すなんてことは初めて聞いたよ。(おまいら、OKとキャンセル以外に別のボタンがあった方がいいとは思わないか?「使用状況の表示」ボタンとか?)

追記: 最初に書いたときには気づかなかったが、OKをクリックしても青いハイライトはとどまっている。だから何をやろうが関係なく、ダイアログを表示したなら、閉じた後に忘れずにEscを押す必要がある。おまけで余計な1ステップを、毎回毎回。ReSharperはものごとを簡単にし、一般的なタスクにかける時間を少なくするためのものじゃなかったのか?ブーブー。

わーわー言ったが、2ヶ月かかってやっと青色表示を消す簡単な方法を見つけた。Escを押すだけ。これはぜひ知っておくべきだ。でないとかなりしつこいからだ。Escを押さなければ、このハイライトはファイルを閉じるかコードを削除するかするまでずっと残っているぞ。

注: 明日の記事はまだあんまりできてないけど、今週末は外出してしまう。なので明日分の記事は日曜日に投稿されるだろう。

Day 22: Alt+Insでコード生成

元記事

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

さて、Alt+Enterはネタにし終わった。全てはコード修正に関するものだ。これで新規コードが生成されるときもある(例えば、クラスがインターフェースを実装するように変更したときには、Alt+Enterはメソッドの実装を生成しないといけない)けど、焦点はエラーや警告を修正することにある。

今回のネタはAlt+Insだ。これははっきりとコード生成に狙いを定めたものだ。

Alt+Insメニュー

Alt+Insを押すとメニューが現れ、コード生成コマンドを全て表示する。このメニューはいつも全てのコマンドを表示し、利用できないものはグレーアウトされる(利用可能なものだけ表示する他のReSharperメニューとは対照的だ)。

Alt+Insはクラス内にコードを生成するので、カーソルがクラス内にないときにAlt+Insを押したらすべてがグレーアウトされる。

Alt+Insウィザードの共通機能

Alt+Insコマンドはすべてウィザードを表示する。ウィザードのページには一般的に、生成されるコードで考慮したいもの(たとえばフィールド)を選ぶことができるリストが含まれているだろう。

ウィザードページには2種類のリストがある。チェックリストと普通のリストだ。

上の通り、チェックリストには各々のアイテムの隣にチェックボックスがある。アイテムを0個以上選ぶことができるときに(つまり、「選択しない」ことに意味がある場合に)使われるようだ。

普通のリストにはチェックボックスがない。選択するときはリストからアイテムを1つ以上選ぶ。これは標準的な複数選択リストボックスだから、Shift+クリックやCtrl+クリックで複数のアイテムを選択できる。

普通のリストは、少なくとも1つのアイテムを選ばなければならないときに使われるようだ。しかし実際には強制されない。唯一選択されているアイテムをCtrl+クリックして選択解除し、ウィザードの次の画面に進むと、ちょっと面白い結果になるだろう。

もしリストのすべてを選択したいなら、簡単にできる。チェックリストでは、Ctrl+Aを押した後にSpaceを押す。普通のリストでは、単にCtrl+Aを押すだけだ。

コンストラクタの生成

このコマンドでは、クラスのフィールドをいくつかまたは全て初期化するための、引数付きコンストラクタが生成される。どのフィールドを初期化したいかを選ぶことができるチェックリストが表示される。

注意すべき機能:

  • 垂直掘り下げ。ベースクラスのコンストラクタが引数をとる場合は、新しく生成されるコンストラクタは自動的に同じ引数をとり、それをベースクラスのコンストラクタに渡す。これらの引数はパラメータリストの最初に置かれる。(後で、お好みでパラメータを並び替える機能について話そう。)
  • 複数コンストラクタ。ベースクラスに複数のコンストラクタがあるときは、どのコンストラクタを呼び出すかを尋ねる第2のウィザードページがある。2つ以上選んだら、新しく2つ以上のコンストラクタが生成される。
  • アホ。コンストラクタがすでに存在しているかどうかのチェックはない。すでに存在するのと同じ引数を持つコンストラクタを生成するような指示をすれば、よろこんでやってくれる。その結果コードがコンパイルできなくなるので赤い波線が表示される。良いコードが生成されるか確認しないたった2つのAlt+Insコマンドのうちの片方がこれだ。

プロパティの生成

コマンドは3つある。「読み取り用プロパティ」、「書き込み用プロパティ」、「読み書き用プロパティ」だ。動作は一緒。どのフィールドにプロパティを生成するかを尋ね、その通りにする。

この機能は賢い。すでにいくつかのプロパティを作成していたなら、それらのフィールドはウィザードで選択できない。もしすべてのフィールドにプロパティを作成していたなら、メニュー内でプロパティ生成コマンドが全部使用不可になる。

インターフェースメンバーの実装

この機能は実はAlt+Enterを使った「メンバーの実装」と同じものだけれど、こっちはインターフェースメンバーだけが表示され、抽象メソッドは表示されない。

継承メンバーのオーバーライド

これも同じで、Alt+Enterの「メンバーの実装」とほぼ同じだけれど、(a)インターフェースメンバーは表示されないことと、(b)抽象メソッドだけでなく、(非抽象の)仮想メソッドも表示することが違う。

Equals()とGetHashCode()もリストに表示されるけど、それをオーバーライドするにはもっとすごい方法があるって事を覚えておいてほしい――Alt+Insメニューにある「EqualsとGetHashCode」を使おう。下で記す。

委譲用メンバーの生成

アダプタデコレータを書いているとき、もしくはデメテルの法則に従ったコードを書きたいってだけのときでも、この機能は本当に便利だ。こんなコードが生成される。

public int Length
{
    get { return _inner.Length; }
}
public void GetFoo(FooSpec spec)
{
    return _inner.GetFoo(spec);
}
public void DoSomething()
{
    _inner.DoSomething();
}

この例では、LengthプロパティとGetFoo()およびDoSomething()メソッドを_innerフィールドへ委譲している。「委譲用メンバーの生成」ウィザードでは2~3回クリックすればすむし、一度に複数のメンバーができるし、ヒューマンエラーの傾向は手で書くよりもずっと少なくなる。

どんなフィールドやプロパティに対しても委譲することができる(ので、例えば遅延初期化される何かに委譲してもいい)。まずフィールドやプロパティを選び、そして次のページで、委譲したいメンバーを選ぶ。

「コンストラクタの生成」と同じくこの機能もアホだ。同じメソッドシグネチャを1度以上委譲できてしまう。そうするとコンパイルできないコードになってしまう。

EqualsとGetHashCodeの生成

EqualsとGetHashCodeをオーバーライドしなきゃいけないことはそんなにないけど、もしやるなら、自動化の支援があれば大歓迎だと思う。

ウィザードはまず、Equals()メソッドでどのフィールドを比べたいのか聞いてくる。次に、GetHashCode()の計算にどのフィールドを使うのかを聞いてくる。賢いことに、等価性の決定に使われていないフィールドがあったなら、そのフィールドはハッシュコードの計算に使わないほうがいいことを分かっている(つまり、最初のページでチェックしなかったフィールドは、2ページ目で利用できない)。

もし選んだフィールドに参照型があれば、ウィザードに3ページ目が存在し、非ヌルであることが想定できるフィールドがあるか聞いてくる。もしあれば、GetHashCode()の実装を単純にできる。

下は結果として生成されるコードの例だ。_nameは非ヌルであるとしている。_addressは違う。

public override bool Equals(object obj)
{
    if (this == obj) return true;
    Foo foo = obj as Foo;
    if (foo == null) return false;
    if (_x != foo._x) return false;
    if (_y != foo._y) return false;
    if (!Equals(_name, foo._name)) return false;
    if (!Equals(_address, foo._address)) return false;
    return true;
}
public override int GetHashCode()
{
    int result = _x;
    result = 29*result + _y;
    result = 29*result + _name.GetHashCode();
    result = 29*result + (_address != null ? _address.GetHashCode() : 0);
    return result;
}