スタンプ結合
Stamp coupling
複数のモジュールが、複合データ構造を共有し、その一部のみを使用する
「複合データ構造」はTSならobject型のこと
モジュールが必要としないフィールドが変更されることで、変更しないといけない可能性が生じる
例を見るのが早い
code:ts
const greeting= (user: User) => ${user.name}は$${user.age}歳
これ何が問題なんだ?
いや、そもそもそこまで問題視はされていない
複合オブジェクトを渡して、一部にしかアクセスしないことが問題?
というより、複合オブジェクトを渡すこと自体、が問題?
というか、「複合オブジェクトが知りすぎていること」が問題?
だとすると結合とかは関係なくて、単に内部がpublicすぎる複合オブジェクトの存在を認めている事自体、が問題と言えないだろうか
問題点は何だっけ
依存している型の構造が変わった時に関数の定義を修正する必要がある
と、思ったが、実際どうなんだろう
根幹の型T
Tの周辺の関数f1,f2,..
という2つの登場人物がある時に、
スタンプ結合になっているとき
Tの構造を変えた時、f1,f2,..の定義も変える必要がある
しかし、f1,f2,...を呼んでいる箇所は特に変更しなくて良い
f1(t)としてるだけなので
スタンプ結合になっていないとき
Tの構造を変えた時、f1,f2,..の定義は変える必要がない
ただ、f1,f2,...を呼んでいる箇所は修正が必要
f1(t.firstName)をf1(t.name.first)にしたり
これは例が簡潔すぎるだけで、他の例を出せばスタンプ結合の弱さがもっと出るか?
テストする時にいらんpropertyも用意しないといけないのが面倒
再利用性が低い
その型に対してしか使えない
その型に対してのみ使う、という意図を示すのには良い?
可読性が低い
依存している登場人物の列挙が為されていない
関数の内部を読まないと、実際何に依存しているのかぱっとわからない
嬉しい店
呼ぶ側は楽
OOP的
object丸ごと渡すだけ
objectと関数が密に結合している
↑この表現が正しいのかは不明、後で考える
というか、常にスタンプ結合を排除するのが正しいとは言えないということ
関数の責務として、個々の値に対する処理7日、グループに対する処理7日をちゃんと見極まえる
後者にすべき、とする判断軸を言語化しておきたい
抽出関数は良いけど、導出関数(construcotr的な)は良くない、みたいなのが浮かんだほんまかはしらn
以前あった状況としては、
例えばCurtainみたいなEntityがあって、
その要素を個別に、引数を細かく取って作るとあまり嬉しくならない
関数の引数が増える
関数の呼び方が呼び出し元の責任に移り、修正箇所が増える
引数が変わるたびに、連鎖的に呼び出し元を修正しないといけなくなる
イメージとしては、
Curtaionの構成要素がa,b,c,d,e,fとある時
a,b,c→r1(目的のもの)、
a,b,d,e→r2(目的のもの)、
a,b,r1,f→r3
r1に依存してる
のようなものがいくつかある
そうではなく、
Curtain→{r1,r2,r2}という関係性と捉え直す
r1,r2,r3などは定義より、Curtainから計算されることは自明である
Curtianの内部構造が変わったり、r1,r2などの計算方法が修正されても
Curtain→{r1,r2,r3}という関係性は壊れない
境界部分でこの関数を呼んで、r1,r2,r3を予め生成しておいて、それを使い回す
classをinstantiateしているのにイメージは近い?
調べてないけどfpにもそういう名前のついたテクニックがありそうな気もする?
↑全く意味がわからんので具体例をgpt-4に生成させて億と良さそう
もう1個思いついた
foldあるいはpipelineでやる
スタンプ結合を回避しようと徹すると引数が増えまくる時がある
そういう時に、これを使ってみると上手くいくかも
こんな関数があったとする
calcSewingPrice = CurtainFabric -> CurtainFabricSewingPrices
これは結合が強いのだろうか
実際に、関数の内部では、CurtainFabricの一部のpropertyを読んで計算をしている
しかし、引数でそれをバラバラに渡そうとすると引数の数が増える
CrutainFabricの大きさに依存することになる?
例えばCurtainFabricがかなり大きなオブジェクトだった場合にこれは結合が強いと言えるようになる?
「結合が強い」って何?
そもそもの問題意識って何だっけ
しかし、CurtainFabricという抽象レベルで見れば、シンプルな型だなと読める
「CurtainFabricの中身が大きい」「この関数の具体的な計算方法を知っている」のような知識がある場合にのみ「結合が強い」という風に判断できてしまうのではないか
だとすると、スタンプ結合って問題の指摘としてはだいぶ意味不明ではないか
問題のこともあるし問題のこともない、何の指標にもなっていないのではないか
いやこれ大きいオブジェクトの構造に依存する関数側のスタンスが悪いのか
関数側は、その内容を実現できるだけの構造を新たに考え、それを引数に求めるように変更する
呼び出し側は、今目の間にある構造をその構造に変換して関数を適用する必要がある
いくかのアプローチが考えられる
Interfaceのようなものを定義して、それをimplementsする形で構造を定義する
classの考え方はこれ
traitやtype classも同様のアプローチをしている
なにか構造を定義して、それに対して、特定のinterfaceを満たすように実装を加える
その場で、その構造に変換する層を導入する
これで実現はできるけど層自体のメンテナンスコストが生まれるのであまり良くない
そもそも今から作るデータ構造を、そのinterfaceの要件を満たすように作る
これはできるけど、スケールしなさそうな予感しかしない
そもそもそれができるなら最初からやっとるわいという感じがする
↑これらは結局全部同じことをしていて、手段がやや異なるだけ
その手段によってメンテコストが大きく変わってくる
tsでfpをする場合、恐らくした2つの方法しか取れない
classを使うなら最初の方法を使える
なのでかなり辛い
そう言えば、型クラスをサポートしていないfp系の言語ってなにか代替手段があるんだろうか
F#とか?
関数視点で見ると、
そのオブジェクトが同一モジュールに属するなら、まあそのオブジェクトを引数とにとっても良いと判断できそう
身内なので死なば諸共だし、その引数の中身を全部知っていてもおかしくはない
密結合なのだが、moduleしてるというのはそういう判断をしたということだし
同一モジュールに属さないのであれば、特定のオブジェクトの構造に依存するのではなく、関数視点で最も理想的な構造を引数に取るようにする
その構造が、そのモジュールの扱うデータ構造だと解釈できる
この引数のオブジェクトの大きさが問題と言うよりは、
そもそものデータ構造に対するアクセス可否の部分が問題になるのでは
お大きい抽象度での関数の扱い
gpt
構造が変わったときの例や、他の例を考えてもらう
getterを用意すると良い?
だとするとやっぱりオブジェクトの扱いの話ということになりそうだけど
引数云々ではなく「大きいオブジェクトを取り扱っている」ということ自体が問題にはならないだろうか
知りたいのは大きいオブジェクトの取り扱いほうほうではないだろうか