Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

長大で乱雑、テストも不十分なRailsコントローラーをリファクタリングする方法

Rails開発者としてのキャリアの中で、「もうプログラミングなんて辞めてしまいたい」と思わせられるコントローラーに、必ず一度は出会うことでしょう。1つの機能のコードがすべてそこに詰め込まれていたり、15個ものbefore_filterがインスタンス変数を使って暗黙のうちに連携し、決まった順序で呼び出さないと動かない——そんなコードです。そして当然のように、そのテストはこんな感じになっています。

test "index" do
  get :index
  assert_response :success
end

すごい。これでもうテストカバレッジ100%ですよね?

目をつぶって存在しないふりをするのが一番楽ですが、いつか必ずこのコントローラーのバグを修正しなければならない日が来ます。そして、優れたソフトウェア開発者であるなら、見つけたときよりも良い状態でコードを残したいと考えるはずです。

しかし、頼れるテストがない状態で、どうやってリファクタリングすればいいのでしょうか?

まずは何とかしてテストを用意する

安心してリファクタリングするには良いテストが必要です。しかし、このコードに対して良いテストを書けるようになるのは、リファクタリングした後だという矛盾があります。では、どうすればいいのでしょう?

実は、コントローラーがどれほどひどく書かれていても、データを送信してレスポンスを検証するだけの結合テスト(インテグレーションテスト)なら書けます。まずは、リファクタリング中にコントローラーの既存の振る舞いが変わらないことを保証するテストを書きましょう。

こうしたテストはユニットテストほど精密ではありません。それでも、これから行うリファクタリングで全体が壊れないという安心感を得られますし、テストを書く過程そのものがコードへの理解を深め、どうリファクタリングすべきかの判断材料にもなります。

依存関係を切り離す

悪いコントローラーコードを悪くしているものは何でしょうか? 多くの場合、それはbefore_filter同士、ヘルパーメソッド同士、あるいは200行にも及ぶメソッド内の各部分同士の暗黙的な依存関係です。早すぎた抽象化が原因の場合もあります。コードを改善するには、まずこれらの依存関係を断ち切る必要があります。

Railsのコントローラーには、依存関係を断ち切る簡単な方法があります。before_filter、ヘルパーメソッド、スーパークラスなど、コントローラー的なコードが潜んでいそうな場所からコードをすべてコピーし、元のメソッド呼び出しをそのコピーしたコードで置き換えるのです。

これは一時的にDRY原則を崩す行為ですが、後により分かりやすい形でリファクタリングするための布石です。 見た目は醜くなりますが、すべてのコードが表に出てくるため、どの部分がどこと相互作用しているのか、処理がどう流れているのかが一目瞭然になります。

(この作業中は、変更のたびにテストを実行し、インライン化によって何も壊れていないことを確認してください。)

テストしやすいコードへ向けてリファクタリングする

ここまで来れば、いよいよ本格的なリファクタリングの再構築に入れます。基本的にはメソッドの抽出オブジェクトの抽出メソッドの引き上げといった定番のリファクタリング手法を中心に進めることになるでしょう。いくつか異なるアプローチを試してみて、一番しっくりくる形を見つけてください。

最初の数回のパスでは、高レベルの結合テストをセーフティネットとして頼ることができます。しかし、すぐにもっと強力なものが欲しくなるはずです。

リファクタリングを進めながら、コードをテストしやすくするチャンスを探しましょう。 具体的には、テストダブルを注入できる場所を作ること、オブジェクト間の依存を減らすこと、コントローラーが「簡単に生成してユニットテストできるオブジェクト」に依存するようにすることを意味します。コードをテスト可能なオブジェクトへ移動させれば、コントローラーは小さくなり、理解しやすく、そしてコントローラー自体のテストも容易になります。

番号付きリストでまとめると?

改めて、巨大なコントローラーを分解するための手順を整理します。

  1. コントローラーに対する高レベルの結合テストを用意して実行できるようにする。
  2. テストを実行し、すべてパスすることを確認する。
  3. before_filter、スーパークラスのメソッドなど、コードを隠している抽象化をインライン化する。
  4. テストを実行し、まだパスすることを確認する。
  5. リファクタリングを1つ実施する(メソッドの抽出サービスオブジェクトの抽出インスタンス変数のローカル変数への置き換えなど)。コードが読みやすくなったか確認する。
  6. テストを実行し、まだパスすることを確認する。
  7. コードを抽出した場合は、抽出したオブジェクトやメソッドに対してユニットテストを書く。
  8. テストを実行し、まだパスすることを確認する。
  9. コントローラーにまだ改善の余地があるなら、ステップ5に戻る。

次の一歩

さらに学びたい方には、『レガシーコード改善ガイド(Working Effectively with Legacy Code)』がバイブルです。テストのない保守困難なコードを、扱える状態へと変革するための知恵が詰まっています。巨大なモノリシックなコントローラーと頻繁に向き合う方、そして私と同じようにリファクタリングを楽しめる方には、心からおすすめの一冊です。

さあ、あなたの恐怖体験も聞かせてください! これまで遭遇した最悪のコントローラーコードは、どんなものでしたか?

  1. Minitestの仕組みをソースコードで徹底解説!Rubyテストフレームワークの内部動作

    Minitestとは? MinitestはRubyのテストライブラリで、TDD(テスト駆動開発)スタイルでコードのテストを書くためのツールです。 Railsのデフォルトのテストフレームワークであり、Railsの生みの親であるDHH(David Heinemeier Hansson)のお気に入りでもあります。 主な競合であるRSpecと比較して、シンプルでコード量が少ない点を評価してMinitestを選ぶ開発者も多くいます。 下の画像をご覧ください: ただし、この記事の目的は「どちらを選ぶべきか」「どちらが優れているか」を語ることではありません。 この記事で扱うのは、Minitestが実際にど

  2. コントローラーをキーボードにマッピングする方法!SteamとAntiMicroで簡単設定

    ゲームコントローラーをPCに接続すると、自動的に認識されます。Windows 10ではボタンの割り当てもあらかじめ設定されているため、改めてキーをマッピングする必要はありません。しかし、一部のゲームではこの初期設定がうまく機能しないことがあります。そんなときは、コントローラーをキーボードに割り当てられるマッパーツールを活用しましょう。この記事では、コントローラーをキーボードにマッピングする方法を詳しく解説します。 コントローラーをキーボードにマッピングする方法 コントローラーの各ボタンの機能は、ゲーム内の設定画面で確認できます。多くのゲーマーはキーボード操作でのプレイを好みます。長時間のコン