たれぱんのびぼーろく

わたしの備忘録、生物学とプログラミングが多いかも

抽象化とYAGNI/KISS

その抽象化、本当に必要ですか/YAGNI
無駄に凝集度を下げてシンプルさを失ってませんか/KISS?

抽象化と疎結合

抽象への依存は疎結合を促進する。

  • origin 一体: AB
  • step1 分離: A---B
  • step2 抽象化: A-inter & inter-B => A.bind(B)

一体化してるやつを切り出し.
例えば関数にして切り出し.

切り出し部分にインターフェース定義 + 注入
「抽象に依存」しているので好きな具象を注入 (依存性の注入) できる.

インターフェースだけを繋ぎにして両側が独立.
AをA'にしても、BをB'にしてもOK.
両者を疎結合 (低い結合度、loose coupling) にする。

無限分割

1行1行にインターフェースを用意しようと思えば用意できる.

抽象化選手権

お題: タイマーでアラーム起動

素朴な実装

setAlerm = () => setTimeout(alert, 2*3600, "おはよー");

setAlerm();

TimerとReaction

type reaction = () => void  

setTimer = (rct: reaction) => setTimeout(rct, 2*3600);
hello : reaction = () => alert("おはよー");

setTimer(hello);

「起動」と「事象」の分離.
事象側はタイマーの実装を知らなくて良いし、起動側は事象の実装を知らなくて良い.
setTimer(goodNight)とか簡単に用意可能.
Timerの実装をsetTimeoutから差し替えるのも可能.

Trigger - Bridge - Reaction

「引き金」と「橋渡し」と「反応」の分離

type trigger = () => Promise<void, never>  
type reaction = () => void  

timer : trigger = () => new Promise(resolve => setTimeout(resolve, 2*3600);
hello : reaction = () => alert("おはよー");
bridgeActions = (trg: trigger, rct: reaction) => trg().then(rct);

await bridgeActions(timer, hello);

2つのアクションを連携、的な抽象化.
bridgeActions(customerEnter, hello)とかできる.

Trigger - Action / Reducer

Flux/Redux-way.
メッセージパッシング.
TriggerがActionを産み、受け手Reducerは反応したいActionが来たら勝手に応答する.

その抽象YAGNI

抽象化した概念にマッチする複雑性を持ってない場合、YAGNI.
alert関数をお試しで使うためにRedux-wayなメッセージングは過剰.

抽象は存在で、抽象化は動作.
得られる・失われる「特性」を語ってない.

抽象への操作→捨象されたものを忘れられる→単一責任・高凝集

無限分割の何が不適切なのか

役割をサブ役割に、と無限分割が可能.
各ピースは厳密に1つの役割しか持たず、かつ疎結合にされているので、モジュール性が非常に高い.
よい性質を持ってそうなのに、何が悪いのか.

抽象化のコスト: 認知負荷
1セットの実装には全レイヤーの抽象化把握と具象化が必要.
1回しか使われない抽象は利用コストが嵩む.

N:M => N*M通りの把握
N:1:M => (N+1) + (M+1) 通りの把握

抽象の理解にコストがかかる (1の部分)。代わりに組み合わせ爆発を防止できる (*の部分)。

abstX = {instA, instB, instC, ...}
色んな具象を、ある側面から、1つの抽象にまとめあげる.

粒度?

より高レベルな言語を使えば具象側がシンプルになる (実のところは言語処理系が実装でプログラムは抽象)
適切な粒度・凝集度は言語エコシステム含んだ総合的な物.
時代・分野によって違って当然.