リファクタリングしながらクラス設計を考える
2020/10/24 02:00
リファクタリングをしながらクラス設計を考える流れを動画でお送りしています。実際にコードを弄りながらオブジェクトの責務について考えたり、重複コードや今後の柔軟性を目指して修正を重ねていきます。後半ではこれをさらに推し進めてオブジェクトの接点を小さくし、テスト用のデータ構築やその呼び出し分けまで行いました。この過程でAdapterパターン、Compositeパターンなどのデザインパターンを適用しています。20分強、16分強の動画です。
リファクタリングとは
既に動いているコードについて、その動作を変えずにより良い設計にするのを「リファクタリング( Refactoring )」と言います。
このエントリではリファクタリングをしながら、他のコンテンツで言及している内容なども含めつつより良い形を目指していきます。どのような事を考えながら進めているのかという事が伝わると嬉しいです。
ステップ1
ここでは以下のような想定でやっています。今回のコードはサンプルのため全体のコードが短いので「ここまでしなくとも別に良い」という判断も十分にありうることは動画の前提でもお伝えしました。
課題
ScoreCalculatorの責務過多
- そもそも計算ロジックだけに注力したいのに「どこからどんなデータを読んでくるのか」という責務のコードが多く入り過ぎている。
- その為に計算ロジックが変更されない場合でも(読み込みデータの元が増えたりしただけでも)、クラスの変更が入ってしまう。
- 単一責任の原則のような原則には沿っていないと考えられる。
CsvReaderのコピペコード
- CsvReaderの仕様変更がある度に複数箇所のコードを同様に修正する必要が出てくるので共通化したい。
JsonReaderとCsvReaderの責務の類似性と変更可能性
- どちらも「計算の元データを読み込んでくる」という目的は同一
- 違いはフォーマットの違いや、呼び出し方のインターフェースのみ
- 責務が類似しているなら一緒のインターフェースにできるのでは?という匂いがする
- JSON、CSVがあるので、他のフォーマットにも従う必要が出てくるかもしれない
修正
- ScoreCalculatorはRecordReaderの種類が増えたり減ったりしてもコードを修正する必要が無くなり、計算ロジックが中心であるコードに近づいた
- RecordReaderを差し込むことでScoreCalculatorが完成して動作する形になった
- 各読み込み方法はRecordReaderのサブクラスにまとまった
- CsvReaderに修正を加えずに対応するため、Adapterパターンを適用した
トレードオフ
- 抽象度を上げて今後の変更に柔軟になった
- コードの具体的な動きが見えにくくなった
- 変更の影響範囲が適切になった(具体的な読み込み方法を直したいならRecordReaderの派生クラスを修正、計算方法を直したいならScoreCalculatorを修正)
ステップ2
- RecordReaderにCompositeパターンを適用して、RecordReaderの親子関係の構築をScoreCalculatorから完全に追い出し、ScoreCalculatorがRecordReaderを直接操作するだけで良い形になった
- シンプルなFactoryパターンを適用して、テスト用のScoreCalculatorや本番稼働用のScoreCalculatorを作成できるようにし、テスト実行、本番実行でRecordReaderの構築を分けられるようにした
- 自動テスト( UnitTest )的なコードの内容についても触れた
- (FactoryのところはDIコンテナ (DI Container) から行っても良さそうなことを示唆)
解説に利用したコード ~ Sample code ~
https://github.com/CircleAround/pgonline/tree/master/src/20201006classdesign