第5回 今ならこうするシリーズ。更新を忘れていた。
自分でFlutterのWidgetを作っているときに以下のように値を受け取るWidgetを作りがちだった。
HogeButton(title: "ほげボタン", titleColor: ...); class HogeButton extends StatelessWidget { @override Widget build(BuildContext context) { return TextButton( style: ..., child: Padding( ..., child: Text(title, style: ...) ), ); } }
今ならこうする
HogeButton( child: HogeButtonRegularText( title: "ほげボタン", titleColor: ... ), ); class HogeButtonRegularText extends StatelessWidget { @override Widget build(BuildContext context) { return Text(title, style: ...) } } class HogeButton extends StatelessWidget { @override Widget build(BuildContext context) { return TextButton( style: ..., child: Padding(..., child: child), ); } }
ここまで書いてボタンの例はあんま良くないかもなと思いつつめんどくさいのでそのまま進める。
理由
Widgetの役割を分けたい
値だけを受け取るWidgetのHogeButtonは、以下のような役割がある。
- Buttonのスタイルを決める
- Buttonのchildを囲むPaddingを決める
- Buttonのchildの内容を決める
- Buttonのchildのテキストスタイルを決める
一方でchildを受け取るWidgetのHogeButtonは、以下のような役割がある。
- Buttonのスタイルを決める
- Buttonのchildを囲むPaddingを決める
その代わりにHogeButtonRegularTextが以下を担う。
- Buttonのchildのテキストスタイルを決める
またButtonのchildの内容を決めるのは、HogeButtonを呼び出すところになる。
このように役割を分けることで、密結合が避けられて影響範囲を小さくし変更に対するコストを下げることができる。
具体的には、ボタンにテキストだけではなくIconを表示したい場合、childを受け取るWidgetの場合はHogeButtonIconTextを作ればよいが、値だけを受け取るWidgetはIconがある場合は表示するなど分岐をする必要があり後々大変になってくる。
分岐を避けるためにIconHogeButtonを作ることはできるが、HogeButtonとボタンのスタイルは同じなのにWidgetは分ける必要があるため、再利用性が低い。
懸念点
コードの記述量が多くなるのでは
多くはなる。ただchildを受け取るWidgetのほうが関心の分離は出来ているし、それぞれのクラスは非常にシンプルな役割になっている。逆に値だけを受け取るWidgetだとコードの量自体は少ないが、HogeButtonが複数の責任を持つクラスになり変更に対するコストが大きくなってしまう。
自由度が高すぎるのでは
Widgetであればなんでも受け取れるのでHogeButtonRegularTextがすでにあることに気づかずに自前で作ってしまうみたいなこともありそう。それをどうやって防ぐかは命名規則をしっかり決める、デザインするときに名前をつける以外で今の所いいアイデアがない。
メソッドを分ければ役割が分かれるのでは
メソッドで分ける方法は、影響範囲が明確に分かれていないので役割も分かれていない。
まとめ
正直なところ、こういうことは1年で消えてなくなる可能性があるサービスは、あんまり気にする必要がないと思っているけど、少なくとも数年は生き残りそうなソフトウェアは、コードの記述量が少ないといった短期的に楽なコードではなく変化しやすさを重視した中長期的に楽なコードを目指すべきではないかと思っている。
あと当たり前なんだけど値だけを受け取るWidgetを一切作らないのは無理な話なのでそれは考えてない。むずかしいな〜〜