Save 37% off PRO during our Black Friday Sale! »

複雑な条件と戦う

B8403d102456248570005ee7fb2ba0f7?s=47 philomagi
September 04, 2019

 複雑な条件と戦う

複雑な条件の組み合わせで
- テストが難しく
- 実装が肥大化し
- 変更が辛い
状態になったコードを改善する。

Specification Pattern/仕様パターン について、「実装的に嬉しいこと」にフォーカスして整理。

B8403d102456248570005ee7fb2ba0f7?s=128

philomagi

September 04, 2019
Tweet

Transcript

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

  2. 発表者 @Philomagi • 主にフロントエンド主体のWEB系エンジニア • ScalaとTypescriptとRubyが好き ◦ Rubyは最近、公私共に若干疎遠 • PHPは中々縁が切れない悪友

    ◦ 最近は、「然程悪いやつでもないな」と思い始めてる 2
  3. 概要 • 「複雑な条件」は辛い • 「条件」自体をオブジェクト化することで対処 • 思いっきりプログラマ向けです 3

  4. 「複雑な条件」の辛さ 4

  5. 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 }
  6. 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 }
  7. 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 } 組み合わせが複雑になる
  8. 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 } 組み合わせが複雑になる → 入力の組み合わせも増える
  9. 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 } 組み合わせが複雑になる → 入力の組み合わせも増える → 考えるべき事項が増える
  10. 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 } 組み合わせが複雑になる → 入力の組み合わせも増える → 考えるべき事項が増える → テストが難しくなる
  11. 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 }
  12. 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 }
  13. 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 } パターンが増える
  14. 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 } パターンが増える → 実装が増える
  15. 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 } パターンが増える → 実装が増える → またパターンが増える
  16. 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 } パターンが増える → 実装が増える → またパターンが増える → 実装が肥大化していく
  17. 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 } パターンが増える → 実装が増える → またパターンが増える → 実装が肥大化していく → 内容の把握が大変になる
  18. 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 } パターンが増える → 実装が増える → またパターンが増える → 実装が肥大化していく → 内容の把握が大変になる → 変更が辛くなる
  19. 19 「複雑な条件」で辛いこと • テストが難しくなる • 実装が肥大化しやすい • 変更が辛い

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

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

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

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

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

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

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

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

    Specification or(other: Specification): Specification } 条件オブジェクト同士を 組合せるためのメソッド
  28. 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 }
  29. 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 }) }
  30. 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 }) }
  31. 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
  32. 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
  33. 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 条件オブジェクトは互いに独立している
  34. 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)
  35. 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 条件オブジェクトは互いに独立している 条件オブジェクト毎に個別にテストできる 一つのテストで考えることが減るので、 テストが簡単になる
  36. 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
  37. 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
  38. 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 実装の追加はオブジェクトの組み立てのみ 具体的な条件はオブジェクト内部に隠蔽
  39. 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 実装の追加はオブジェクトの組み立てのみ 具体的な条件はオブジェクト内部に隠蔽 • 追加が最低限なので、肥大化しにくい • 簡単に変更できる
  40. 「条件」をオブジェクト化すると • テストが書きやすい • 実装が肥大化しにくい • 条件を変更しやすい => 「複雑な条件」でも対応しやすくなる まとめ

    40
  41. 元ネタ • DDD「仕様オブジェクト」 ◦ Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) •

    Specification Pattern ◦ https://www.martinfowler.com/apsupp/spec.pdf 41
  42. ご清聴ありがとうございました 42