コードを共通化・抽象化するときは以下のような順番で取り掛かると良いんだろうな、という話
- 分かりきっている複雑性が低いコンポーネント
- ドメイン固有の複雑性が高いコンポーネント
- 1と2のコンポーネントを束ねる役割の複雑性が中程度のコンポーネント
自分が今Flutterを書いているのでフロントエンドだけの話かもしれないが、思っていることを書いてみる
背景
仕事でのコードに対するコミットメントがメンバーのPRレビュー中心になってきた。全体のアーキテクチャは自分がある程度整理していて、個々の機能開発の方針は任せている。
レビューは、局所的な設計指針に細かく指摘するよりもある程度本人の方針に任せ、テストコードや一貫性で全体の品質を担保すれば良いというスタンス。
結果的に、無難なコードで多くの機能が素早く追加されてきた。プロダクトのフェーズ的にも十分満足していて、今後のリファクタリングにも耐えられる必要十分なコード品質だと考えている。
ただ最近自分でコミットメントしたほうが良い開発案件があり、久しぶりに自分で実装することになった。やってみると、自分が考える設計とは違う部分たくさんあることに多少ストレスを感じてしまった。で、必要な部分はリファクタリングしつつ案件を進めている。その過程でごちゃごちゃ考えていたことを言語化した。
分かりきっている複雑性が低いコンポーネント
例えばThemeだったり、よく使うTextStyleやElevatedButtonとか。共通で扱うことを想定してコンポーネント化されているものはカスタマイズ性が高く柔軟なことが多いので、それをプロダクト用に固めてしまうと収まりが良くなる。
機械的に進めていけるタスクなので取り掛かりやすい。コストに対するリターンのコスパもよく、終わったときの満足感もそれなり。ごく当たり前にほとんどの人がやっていることだと思う。
ドメイン固有の複雑性が高いコンポーネント
最小単位のコンポーネントをまとめた後はもう少し大きいコンポーネントのリファクタリングをしたくなるが、これは止めたほうが良い。自分は今まで何回もそれで失敗してきた。
中程度のコンポーネントは複雑性が高いコンポーネント依存することが多い。複雑性が高いコンポーネントがそのままの状態でそれ微妙な仕様差異を吸収しようとすると、結果的に変なパラメータが増えたよくわからないコンポーネントに化ける可能性が高い。巷で言われる神クラスのようなものは総じて、良く使われる中程度のコンポーネントだったものが化け、名前とかけ離れていたり凝集度が低いように見えるようなものだったりする。
しかも、時間がかかる割にインパクトが大きくないので、生産性が上がった・見通しが良くなったというような成果の実感が得にくい。
ドメイン固有で複雑で抽象化しきれていない処理は、変更するには取り掛かるハードルが高くて深い理解が必要。しかし、コードリーディングに一番時間がかかる処理だからこそインパクトがあるし、影響を与えている中程度の処理の設計の改善アイディアにも繋がりやすい。
確かに難易度が高いが、小さいコンポーネントのリファクタリングしているときの 「やって意味あるのかコレ?」感が無いのも個人的にはグッドポイント
1と2のコンポーネントを束ねる役割の複雑性が中程度のコンポーネント
小さいものと大きいものが終わったら、それらを繋ぐ中程度のコンポーネントに着手する。この頃には全体構造への理解が進んでいるので、変な共通化(Common~、~Utilを多用するとか)をしない限り目的が明確なコンポーネントとなって、IFも想像しやすいものになっていくと思う。
直接何かをするというよりも、Builder
やMediator
のような性質の薄めのコンポーネントのほうが収まりが良いかも知れない。大きいものと小さいものを繋ぐだけのイメージ。中くらいのコンポーネントも具体的な処理を始めると抽象化のレイヤーが3段階になって、自分は難しく感じる。これは人それぞれかも。
正直このコンポーネントは人によっては不必要に見えたり価値を感じなかったりすると思うので、無理して作らず他の仕事を優先しても良いかも知れない。
まとめ
自分がリファクタリングするときの指針をまとめてみた。見る人によってはナンジャコレ感があるかも知れないが、あくまで自分の方針ってことで許してほしい。
最近はアーキテクチャよりもコンポーネントの凝集性のほうが重要だと感じていて、それを実現するためのリファクタリングの観点に意識を向けている。
現実世界のプログラムは小規模・大規模のようなラベル付がされているわけではないので何となく難易度が簡単な順で着手しがちだが、一旦立ち止まって全体に対しての相対的な複雑性を眺めてから仕事に取り掛かるようにしたい。