Slide 1

Slide 1 text

分岐を低減する interface設計と発想の転換 Mar. 3, 2023 @ エンジニア文化祭 2023 READYFOR株式会社 ミノ駆動

Slide 2

Slide 2 text

みんなー 元気ー?

Slide 3

Slide 3 text

まずは元気にアイサツしてみよう

Slide 4

Slide 4 text

#Forkwell文化祭 設計なんもわからん

Slide 5

Slide 5 text

本資料のリンクは #Forkwell文化祭 でツイート済みです 資料後半が詳細かつ重厚なので お手元で資料を見ながらのご視聴を推奨します

Slide 6

Slide 6 text

こんにちは ミノ駆動です

Slide 7

Slide 7 text

ここで働いています

Slide 8

Slide 8 text

最近ニュースになったクラウドファンディング

Slide 9

Slide 9 text

職歴とか今やってる仕事とか 大手電機メーカーなどで十数年組込み系、 2019年にWeb系へ移籍、 2021年4月にREADYFORにジョイン アプリケーションアーキテクトとして、 レガシーシステムのリファクタリングや 拡張性向上設計など、 システム設計に従事 ミノ駆動 @MinoDriven

Slide 10

Slide 10 text

著作紹介 『良いコード/悪いコードで学ぶ設計入門』 技術的負債を作り込まない、 変更容易性の高い設計を学ぶ、 初級〜中級向け入門書。 新卒エンジニアに最適です。 輪読会も多数開催されております。 発売 10ヶ月で 8刷重版 発行部数 3万部 超え達成 3万部記念で 帯が付きました

Slide 11

Slide 11 text

ITエンジニア本大賞2023 大賞受賞!!

Slide 12

Slide 12 text

【本日のお話】 分岐低減に寄与するinterfaceの設計に必要な 考え方や発想の転換

Slide 13

Slide 13 text

分岐が複雑化して困っていませんか?

Slide 14

Slide 14 text

プログラミング言語には、抽象的に扱う仕組みとしてinterface(ま たはinterfaceに類似する仕組み)があります。 クラス クラス クラス interface interfaceを使うと分岐が劇的に低減し、 コードがシンプルになります。

Slide 15

Slide 15 text

でも……

Slide 16

Slide 16 text

interface上手く使えてます??

Slide 17

Slide 17 text

【架空事例】 防犯システムで考える interface設計

Slide 18

Slide 18 text

【架空事例】 ホームセキュリティシステムの開発 製品コンセプト: 家全体の防犯システムを 統括的に制御するアプリケーション 初期の仕様: ※あくまでinterface設計を論点とする架空事例です。このセキュリティシステム自体の細かな仕様や制御技術につ いてあれこれ言われても困ってしまいますので、論点と異なる事柄についてはご容赦願います。 不審者の侵入検知 赤外線センサー 侵入検知時の通知方法 家人のスマホへSMSで通知

Slide 19

Slide 19 text

その後…… 「もっといろんな仕組みに対応できるようにしてほしい」 という市場要望が上がってきました。 要望例: 窓ガラスの破損を検知できるようにしてほしい。 監視カメラに対応してほしい。 メッセージアプリでも通知を受け取れるようにしてほし い。

Slide 20

Slide 20 text

さらに、顧客によって仕組みのニーズがばらばらでした。 (ある顧客は「防犯カメラ」と「SMS通知」がいいと言ったり、また別 の顧客は「赤外線センサー」と「メッセージアプリ通知」の組合せ を!と言ってきたり) こうした各顧客の詳細なニーズに対応したシステムを、個別に オーダーメイドで開発するのは莫大な工数がかかり、非現実的で す。 従って、元のアプリケーションに仕様追加する形で顧客ニーズの 吸収を試みました。

Slide 21

Slide 21 text

