Upgrade to Pro — share decks privately, control downloads, hide ads and more …

分岐を低減するinterface設計と発想の転換 / interface_design_ide...

分岐を低減するinterface設計と発想の転換 / interface_design_idea.pdf

2023/03/03、エンジニア文化祭2023の登壇資料です。
『分岐を低減するinterface設計と発想の転換』
https://forkwell.connpass.com/event/272596/

MinoDriven

March 03, 2023
Tweet

More Decks by MinoDriven

Other Decks in Programming

Transcript

  1. if (赤外線センサー.有効() && 赤外線センサー.検知()) { if (SMS通知.有効()) { SMS通知.実行(); }

    if (メッセージアプリ通知.有効()) { メッセージアプリ通知.実行(); } } else if (窓ガラス破損.有効() && 窓ガラス破損.検知()) { if (SMS通知.有効()) { SMS通知.実行(); } if (メッセージアプリ通知.有効()) { メッセージアプリ通知.実行(); …. 左記のように分岐が複雑な作り になってしまいました。 その後さらに仕様追加されるに つれ、分岐は爆発的に複雑化し ていきました……
  2. 【問題領域】 【解決領域】 【目的達成手段】 地図 【目的】 山登りしたい 【目的/課題】 遭難したくない 【目的/課題】 虫刺され怖い

    【目的達成手段】 虫除けスプレー 【目的達成手段】 山登り 課題抽出 (目的の具体化) 手段策定
  3. 【問題領域】 目的/課題 目的/課題 目的/課題 目的/課題 目的/課題 【解決領域】 達成手段 達成手段 達成手段

    達成手段 達成手段 目的/課題と達成手段は、 ツリー構造、かつ鏡合わせ の関係になる
  4. 【問題領域】 【解決領域】 【目的】 目的地に移動したい 自動車 電車 飛行機 << interface >>

    移動手段 移動する() 目的達成手段の抽象化 同一目的に対する 達成手段のバリエーション
  5. 自動車 電車 飛行機 << interface >> 移動手段 移動する() ゴーヤ 船

    どう考えても 移動目的には使えない 移動目的として 使用可能
  6. 仕様1 赤外線センサーで侵入検知する。 仕様2 侵入検知したことを家人のスマホへSMS通知する。 要望1 メッセージアプリでも通知してほしい。 要望2 窓ガラスの破損を検知できるようにしてほしい。 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる

    ようにしてほしい。 要望4 顔で家人かどうか判別はできないものか。 要望5 動き方の特徴で家人かどうか判別できませんか。 要望6 物を壊すなど、異常な行動を検知できないものか。 初期の仕様: 追加要望:
  7. 仕様1 赤外線センサーで侵入検知する。 侵入検知目的 仕様2 侵入検知したことを家人のスマホへSMS通知する。 通知目的 要望1 メッセージアプリでも通知してほしい。 通知目的 要望2

    窓ガラスの破損を検知できるようにしてほしい。 侵入検知目的 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる ようにしてほしい。 侵入検知目的 要望4 顔で家人かどうか判別はできないものか。 映像分析目的 要望5 動き方の特徴で家人かどうか判別できませんか。 映像分析目的 要望6 物を壊すなど、異常な行動を検知できないものか。 映像分析目的 初期の仕様: 追加要望:
  8. 【問題領域】 【解決領域】 【目的】 通知したい << class >> SMS通知 << class

    >> アプリ通知 << interface >> 通知手段 通知する() 【目的】 SMSで 通知したい 【目的】 アプリで 通知したい 通知目的で 抽象化可能
  9. 仕様1 赤外線センサーで侵入検知する。 侵入検知目的 仕様2 侵入検知したことを家人のスマホへSMS通知する。 通知目的 要望1 メッセージアプリでも通知してほしい。 通知目的 要望2

    窓ガラスの破損を検知できるようにしてほしい。 侵入検知目的 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる ようにしてほしい。 侵入検知目的 要望4 顔で家人かどうか判別はできないものか。 映像分析目的 要望5 動き方の特徴で家人かどうか判別できませんか。 映像分析目的 要望6 物を壊すなど、異常な行動を検知できないものか。 映像分析目的 初期の仕様: 追加要望:
  10. 【問題領域】 【解決領域】 【目的】 侵入検知したい << class >> 赤外線 センサー <<

    class >> ガラス破壊 センサー << interface >> 侵入検知手段 検知する() 【目的】 動きで 検知したい 【目的】 ガラス破損を 検知したい 検知目的で 抽象化可能 【目的】 映像で 検知したい << class >> 監視 カメラ ※ここでの「赤外線センサー」や「ガラス破壊センサー」は物理的 なデバイスではなく、各デバイスの制御用クラスをイメージして おります
  11. 仕様1 赤外線センサーで侵入検知する。 侵入検知目的 仕様2 侵入検知したことを家人のスマホへSMS通知する。 通知目的 要望1 メッセージアプリでも通知してほしい。 通知目的 要望2

    窓ガラスの破損を検知できるようにしてほしい。 侵入検知目的 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる ようにしてほしい。 侵入検知目的 要望4 顔で家人かどうか判別はできないものか。 映像分析目的 要望5 動き方の特徴で家人かどうか判別できませんか。 映像分析目的 要望6 物を壊すなど、異常な行動を検知できないものか。 映像分析目的 初期の仕様: 追加要望:
  12. 【目的】 映像を分析して 検知できるようにしたい 【目的】 顔で家人かどうかを 判別したい 【目的】 動き方の特徴で 家人かどうかを 判別したい

    【目的】 異常行動を 検知したい 【目的】 映像で 検知したい 【目的】 映像を後から 見れるようにしてほしい
  13. << class >> 顔検出 << class >> 歩行パターン検知 << class

    >> 異常行動検知 << class >> 監視カメラ << class >> 映像記録 << interface >> パターン検知手段 検知する()
  14. 顔検出 歩行パターン 検知 異常行動 検知 監視カメラ 映像記録 << interface >>

    パターン検知手段 検知する() ガラス破壊 センサー 赤外線 センサー << interface >> 侵入検知手段 検知する() << interface >> 通知手段 通知する() SMS通知 アプリ通知 防犯システム
  15. if (赤外線センサー.有効() && 赤外線センサー.検知()) { if (SMS通知.有効()) { SMS通知.実行(); }

    if (メッセージアプリ通知.有効()) { メッセージアプリ通知.実行(); } } else if (窓ガラス破損.有効() && 窓ガラス破損.検知()) { if (SMS通知.有効()) { SMS通知.実行(); } if (メッセージアプリ通知.有効()) { メッセージアプリ通知.実行(); …. 良くないロジックは、何を使うか を判断する分岐と実行するロ ジックが混在し、あみだくじ状態 になっています。 これは自動車に例えたら、自動 車を走らせながら自動車を組み 立てているのと同じこと。 現実ではあり得ない珍妙なこと ですが、ソフトウェアではできて しまいます。
  16. class AlarmDestinations { private final List<Alarmable> alarmDestinations; AlarmDestinations() { alarmDestinations

    = new ArrayList<>(); } void add(Alarmable alarmDestination) { alarmDestinations.add(alarmDestination); } void alarm() { alarmDestinations.forEach(Alarmable::alarm); } } 通知interfaceの ファーストクラスコレクション interface実装クラスが何であるか知 らないが、ただ実行するだけ 使う側 interface実装クラスのインスタンス を渡してリストに追加
  17. class AlarmDestinationsFactory { static AlarmDestinations create() { AlarmDestinations alarmDestinations =

    new AlarmDestinations(); if (SmsAlarm.isEnabled()) { alarmDestinations.add(new SmsAlarm()); } if (SmartphoneAppAlarm.isEnabled()) { alarmDestinations.add(new SmartphoneAppAlarm()); } return alarmDestinations; } } 作る側 どのinterface実装クラスを使うか判 断して組み込む。 このfactory内で必要なものを全て 組み上げる。
  18. public class TrespassDetectors { private final List<TrespassDetectable> detectors; TrespassDetectors() {

    detectors = new ArrayList<>(); } void add(TrespassDetectable detector) { detectors.add(detector); } boolean detect() { return detectors.stream().anyMatch(TrespassDetectable::detect); } } 侵入検知interfaceの ファーストクラスコレクション interface実装クラスが何であるか知 らないが、ただ実行するだけ 使う側 Factoryは通知用Factoryとほぼ 同じ構造だが、詳しくは後述 interface実装クラスのインスタンス を渡してリストに追加
  19. class TrespassDetectorsFactory { static TrespassDetectors create() { TrespassDetectors trespassDetectors =

    new TrespassDetectors(); if (InfraredSensor.isEnabled()) { trespassDetectors.add(new InfraredSensor()); } if (GlassDestructionSensor.isEnabled()) { trespassDetectors.add(new GlassDestructionSensor()); } if (MonitorCamera.isEnabled()) { PatternDetectors patternDetectors = PatternDetectorsFactory.create(); trespassDetectors.add(new MonitorCamera(patternDetectors)); } return trespassDetectors; } 作る側 Factory内で Factoryをcall どのinterface実装クラスを使うか判 断して組み込む。 このfactory内で必要なものを全て 組み上げる。
  20. class MonitorCamera implements TrespassDetectable { private final VideoRecorder videoRecorder; private

    final PatternDetectors patternDetectors; MonitorCamera(PatternDetectors patternDetectors) { videoRecorder = new VideoRecorder(); this.patternDetectors = patternDetectors; } public boolean detect() { return patternDetectors.detect(); } 監視カメラクラスは具体的にパターン検知 機能を持っているかは知らない。ただ検知 を実行するだけ。
  21. TrespassDetectors trespassDetectors = TrespassDetectorsFactory.create(); AlarmDestinations alarmDestinations = AlarmDestinationsFactory.create(); HomeSecurityService homeSecurityService

    = new HomeSecurityService( trespassDetectors, alarmDestinations); 通知と侵入検知の インスタンスは、 Factoryで作って…… 作ったインスタンスを 防犯サービスクラスに渡す だけ。これで完成品。
  22. class HomeSecurityService { private final TrespassDetectors trespassDetectors; private final AlarmDestinations

    alarmDestinations; HomeSecurityService( TrespassDetectors trespassDetectors, AlarmDestinations alarmDestinations) { this.trespassDetectors = trespassDetectors; this.alarmDestinations = alarmDestinations; } void execute() { if (trespassDetectors.detect()) { alarmDestinations.alarm(); } } 使う側 どんな侵入検知機能や通知機能 を持っているかは知らない。 侵入検知して通知するだけ。
  23. まとめ • システムは目的達成手段であり、その構成要素たるクラスやinterfaceも目的達成 手段であることを意識すること。 • interfaceは目的単位で抽象化すること。 • interface実装クラスは同一目的に対する達成手段のバリエーション。 • interfaceを用いる際は、「作る」と「使う」を分けること。

    • どのinterface実装クラスを用いるかの判断はFactoryやDIコンテナなど「作る側」 に持たせ、全てを組み込んだ完成品として生成すること。 • 「使う側」は完成品をただ実行するだけのシンプルな構造にすること。部品 (interface実装クラス)が何であるかいちいち気にしない構造にすること。 • interface設計した箇所はより高機能なものへ置換できる可能性を秘めている。 • interfaceは銀の弾丸ではない。基本的には手段のバリエーション、という観点で の設計を推奨。