Slide 1

Slide 1 text

「複雑な条件」と戦う 「条件のオブジェクト化」による抽象化の試み 2019/09/04@残暑に負けるな!自由研究 LT大会 #engineers_lt 1

Slide 2

Slide 2 text

発表者 @Philomagi ● 主にフロントエンド主体のWEB系エンジニア ● ScalaとTypescriptとRubyが好き ○ Rubyは最近、公私共に若干疎遠 ● PHPは中々縁が切れない悪友 ○ 最近は、「然程悪いやつでもないな」と思い始めてる 2

Slide 3

Slide 3 text

概要 ● 「複雑な条件」は辛い ● 「条件」自体をオブジェクト化することで対処 ● 思いっきりプログラマ向けです 3

Slide 4

Slide 4 text

「複雑な条件」の辛さ 4

Slide 5

Slide 5 text

5 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga }

Slide 6

Slide 6 text

6 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga }

Slide 7

Slide 7 text

7 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } 組み合わせが複雑になる

Slide 8

Slide 8 text

8 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } 組み合わせが複雑になる → 入力の組み合わせも増える

Slide 9

Slide 9 text

9 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } 組み合わせが複雑になる → 入力の組み合わせも増える → 考えるべき事項が増える

Slide 10

Slide 10 text

10 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } 組み合わせが複雑になる → 入力の組み合わせも増える → 考えるべき事項が増える → テストが難しくなる

Slide 11

Slide 11 text

11 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz }

Slide 12

Slide 12 text

12 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz }

Slide 13

Slide 13 text

13 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } パターンが増える

Slide 14

Slide 14 text

14 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } パターンが増える → 実装が増える

Slide 15

Slide 15 text

15 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } パターンが増える → 実装が増える → またパターンが増える

Slide 16

Slide 16 text

16 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } パターンが増える → 実装が増える → またパターンが増える → 実装が肥大化していく

Slide 17

Slide 17 text

17 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } パターンが増える → 実装が増える → またパターンが増える → 実装が肥大化していく → 内容の把握が大変になる

Slide 18

Slide 18 text

18 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } パターンが増える → 実装が増える → またパターンが増える → 実装が肥大化していく → 内容の把握が大変になる → 変更が辛くなる

Slide 19

Slide 19 text

19 「複雑な条件」で辛いこと ● テストが難しくなる ● 実装が肥大化しやすい ● 変更が辛い

Slide 20

Slide 20 text

どうやって解消するか? 20

Slide 21

Slide 21 text

「条件」のオブジェクト化 21

Slide 22

Slide 22 text

一つ一つの「条件」を 小さなオブジェクトとして実装する 「条件をオブジェクト化する」とは? 22 条件A 条件C 条件D 条件B

Slide 23

Slide 23 text

小さな「条件オブジェクト」を組み合わせて 「複雑な条件」を実装する 「条件をオブジェクト化する」とは? 23 条件A 条件C 条件D 条件B AND OR AND

Slide 24

Slide 24 text

コードだとどうなるの? 24

Slide 25

Slide 25 text

25 1. 「条件オブジェクト」のIFを定義 interface Specification { isSatisfiedBy(param): boolean and(other: Specification): Specification or(other: Specification): Specification }

Slide 26

Slide 26 text

26 1. 「条件オブジェクト」のIFを定義 interface Specification { isSatisfiedBy(param): boolean and(other: Specification): Specification or(other: Specification): Specification } 条件を満たしているかどうか判 定するメソッド

Slide 27

Slide 27 text

27 1. 「条件オブジェクト」のIFを定義 interface Specification { isSatisfiedBy(param): boolean and(other: Specification): Specification or(other: Specification): Specification } 条件オブジェクト同士を 組合せるためのメソッド

Slide 28

Slide 28 text

28 2. 具体的な「条件オブジェクト」を実装 class HogeSpecification implements Specification { isSatisfiedBy(param): boolean { return param.A && !param.B && param.C > 0 } // and/or の実装例は↓で // https://gist.github.com/tooppoo/2769cb3e842589d4b6c9392e3778408f }

Slide 29

Slide 29 text

29 3. 「複雑な条件」を組み立てる function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification().and(new FugaSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) }

Slide 30

Slide 30 text

30 3. 「複雑な条件」を組み立てる function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification().and(new FugaSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) }

Slide 31

Slide 31 text

31 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After

Slide 32

Slide 32 text

32 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After

Slide 33

Slide 33 text

33 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After 条件オブジェクトは互いに独立している

Slide 34

Slide 34 text

34 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After 条件オブジェクトは互いに独立している 条件オブジェクト毎に個別にテストできる const spec = new HogeSpecification() expect(spec.isSatisfiedBy(param)).toBe(true)

Slide 35

Slide 35 text

35 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D return hoge && fuga } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After 条件オブジェクトは互いに独立している 条件オブジェクト毎に個別にテストできる 一つのテストで考えることが減るので、 テストが簡単になる

Slide 36

Slide 36 text

36 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) .or(new PiyoSpecification()) .or(new FizzSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After

Slide 37

Slide 37 text

37 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) .or(new PiyoSpecification()) .or(new FizzSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After

Slide 38

Slide 38 text

38 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) .or(new PiyoSpecification()) .or(new FizzSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After 実装の追加はオブジェクトの組み立てのみ 具体的な条件はオブジェクト内部に隠蔽

Slide 39

Slide 39 text

39 function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const hoge = A && !B && C > 0 const fuga = (A || B) || C < D const piyo = B && C > 0 && D < 0 const fizz = (A && C > 0) || (!B && D >= C) return hoge && fuga || piyo || fizz } function isXxx( A: boolean, B: boolean, C: number, D: number ): boolean { const spec = new HogeSpecification() .and(new FugaSpecification()) .or(new PiyoSpecification()) .or(new FizzSpecification()) return spec.isSatisfiedBy({ A, B, C, D }) } // Before // After 実装の追加はオブジェクトの組み立てのみ 具体的な条件はオブジェクト内部に隠蔽 ● 追加が最低限なので、肥大化しにくい ● 簡単に変更できる

Slide 40

Slide 40 text

「条件」をオブジェクト化すると ● テストが書きやすい ● 実装が肥大化しにくい ● 条件を変更しやすい => 「複雑な条件」でも対応しやすくなる まとめ 40

Slide 41

Slide 41 text

元ネタ ● DDD「仕様オブジェクト」 ○ Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) ● Specification Pattern ○ https://www.martinfowler.com/apsupp/spec.pdf 41

Slide 42

Slide 42 text

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