if (赤外線センサー.有効() && 赤外線センサー.検知()) { if (SMS通知.有効()) { SMS通知.実行(); } if (メッセージアプリ通知.有効()) { メッセージアプリ通知.実行(); } } else if (窓ガラス破損.有効() && 窓ガラス破損.検知()) { if (SMS通知.有効()) { SMS通知.実行(); } if (メッセージアプリ通知.有効()) { メッセージアプリ通知.実行(); …. 左記のように分岐が複雑な作り になってしまいました。 その後さらに仕様追加されるに つれ、分岐は爆発的に複雑化し ていきました……

Slide 22

Slide 22 text

このような事例と 同じ経験はありませんか? ウッてなりませんでしたか??

Slide 23

Slide 23 text

一方、interfaceが分岐低減につながる、 となんとなく知ってはいるものの、 実際の開発で上手く活用できず、 結局既存ロジックに 分岐をネジ込んでしまっていませんか?

Slide 24

Slide 24 text

interfaceを上手く活用するには 大幅な発想の転換が必要です

Slide 25

Slide 25 text

interface設計が上達する ものの見方、考え方を解説します

Slide 26

Slide 26 text

interface設計の要点2本柱 要点1:目的単位で抽象化すること 要点2:「作る」と「使う」を分けること 本日の理解目標

Slide 27

Slide 27 text

要点1:目的単位で抽象化すること

Slide 28

Slide 28 text

システムは目的達成手段

Slide 29

Slide 29 text

システムは目的達成手段 移動手段の一例 二足歩行も 立派なシステム 「〜〜したい」という目的を叶えるための目的達成手段がシステムである 上記例は「目的地へ移動したい」という目的を叶えるための移動手段 システムには必ず目的がある

Slide 30

Slide 30 text

【目的】 商品を売買したい 【売買手段】 ECサイト 当然ソフトウェアも目的達成手段 【目的】 遊びたい 【娯楽手段】 ゲームソフト

Slide 31

Slide 31 text

【目的達成手段】 システム 【目的達成手段】 クラス 【目的達成手段】 クラス 【目的達成手段】 クラス システムは目的達成手段であるから その構成要素たるクラスもまた目的達成手段

Slide 32

Slide 32 text

問題領域と解決領域

Slide 33

Slide 33 text

目的地に移動したい 自動車 飛行機 電車 問題領域 解決領域 問題領域に対し、それを解決するさまざまな 解決領域が世の中にはある

Slide 34

Slide 34 text

目的の具体化(ツリー構造化)

Slide 35

Slide 35 text

目的 課題 対策

Slide 36

Slide 36 text

目的 課題 対策

Slide 37

Slide 37 text

● 目的が決まると課題が浮き彫りになる ● 課題に対処するための解決手段が決まる ● 目的が違うと課題も解決手段も違ってくる

Slide 38

Slide 38 text

【問題領域】 【解決領域】 【目的達成手段】 地図 【目的】 山登りしたい 【目的/課題】 遭難したくない 【目的/課題】 虫刺され怖い 【目的達成手段】 虫除けスプレー 【目的達成手段】 山登り 課題抽出 (目的の具体化) 手段策定

Slide 39

Slide 39 text

【問題領域】 目的/課題 目的/課題 目的/課題 目的/課題 目的/課題 【解決領域】 達成手段 達成手段 達成手段 達成手段 達成手段 目的/課題と達成手段は、 ツリー構造、かつ鏡合わせ の関係になる

Slide 40

Slide 40 text

目的達成手段とinterfaceの関係

Slide 41

Slide 41 text

【問題領域】 【解決領域】 自動車 電車 飛行機 手段の具体的な特徴や性質は異なる が、全て移動目的に対応する手段 【目的】 目的地に移動したい

Slide 42

Slide 42 text

【問題領域】 【解決領域】 【目的】 目的地に移動したい 自動車 電車 飛行機 << interface >> 移動手段 移動する() 目的達成手段の抽象化 同一目的に対する 達成手段のバリエーション

Slide 43

Slide 43 text

自動車 電車 飛行機 << interface >> 移動手段 移動する() ゴーヤ 船 どう考えても 移動目的には使えない 移動目的として 使用可能

Slide 44

Slide 44 text

防犯システムの設計に応用する

Slide 45

Slide 45 text

仕様1 赤外線センサーで侵入検知する。 仕様2 侵入検知したことを家人のスマホへSMS通知する。 要望1 メッセージアプリでも通知してほしい。 要望2 窓ガラスの破損を検知できるようにしてほしい。 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる ようにしてほしい。 要望4 顔で家人かどうか判別はできないものか。 要望5 動き方の特徴で家人かどうか判別できませんか。 要望6 物を壊すなど、異常な行動を検知できないものか。 初期の仕様: 追加要望:

Slide 46

Slide 46 text

仕様1 赤外線センサーで侵入検知する。 侵入検知目的 仕様2 侵入検知したことを家人のスマホへSMS通知する。 通知目的 要望1 メッセージアプリでも通知してほしい。 通知目的 要望2 窓ガラスの破損を検知できるようにしてほしい。 侵入検知目的 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる ようにしてほしい。 侵入検知目的 要望4 顔で家人かどうか判別はできないものか。 映像分析目的 要望5 動き方の特徴で家人かどうか判別できませんか。 映像分析目的 要望6 物を壊すなど、異常な行動を検知できないものか。 映像分析目的 初期の仕様: 追加要望:

Slide 47

Slide 47 text

【問題領域】 【解決領域】 【目的】 通知したい << class >> SMS通知 << class >> アプリ通知 << interface >> 通知手段 通知する() 【目的】 SMSで 通知したい 【目的】 アプリで 通知したい 通知目的で 抽象化可能

Slide 48

Slide 48 text

仕様1 赤外線センサーで侵入検知する。 侵入検知目的 仕様2 侵入検知したことを家人のスマホへSMS通知する。 通知目的 要望1 メッセージアプリでも通知してほしい。 通知目的 要望2 窓ガラスの破損を検知できるようにしてほしい。 侵入検知目的 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる ようにしてほしい。 侵入検知目的 要望4 顔で家人かどうか判別はできないものか。 映像分析目的 要望5 動き方の特徴で家人かどうか判別できませんか。 映像分析目的 要望6 物を壊すなど、異常な行動を検知できないものか。 映像分析目的 初期の仕様: 追加要望:

Slide 49

Slide 49 text

【問題領域】 【解決領域】 【目的】 侵入検知したい << class >> 赤外線 センサー << class >> ガラス破壊 センサー << interface >> 侵入検知手段 検知する() 【目的】 動きで 検知したい 【目的】 ガラス破損を 検知したい 検知目的で 抽象化可能 【目的】 映像で 検知したい << class >> 監視 カメラ ※ここでの「赤外線センサー」や「ガラス破壊センサー」は物理的 なデバイスではなく、各デバイスの制御用クラスをイメージして おります

Slide 50

Slide 50 text

仕様1 赤外線センサーで侵入検知する。 侵入検知目的 仕様2 侵入検知したことを家人のスマホへSMS通知する。 通知目的 要望1 メッセージアプリでも通知してほしい。 通知目的 要望2 窓ガラスの破損を検知できるようにしてほしい。 侵入検知目的 要望3 監視カメラに対応してほしい。もちろん録画して後から見れる ようにしてほしい。 侵入検知目的 要望4 顔で家人かどうか判別はできないものか。 映像分析目的 要望5 動き方の特徴で家人かどうか判別できませんか。 映像分析目的 要望6 物を壊すなど、異常な行動を検知できないものか。 映像分析目的 初期の仕様: 追加要望:

Slide 51

Slide 51 text

【目的】 映像を分析して 検知できるようにしたい 【目的】 顔で家人かどうかを 判別したい 【目的】 動き方の特徴で 家人かどうかを 判別したい 【目的】 異常行動を 検知したい 【目的】 映像で 検知したい 【目的】 映像を後から 見れるようにしてほしい

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

顔検出 歩行パターン 検知 異常行動 検知 監視カメラ 映像記録 << interface >> パターン検知手段 検知する() ガラス破壊 センサー 赤外線 センサー << interface >> 侵入検知手段 検知する() << interface >> 通知手段 通知する() SMS通知 アプリ通知 防犯システム

Slide 54

Slide 54 text

要点2:「作る」と「使う」を分けること

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

作る 使う 「作る」と「使う」を分離する 作って完成されたものを使う、という発想が重要

Slide 57

Slide 57 text

【作る】 ● どの部品(interface実装クラス)を使用するかの判断は、 FactoryやDIコンテナ(※ここでは「作る側」と呼称)に任せ る。 ● 部品選択と組み立て(interface実装クラスの選択判断とイン スタンス化)は作る側にカプセル化する。 【使う】 ● 使う側ではどの部品(interface実装クラス)が使われている かは一切気にもせず判断もしない。 ● 渡されたインスタンスをただ実行するだけに徹する。

Slide 58

Slide 58 text

全体構造 ※あくまでinterface設計を論点とするクラス図と疑似コード です。「イベント駆動にすべき」「もっと複雑な制御が必要なは ず」といった論点と異なる指摘についてはご容赦願います。

Slide 59

Slide 59 text

映像分析 侵入検知 通知 防犯システム

Slide 60

Slide 60 text

通知

Slide 61

Slide 61 text

使う側 作る側

Slide 62

Slide 62 text

通知手段 SMS通知 アプリ通知

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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内で必要なものを全て 組み上げる。

Slide 65

Slide 65 text

侵入検知

Slide 66

Slide 66 text

使う側 作る側 赤外線センサー ガラス破壊 センサー 監視カメラ

Slide 67

Slide 67 text

public class TrespassDetectors { private final List 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実装クラスのインスタンス を渡してリストに追加

Slide 68

Slide 68 text

映像分析

Slide 69

Slide 69 text

使う側 作る側 顔検出 歩行パターン検知 異常行動検知 パターン検知手段

Slide 70

Slide 70 text

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内で必要なものを全て 組み上げる。

Slide 71

Slide 71 text

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(); } 監視カメラクラスは具体的にパターン検知 機能を持っているかは知らない。ただ検知 を実行するだけ。

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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(); } } 使う側 どんな侵入検知機能や通知機能 を持っているかは知らない。 侵入検知して通知するだけ。

