$30 off During Our Annual Pro Sale. View Details »

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

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

    View Slide

  2. みんなー 元気ー?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. こんにちは ミノ駆動です

    View Slide

  7. ここで働いています

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. でも……

    View Slide

  16. interface上手く使えてます??

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. システムは目的達成手段

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. 問題領域と解決領域

    View Slide

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

    View Slide

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

    View Slide

  35. 目的 課題 対策

    View Slide

  36. 目的 課題 対策

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  40. 目的達成手段とinterfaceの関係

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  60. 通知

    View Slide

  61. 使う側
    作る側

    View Slide

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

    View Slide

  63. 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実装クラスのインスタンス
    を渡してリストに追加

    View Slide

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

    View Slide

  65. 侵入検知

    View Slide

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

    View Slide

  67. 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実装クラスのインスタンス
    を渡してリストに追加

    View Slide

  68. 映像分析

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  72. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  76. 以上がinterface設計の要点です

    View Slide

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

    View Slide

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

    View Slide

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




    View Slide








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

    View Slide

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

    View Slide

  82. 注意点

    View Slide

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

    View Slide

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

    View Slide

  85. おまけ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide