📘

うわ、私の凝集度低すぎ、、、!?

2023/04/25に公開

さすがに古いですね笑

前書き

筆者は最近個人開発を始めました。
プログラミングのプの字も知らない状態から必死に勉強し、ようやく形になれる程度にはコーディング能力が身についたのです。

さて、個人開発をすすめていくうちに気がついたことがあります。
なんか、やたらいろんなところのクラスを行ったりきたりするなと。

やたらクラスを移動するらしい

ことの始まりは、とある音読アプリを作っていた時のことです。
筆者のディレクトリ構造はごくごく一般的な、どこのご家庭にもあるLikeオニオンアーキテクチャです。
データクラスや概念をオブジェクト化したものをまとめるDomain層があり、そのDomainをこねくり回すNotifierクラス(筆者はFlutterを使っています)、データはRepository君がNotifierさんにDIで渡してあげて(主語あってる?)、最終的にPage層で表示するみたいな流れになります。
拙いところはあるものの、俯瞰すればよくみる構造だと思います。


(引用: https://little-hands.hatenablog.com/entry/2018/12/10/ddd-architecture)

しかし、筆者は自分のプロジェクトのdomain, controller, repositoryと眺めてみるとどれがどこと繋がっているのか把握しづらいことに気がつきました。

うわ、私の凝集度低すぎ、、、!?

(タイトル回収)

なにが問題なの?

バグの原因になりうる

関係しているdomainやcontrollerが離れているので、各々の関係性を把握しづらくなりミスを誘発しやすくなります。
左側(今回は名前について言及していなかったので今記事では役割駆動パッケージングと呼ばせていただきます。)でも多少は離れているのですが、共通の関心事のディレクトリにまとめてあるので離れていても整合性が担保され、ミスをする可能性を減らせます。
対して技術駆動ではディレクトリ間を大きく跨ぐことになることで、各オブジェクトが間違った用途で使用され、予期せぬバグを起こす可能性が高まります。
個人開発の領域ではそこまで顕著なものではないですが、中規模以上の開発になってくるとこのデバフがボディーブローのように効いてきます。

認知的負荷の増加

関係しているdomainやcontrollerが離れているので、認知的負荷の増大にもつながります。
担当を割り当てる時も関心ごと単位で話ができるので意思疎通がとてもスムーズになりますし、なにより説明をする時間が大幅に減らせるのでお互いストレスなく開発ができます。

ゲームのパッケージの中に違うゲームのカセット入れてる友人見たことありませんか?あれです。
ある程度まとまった方が整理しやすいのに惰性で管理してしまい、結果後悔することになったことは簡単に想像できますよね?今回も同じことが言えます。

技術駆動と役割駆動では前者の方が実装するのにそこまで脳を使わなくて済むというところが厄介なところです。(設計書次第なところもありますが)。

ゲームを片付けるその一瞬の時間では楽です。何も考えず適当にしまえばいいので。
しかし、いざやりたいゲームを探すときに手間取るのは目に見えています。

そのパッケージが100個あるとしたらどうなるでしょうか?

何が原因か

ずばり、クラス間の関係性がいまいちよくわからないという点です。
domainやcontrollerなどの依存の方向はオニオンアーキテクチャの通りなのですが、同じ層の中でのクラス間の関係性が明示的ではなかったのです。
何日も連続して開発している身としてはクラスを見ただけでどういう立ち振る舞いをしているか簡単に想起できるのですが、
いざ他の人に手伝ってもらおう
だったり、
仮に何週間か開けた後に再度開発しようと思ったときに

これ絶対戸惑うだろうな、と思いました。

調査

こんな筆者でも思いつく話なのだから、絶対誰かしら議論しているだろと思い、調査したところ見事にヒットしました。
https://twitter.com/MinoDriven/status/1148960876201836546?lang=ja

どうすればいいの?

私なりの解釈になりますが、密度をもっと高めればいいという考えに至りました。
そもそも凝集度というのは、そのオブジェクトの中に、どれだけ必要なものが含まれているかを表す指標になります。
これを同値変換すると


オブジェクトが実際に含んでいる内容 / オブジェクトに含むべき内容

となります。
密度というのはまさに上の式であり、この値が1に近づけば近づくほど凝集度が高まります。

すなわち密度を高めるとはオブジェクトが真に含むべき内容を考え、その内容を満遍なく実装すること。簡略化するとそのオブジェクトの役割を明らかにすることだと言えます。

domain, repository, serviceなどが上位ディレクトリになるより、給与計算や入社情報といった関心ごとが上位になることでクラス同士がまとまり、効率よく開発ができます。
(技術的なものが乱雑になることが事の発端なので、乱雑にならなければdomainなどが最上位になっても矛盾しません。というかおそらくTwitterの記事はその考えがあってのディレクトリ構造だと思います。左側のやつです。)

今回の話では、音読アプリなので「音読」「文章」「結果発表」というような名前のディレクトリを最上位にし、その中にオニオンアーキテクチャを適用させれば良さそうです。

脱線するけど、、、

筆者はこういう話を想起する度に分子にフォーカスしていましたが、今ではそれ以上に分母にフォーカスするべきだと考えました。
いくら凝集度を高めよう高めようと思って一つのクラスにたくさん詰め込んだらクラスが巨大化した「神クラス」になってしまいます。

ところで

オブジェクト、と書きました。クラスとかディレクトリとかではなく、それらを包含するオブジェクトです。
そのとき、気づかれた方もいらっしゃるかもしれません。
オニオンアーキテクチャって、別にプロジェクト単位じゃなくてもよくね?

そうなんです。
私は無意識のうちに上記のオニオンアーキテクチャはプロジェクト単位で管理、運用するものだと思っていました。
別に間違いではないのですが、そう言った表記がされていないので関心事単位でも採用しても問題はないと言った発想が生まれます。
先ほどの最上位ディレクトリの話がまさしく。

先ほどのTwitterの記事のディレクトリを例にとると、給与計算業務の中にリポジトリやエンティティが含まれていることが分かります。
これは、まさしく関心事単位でアーキテクチャを構築しています。
(Domainとcontrollerが分かれているのは各々が多対1の関係だと思われます。おそらくですが。)
筆者は最上位ディレクトリにdomain, controller以外にもnotifier層などを設置したので、凝集度が低くなってしまいました。

オブジェクト、と書いたのはクラス単位とかプロジェクト単位ではなく、それらより一つ上の概念を表したかったからです。アーキテクチャを採用する箱庭は別にクラスとかプロジェクトとかそういった決まりは特にないんだよ、ということです。

コーディングUXを向上させる

たとえコーディングする人間が自分一人だったとしても、惰性で管理すると碌な目に合いませんね。
毎日開発している時でさえ顕著に現れるものだから、誰かに委託する時とか数週間ぶりに開発すると考えたら地獄です。

私は、他人に説明せずともが理解できるようなアーティファクトを心がけようという基礎的な部分が抜け落ちていました。

そう思うと、今回の話はUI/UXデザインに近いものを感じます。
説明せずともユーザーが意図した通りの操作をしてくれるか、というところに親近感を覚えます。
(筆者はデザインスキルが0なので間違っている箇所があるかもです。)

結局のところ、誰か(自分も含めて)に見せる可能性が0ではないのであれば、惰性で作業するべきではないですね。
悪態をつくわけではないのですが、誰かの存在を気にせず作業することがいかんせん楽なものなのでついつい惰性で動いてしまい、結果今回のような失態を犯す羽目になりました。

自分が構築したものが他者から見て簡単に把握できるものなのか、常に自問自答しながら書いていくことこそがマスターへの第一歩なのでしょうね。筆者はその第一歩の存在にすら気づきませんでした。

そう考えると、今回の話は哲学にも通づるものがありますね。

以上現場からでした。

参考記事

https://qiita.com/MinoDriven/items/2a378a09638e234d8614
(役割駆動っておっしゃっていました。)

https://zenn.dev/tabio/articles/good-code-bad-code#技術駆動パッケージング
(Twitterの記事の方はこちらの本の作者らしいです。恐れ多いです。)

https://togetter.com/li/1374809

Discussion