クラス設計の時に考えること
先日エンジニアメンバーからクラス設計について相談があり、やっぱり単一責任原則は大切だなぁ、クラス設計の時に考えた方が良いこと・前提とする土台にはこういうのがあるよなぁ、みたいな話を色々ふわっと思ったので覚えてるうちに言語化しておく。
事象
相談内容: サードパーティのライブラリを扱うラッパークラスみたいなのを書いて将来の使い回しに備えたが、実際に再利用する時になってみるといまいち使いにくかった。共通化、再利用はできていると思うがしっくり来ない要因がピンと来ないので見てほしい
見た所感: 汎用性を意識しすぎた結果、薄いラッパーがライブラリの関数をコールするだけのクラスになってしまっておりラッパーに何を任せたいのかわからないコードになったために再利用は別にできるけどこのラッパーによって得した事が特に無くモヤっとする、みたいなことが起きているようだった
結論フィードバックは「ライブラリのコードと責任被ってるのでもっとやりたい仕事に特化したクラスとして設計し直して名前も具体的にすれば役割がはっきりして綺麗に使い回しの効くクラスになると思うよ」みたいな感じになった。
この問題が起きやすい要因
これは割と起こりやすい責務定義の問題という感じなのだけれど、なんでこれが起きやすいのか考えてみるとたぶんDRY原則はわかりやすく意識されやすくて守られやすいのに対して、単一責任の原則がわかりにくくて意識されにくく守られにくいというのがありそうと思った。
DRYと単一責任
DRYというのは「Don't repeat yourself」の略で重複コード書かずに共通化しましょうみたいな意味で語られるのが多い原則。
単一責任の原則とは、モジュール、クラス、関数は単一の機能について責任を持つように設計しましょうという原則。
どちらもソフトウェアを変更しやすく維持・保守運用していきたい時に守ったほうが良いよと言われるルールの1つ。
前者はおそらくソフトウェアエンジニアでなくともイメージがつくくらいには単純で重複コードが100箇所ある時、重複したコードを変更したくなった際の修正箇所は100箇所になる。再利用可能な関数を実装するなどして共通化してあれば直す箇所は1箇所で済む。効率的に開発が進むには重複が少ない方が良いことは誰の目にも明らかで、それを守る方法も明確。
他方、単一責任の原則はどうだろう。たぶん正確にその価値観を理解してどのように原則を守るのか方法論を語れるエンジニアはDRYに比べてかなり減ると思われる。
単一責任の難しさ
例えば会社組織において部門や人の責任と権限、仕事内容を定義する時、これはこれで特有の難しさはあるがソフトウェアにおける責任定義の難しさに比べると現実で起きて目に見える世界の話という意味でソフトウェアのそれと比べると一般にはだいぶ想像しやすいだろう。
部門Aの役割、部門Bの役割、それぞれの役割を果たすためにどんなスキルの人材が何名必要である、今後計画通り事業が進捗した場合にそれぞれの仕事量の増え方はどんくらいを見込んでいて採用計画はこんくらい、みたいな話は必要な情報にちゃんとアクセスできさえすれば誰が考えたってそんなに差は出ない。(と思っている。どちらにせよ判断の精度というのは必要な情報を揃える事の方がはるかに大事なのは確かで、揃わなければ間違えるし揃えば間違えにくい)
これがソフトウェアになると責任を付与する対象(プログラム)の形を自由に決められるのでやりたい仕事をどの粒度で分割してどこからどこまでをプログラムAに、どこからをプログラムBに任せるのが適切か、みたいなことを考えることになる。これを適切にやるのがけっこうむずい。
人の仕事を決める時、その人のスキル、アビリティというのはその時点で決まっていて短期的に大きく変化しないので適材適所で配置を考えれば良いが、プログラムはそのプログラムに持たせる能力をプログラマの裁量でコントロールできる。
そうして実装したプログラムを活用した新たなプログラムを書いて別の新しい仕事に対応したり、元々のプログラムを改修してプログラムの能力を増やしたり減らしたり変えたり分割したりくっつけたりする。
そして↑こういうややこしい営みに自分以外の開発者も含んだチームで長いこと付き合っていくことになる。
仕事がスムーズに進むために必要なこと
会社とかで自分の仕事をスムーズに進めるためにはその仕事を進めるために必要な処理とその権限を持った部署や人を把握していることが重要である。
それがわからないといちいち誰かに確認したり調べたりといった工数が間に挟まり、仕事の本質的な進捗を阻害する。
↑の世界では部門ごとの責任や権限、人員配置などを老獪な経営者やマネージャーたちが理性的なプロセスで合理的に決めるため、あんま変なことにならない。新人でも大体1回教えてもらえば誰でも効率よく仕事を終えられるように多くの場合はなっている。あるいは教えてもらえなくても自分で経験して必要なことを身につけていく。
この時、仕事を効率よく進められるようになった新人Xの頭の中で何が起きているのか。
この会社ではこういう業務はあっちの部署で担当している、とかこういう情報はなんとかっていうツールにストックされている、とかこの辺でわかんないのはあの辺に聞けばいい、とか情報の在処に関する認知が形成されたはずだ。その結果として仕事が進めやすくなる。仕事を覚えた、という状態へ近づいてゆく。
ソフトウェアエンジニアリングにおける認知形成
こういう業務はあっちの部署で担当している、みたいな認知の形成はソフトウェアエンジニアリングの文脈ではこういうプログラムはあの辺に置いてあるはずだ、とかこういう名前のプログラムならこういう機能を持っているはずだ、みたいな感じで主にプログラムの
置き場所
名前
機能
の3つに関する期待と洞察の中で進む。(完璧な現場なら全てがドキュメントにまとまっているかもしれないがそんな環境は残念ながら見た事が無いしそれをするのが正しいとも思わない)
一例として一般的なwebフレームワークで言えばデータに関する実装はModelに、HTTPに関する実装はControllerに、UI・テンプレートに関する実装はViewにあることが期待される。
↑のメジャーな三層構造の範囲で間違うことは少ないが、複数モデルを操作する一連の処理をサービス層にまとめるみたいなことをやり始めると途端に「MVC以外全部突っ込みました」みたいなカオス層が爆誕するというのが実際の現場で頻出の課題と思う。
50000行のModelは見た事なくても50000行のなんちゃらServiceを見てこんなの読んでらんねーよと頭を抱えた経験のあるエンジニアは多いだろう。
なぜ↑が起きるか?
Modelの役割は一般に定義されているが、なんちゃらServiceの役割、責任を決め実際の能力を設計、実装していくのは自分たちだから。
つまるところそのチームのソフトウェアエンジニアリングに関する実力が如実に反映されてしまうのである。
一般に定義されたり推奨されたりするわかりやすい領域の外がうまくいってない環境というのは大体エンジニアリングチームの上位職がワークしておらず、中長期的な開発方針を示せていない、ろくにフィードバックもできていないために無秩序に単一のプログラムにひたすらメンバーが機能追加していった結果としてメンテ不可能になるまでインフレーションしていく慣性を獲得したやべープログラムが静かにしかし確かに、増え続けていく。
経済のインフレと同じで金利上げたからってすぐ効かないのでしばらくそのまま増え続けるしその世界のコードの1行あたりの価値はめちゃくちゃ低い。
そんな感じでとりあえず膨張の一途を辿ったプログラムというのは自分に与えられた名前なんか無視した能力を無数に獲得している。
あるいは最初からあってないような名前を付けられてその名の通りに膨らんでくやつもいる。(こいつが1番不憫である。大体なんとかUtilとかなんちゃらHelperとかふわっとした名前がついている)
これです。こいつらを認知するコストがめちゃくちゃ高い。名前から想像できない能力を持って、それを行使して実際に仕事しちゃってるから。
取引先から契約書もらったから法務にリーガルチェック依頼しに行ったらうちの会社は総務がやるので総務に行ってください言われるみたいな。いやその機能は法務に実装しとけよ!と言いたくなるのは名前から想像できる事と実際との間のギャップの分、認知にコストがかかるからである。単純に新しく覚えなきゃいけないこと増やしてくれんなよという事とそんなんだったら他の機能も変なとこに実装してそうだけど大丈夫そ?ってなる。
そのラッパーがいまいちな理由
たぶんDRYが目的になってしまったのが微妙、みたいに言うのがそれっぽいんじゃないかと思う。
目的はもっと他にあって、それを実現する手段の支えになるのがDRYや単一責任の原則。
無い仕事をするための部門を立ち上げちゃったみたいにも言えるかもしれない(そんなことあんのかと思う人がいるかもしれないがソフトの世界ではこれに近い現象は割と起きる)
まとめ
クラス設計は単一責任原則に従って小さくシンプルに維持しよう
DRYや共通化が目的にならないように気をつけよう
ソフトウェアの世界では自由は不自由、便利は不便の元である
あとがき
まとめを書いててたったこれだけのことを言うのにこんな長い文章書く必要あったんかと書き始めたこと自体を軽く後悔するほどには長くなったが、最近久々に電車通勤をしていて隙間時間があるのでこの時間をアウトプットの時間に昇華するという生産的なことをする生産的な人間になりたい。
この記事が気に入ったらサポートをしてみませんか?