Slide 75

Slide 75 text

FactoryはDIコンテナでも良い(or 望ましい)

Slide 76

Slide 76 text

以上がinterface設計の要点です

Slide 77

Slide 77 text

【トピック】 目的のバリエーションと機能性

Slide 78

Slide 78 text

【疑問】 同一目的なのに なぜ手段にさまざまなバリエーションがあるのでしょう?

Slide 79

Slide 79 text

テクノロジーの本質は能力の拡大と縮小 同じ目的でも達成するための能力や効率が格段に違います 移 動 速 度

Slide 80

Slide 80 text

移 動 速 度 小 回 り 達成手段それぞれで性能(人間にとって の能力の拡大、縮小率)が異なる 我々は状況に応じて最適な手段を選択し て生活している ※他にも輸送量やコストなどさまざまな観点がありますが割愛

Slide 81

Slide 81 text

● interface設計可能な箇所は、機能性向上の可能性を示唆し ています。 ● 同一目的でも、より顧客にリーチする機能に置き換えられる 可能性を秘めています。 ● 仕様変更で頻繁にコード変更している箇所はありませんで しょうか。そこはモードやバリエーションを切り替えるための分 岐で複雑化していませんでしょうか。 ● そうした箇所を整理してinterfaceにすると、より高機能なもの へ素早く置き換えられるようになり、開発生産性が高まるで しょう。

Slide 82

Slide 82 text

注意点

Slide 83

Slide 83 text

● 条件分岐をなんでもかんでもinterfaceに置き換えないように 注意しましょう。interfaceは銀の弾丸ではありません。 ● 条件分岐はバリデーションの他、計算判断やアルゴリズム処 理にも用いられます。 ● interfaceは本資料で解説したように、基本的には目的達成手 段のバリエーション、という観点で用いるのが良いでしょう。

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

おまけ

Slide 86

Slide 86 text

interface設計やプラグインアーキテクチャに詳しい書籍 今回の防犯システムの例も本書の援用

Slide 87

Slide 87 text

抽象化の考え方に詳しい書籍

Slide 88

Slide 88 text

課題(目的)の具体化や深堀りに役立つ書籍

Slide 89

Slide 89 text

ご清聴ありがとうございました