本記事の目的
良いコードとは何かを明確にし、
リファクタリングを通じて既存のコードを
良いコードにしていく方策を伝えること
想定読者
- スピード優先で進めた結果、テストがなくコード変更が難しくなってきたプロジェクトのメンバー
- 自動テストを書いた経験が薄いエンジニア
- バックエンドエンジニア
本記事で扱う内容
- 開発スピードを極力落とさずに、テストを実装しながらリファクタリングを進める取り組み
- バックエンドのテスト戦略
本記事で扱わない内容
- テストやリファクタリングの具体的な実装方法
- フロントエンドのテスト戦略
"良いコード"について
自分は良いコードを「変更容易性が高いコード」と定義しており、
後輩に「いいコードとは?」と聞かれたら変更容易性に着目して話をよくしています。
変更容易性と言ってもただ変えるのではなく「バグを出さず(安全に)、かつ素早く正確に変更が可能」という状態を「変更容易性が高い」と思っています
ところで試しにそこそこ権威のありそうな定義があるのか見てみるとそれっぽいものが見つかった。
国際規格 ISO/IEC25010 による定義だと、変更容易性とは 「どれだけ短時間で安全に、低コストで変更を完了できるか」 らしい。
つまり変更容易性が低下すると、以下のような痛みが現れるということと解釈。
- 変更に時間がかかる
- 品質を担保した変更が難しい
- コードが複雑で変更コストが高い
変更容易性を構成する要素
変更容易性を考える時、自分は以下のようなことを意識している。
- 解析性:インテグレーションテストによりひとまず「正しい振る舞い」をスナップショット化する
- 変更性:巨大ロジックを分割し、変更しやすくする
- テスト性:自動テストによって期待通り動作することを確認しやすくする
- 再利用性:コンポーネント分割により再利用性を高める
※変更性と再利用性はリファクタリング方針に依存する
今回のアプローチの3Step
自分は以下のような手順でリファクタを行う。
1リファクタリングの方針を決める
2インテグレーションテストでガードレールを立てる
3段階的リファクタを進める
1. リファクタリングの方針を決める
- オブジェクト指向プログラミングやオニオンアーキテクチャを採用する、など。
- 先に述べた変更容易性の構成要素がどの方針でどう変化するか検討。
- ディレクトリ構造や現状とのギャップを明確にする
よく知れたソフトウェアアーキテクチャやプログラミングの原則は
基本的に変更容易性を高めるためのプラクティスだと思っている。
適切なプラクティスをどう取り入れるかを検討することで
巨人の肩に乗りやすくなる。
往々にして運用コストが見合わなくなることがあるので、
自分たちのPJに何が適切かよく考えよう
ただ、経験がないものはわからないので、
有識者がいなければ少しやってみて
再検討するなど、イテレーションを回すと良いかも。
2. インテグレーションテストを実装する
- 変更容易性のうち、解析性を確保する。
- ユースケースごとにリクエストや入力を洗い出し、レスポンスを固定化
- リファクタ前の挙動をテストで固定し、最低限のガードレールを用意する
プロトタイピングやリリース前の段階ではクラス設計が未熟で、
内部構造を大きく変える可能性がある。
それゆえにこの段階で UT を書いてしまうとリファクタに合わせて
UT を何度も書き直す必要が生じるため、
まずは外部から見た振る舞いを固定できる IT に寄せている。
最初から完璧なドメインモデリングやクラス設計は難しいし、
できたとしても特にプロトタイピングは変化が激しいよなという経験則。
3. 段階的にリファクタリングと単体テストを進める
-
インテグレーションテストはカバレッジを高めると実行時間が長くなるため、徐々に UT に置き換える
3について
自分の場合はチーム外が管理するAPIなどでない限りモックしない。
当たり前だが、モックするとモックしたロジックのテストができないので、ORMを使うロジックのバグに気づけなくなるし、往々にしてORM自体のテストをするために依存関係をモックするコストも高い印象がある。
なので自分のチームが管理するDBとの連携ならば通しておく。
故にUTよりもITを優先するモチベーションが高い。
個人的な実践のポイント
-
一度に全てのテストを実装しない
- 変更を加える機能から順にテストを追加する
- PoC 段階では既存コードをそのままにし、リファクタ方向性だけ決める
-
UT と IT のバランスを意識する
- 経験上コードが単純なら IT 寄りで十分な場合が多い
- クラス設計や内部構造が固まってきたら 共通関数や複雑なドメインロジックに対してUT を増やし、テスト自体のメンテコスト過多・負債化を防ぐ
まとめ
- テストがない状態では「変更容易性」が低下し、変更コスト・安全性・時間の面で大きな負担となる
- 変更容易性を高めるには 解析性・変更性・テスト性・再利用性 の観点が重要
- プロトタイピングやリリース前の段階では まず IT による振る舞い固定 を優先し、内部構造が安定してから UT を増やすことで、開発スピードを保ちながら改善できる