前書き
Java、PHP、Ruby、Pythonといったオブジェクト指向言語を想定しています。
実はstaticは半分釣りです。
この記事は、神クラスへの対処、関数の分割やレイヤー分割に関して書いてます。
こんなはずじゃなかったのに
こんなことってありませんか?
- MVCに沿ってきれいなコードが書けていると思っていたら、いつの間にかとんでもなく膨れ上がってる。
- コードを少し追加するだけなのに、たくさんの調査とテストをしなきゃいけない。
- リファクタリングしたいけど、どこから手を付けていいのかわからない。
こんなときは、まずテストコードを追加することから考えましょう。
なぜテストコードを追加するのか
なぜ神クラスをリファクタリングできないのか。
それは以下の理由につきます。
- 変更の内容が簡単に確認できない。大量の手動テストが必要。
- 大量の変数や関数間の関係が複雑すぎて、どう修正したらいいかわからない。
テストコードを追加すると以下のように改善します。
- リファクタリング時に結果を簡単に確認できる
- 一つの関数の引数・戻り値が、自分が理解できる範囲に収まる -> 変数や関数間の関係がわかりやすくなる
テストコードは追加するだけなら変更コストゼロ
テストコードを実装するのはコストがかかるんじゃないか?
そう思う方も結構いると思います。
でも神クラスのコードを少し変更する場合、大量の調査とテストが必要です。
それに比べると、試しにテストコードを追加するだけなら時間は全然かからないはずです。
なにより、テストコードを追加しても、既存のコードはそのまま動きます。
神クラス側がテストコードに依存してないんですから。
神クラスがちゃんと動くかどうかの調査とか必要ないですよね。
データをバリデーション・加工するロジックをテストしよう
どんなテストコードから始めればいいのか。そう思う方が多いと思います。
自分は以下のコードが簡単で始めやすいと思っています。
- ユーザー入力値のバリデーション
- データのフィルタリングや合計する等の加工・変換する処理
ユーザー入力値のバリデーション
ユーザー入力値のバリデーションは、以下のような構成になりがちです
- 関数の引数がユーザーの入力値
- 関数の戻り値はbool
テストコードを書いてみると、かなり簡単に書けます。
まず試しにやってみるなら、データのチェック・バリデーションがおすすめです。
データの加工・変換
データの加工・変換は、以下のようになります
- 関数の引数が、ユーザーの入力値、データベースから取得した値、インスタンス変数、グローバル変数
- 関数の戻り値が、データ加工後の値
こちらは、データのチェックやバリデーションより引数・戻り値が複雑で、難しいです。
ただ、後述の処理よりは書きやすいので、慣れたらやってみるといいでしょう。
難しいテストコードは後回しにしよう。
逆に、以下のようなコードはテストを書きづらいです。
- データベースからデータを取得する
- データベースにデータを保存する
- HTML等のView・GUIの表示
データベースやブラウザと言ったプログラミング言語外のものが関わるとテストコードが書きづらいです。データベースは、取得されるデータが変わったり、データの保存ができなかったりとトラブルがつきものです。View・GUIの表示も実際に目で見ないとわからない部分があります。
一応テストコードを書けないこともないのですが、簡単なところから始めましょう。
処理がごちゃごちゃしてる?関数で分けよう
神クラスでは、一つの関数にデータのチェック、データベースの読み書き、データの加工、GUIの作成が混ざっていることがよくあると思います。
この場合は、同じクラス内で機能ごとに別の関数に分割しましょう。
まず影響が一番少ないのが、ユーザー入力値のバリデーションなので、ここから始めましょう。
その際に、IDEの機能で関数分割できるか、コンパイルエラーは出ているのかを意識しながらやると分割しやすいです。
分割が終わったらテストコードを書いて結果を確認してみましょう。
テストコードが書きづらいと思ったら、もしかしたらその分割した関数が大きすぎるかもしれません。
もっと小さく分割してみましょう。
テストコードが書き終わったら、実行結果が変わらないか手動でもテストしてみましょう。
ここだけは根性になっちゃいます。悲しい。
その次に、データの加工・変換を分割するといいと思います。
同様にテストコードを書いて、書きづらかったらもっと細かく分割することを心がけましょう。
神クラスのインスタンス変数が作れない? static関数で分割しよう
悲しいことに、そもそもの問題として、テストコード内で関数を呼び出したいけど、神クラスのインスタンスの変数が作れなくて、関数を呼び出せない問題があります。
MVCのControllerクラスとか、そもそもクラスの作り方がわからなかったり。
別のクラスを作ってその中に関数を入れてもいいですが、今度はimport等で問題が出たりします。
その時は、static関数で分割しましょう。
static関数であれば、インスタンス変数を作らずとも実行することができます。
また、インスタンス変数を引数に入れなくてはならなくなります。
関数内で、インスタンス変数を使用している場合、テストコード内でこれらの値を変更するのは結構大変です。引数にすると簡単にテストできるようになります。
レイヤーを定義して、関数を移動する
処理ごとに関数を分けたら、その処理を入れるレイヤーをチーム内で決めて、レイヤー名でフォルダを切って、新規クラスを作成して関数を移動しましょう。
ユーザー入力のバリデーションであれば、Validatorレイヤーがいいでしょう。「なんたらValidator」クラスを作成して、そこに移動するといいです。
データの加工・変換は、ビジネスロジックに当たるものであれば、UseCaseレイヤーを定義しましょう。「なんたらUseCase」クラスを作成して移動しましょう。
汎用的なものは、Utilレイヤーでいいかもしれません。「なんたらUtil」クラスを定義しましょう。
(Utilクラスを作ることには賛否がありますが、自分は用途ごとに小さくUtilクラスを作るのはありだと考えています。)
UseCaseはCleanArchitectureやDDDでよく使われる言葉です。レイヤー分割の参考になるので、余裕があれば勉強してみるのもいいでしょう。
ただ、とりあえずはチーム全員で通じるレイヤー名であればいいと思います。
もちろん新規追加したクラスもテストコードを書くのを忘れずに
神クラスに残ったものも分割してみる。
さて、神クラスにはまだ処理が残されていますが残っているのは、データベースの読み書きやGUI系の処理だと思います。
こちらもレイヤー分割はできます。
データベースの読み書きは、Repositoryレイヤーに入れることが多いです。(JavaのSpring boot等)
GUIの部品は、自分はhelperレイヤーに入れることにしています。(Railsを参考にした。Railsでは、helperモジュールがある)
ただし、手動テストは必須です。
TDDで開発してみる。
ここまでやってみると、テストコードの書き方にも慣れてきたかと思います。
新規でコードを追加するときは、TDDを実践してもいいと思います。
TDDは、大雑把に言うと先に簡単なテストコードを書いて、あとから関数を実装する手法です。
こうすることにより、実装がシンプルになりやすいです。
詳しくは、ググってください。
まとめ
神クラスであっても、少しづつ分割していくと、テストコードによって修正が簡単なクラスに生まれ変わります。
上記の手法は、レガシーコード改善ガイドを自分流に実践してできたものです。
この本は他にもたくさんの神クラスへの対処方法が書かれています。
個人的には章題が好きで、目次を読むだけで面白いです。
(ぶっちゃけこんな記事読むより、この本買って欲しい)
この記事が、神クラスに立ち向かう一助に慣れば幸いです。