インターフェース再入門 / Think Interface again

インターフェース再入門 / Think Interface again

PHPカンファレンス 関西2018で発表しました。
https://2018.kphpug.jp/session/sugiura_sota

046baac588d91fd78a85b189847a151d?s=128

Sota Sugiura

July 14, 2018
Tweet

Transcript

  1. インターフェース再⼊⾨ @sota1235 PHPカンファレンス関⻄ 2018 2018/7/14

  2. var_dump($me); • Sota Sugiura(きりん) • @sota1235 • Mercari, Inc. •

    将来の夢はJavaScript になることです
  3. インターフェース

  4. インターフェースの • よさを知る • パターンを知る • 作り⽅を知る

  5. Agenda 1章 インターフェースのメリット 2章 インターフェースの評価⽅法 3章 インターフェースの作り⽅

  6. 1章 インターフェースのメリット

  7. インターフェースを使うと… • いい感じに抽象化される! • いい感じに疎結合になる!

  8. インターフェースを使うと… • いい感じに抽象化される! • いい感じに疎結合になる!

  9. インターフェース (interface) はインタフェイ ス、インターフェイスとも書き、英語で界⾯や 接触⾯、中間⾯などといった意味を持ち、転じ てコンピュータと周辺機器の接続部分を表すよ うになった。さらに、ユーザーインターフェー スなどのように、⼈間と⾃動機械との間の複雑 な操作をする⼿順・規則との意味にも使われる。 https://ja.wikipedia.org/wiki/インターフェース

  10. ޅŮ ޅŮ ƺȉǕ✣ǫƽ✣Ǐ

  11. 何か = いろいろ

  12. ǧǓljȉ ದᾇ ǃ✣Dz✣ǟ ίʔυ ॻ͖͍ͨ

  13. Ǫnj໬ ǭȃdžǿǴ ểὐ ϐβ ৯΂͍ͨ

  14. DžǿǏ DžǿǏ ǷǓǙǟ σʔλ อଘͯ͠

  15. <?php class User { public function getName() { $rp =

    new Repository(); $name = $rp->getByName(); return $name; } } 3FQPTJUPSZ 6TFS HFU#Z/BNF
  16. <?php class User { public function getName() { $rp =

    new Repository(); $name = $rp->getByName(); return $name; } } 3FQPTJUPSZ 6TFS HFU#Z/BNF 中のロジックは 知らなくていい
  17. インターフェースとは • ⽬的を果たすために⾃分以外のものへ作⽤す るときの窓⼝ • 窓⼝の先は知らなくてもいい • 窓⼝を知っていれば⽬的が果たせる

  18. PHPのInterfaceって必要?

  19. Interfaceを使う意義 • 「裏側はどうでもいいのでこういうものを求 めます」をコードで表現できる • Interfaceを使うコード側の意図が読みやすく なる

  20. <?php class Database { public function getByName(string $name) { //

    Get data from database } } class User { /** @var Database */ protected $db; public function __construct(Database $db) { $this->db = $db; } public function getByName(string $name) { return $this->db->getByName($name); } } %BUBCBTF 6TFS HFU#Z/BNF
  21. <?php class Database { public function getByName(string $name) { //

    Get data from database } } class User { /** @var Database */ protected $db; public function __construct(Database $db) { $this->db = $db; } public function getByName(string $name) { return $this->db->getByName($name); } } • Userクラスが欲しい のはデータ • データの取得元はど こでもいい • にも関わらずコード ではDatabaseクラス に依存している
  22. <?php class Database { public function getByName(string $name) { //

    Get data from database } } class User { /** @var Database */ protected $db; public function __construct(Database $db) { $this->db = $db; } public function getByName(string $name) { return $this->db->getByName($name); } } • Userクラスが欲しい のはデータ • データの取得元はど こでもいい • にも関わらずコード ではDatabaseクラス に依存している 6TFSΫϥε͕ຊ౰ʹඞཁͳͷ͸ ʮHFU#Z/BNFΛ௨ͯ͡σʔλΛฦ͢ʯԿ͔
  23. <?php interface Repository { public function getByName(string $name); } class

    Database implements Repository { public function getByName(string $name) { // Get data from database } } • 「getByNameを通し てデータを得られる 何か」をInterfaceで 表現する • 各クラスはそれに従 い実装する
  24. <?php interface Repository { public function getByName(string $name); } class

    User { /** @var Repository */ protected $rp; public function __construct(Repository $rp) { $this->rp = $rp; } public function getByName(string $name) { return $this->rp->getByName($name); } } • Userクラスでは定め たInterfaceをtype hintする • 求めるInterfaceと 役割をコードで表現 できる
  25. 1章まとめ • インターフェースとは裏側で何が起きている か把握せずに作⽤することができる窓⼝ • PHPのInterfaceを使うことでインターフェー スに期待する動作をコードで表現できる

  26. 2章 インターフェースの評価⽅法

  27. よいインターフェースとは

  28. 1. 名前が役割を適切に⽰しているか 2. 最⼩か完全か 3. 単純か複雑か

  29. 1. 名前が役割を適切に⽰しているか 2. 最⼩か完全か 3. 単純か複雑か

  30. 名は体を表す • インターフェースの名前が役割を適切に表現 すべきである • どんなロジックを持つのか • 返り値は何なのか

  31. getLoginSession

  32. getLoginSession -PHJO4FTTJPOΛ औಘ͠·͢

  33. getData

  34. getData ঎඼৘ใΛ (FU͠·͢

  35. getData ঎඼৘ใΛ (FU͠·͢ • Dataって何のData?

  36. getData ঎඼৘ใΛ (FU͠·͢ • Dataって何のData? ⇒ 名前が曖昧で役割を表現できていない

  37. getCompletedTime

  38. getCompletedTime ׬ྃ࣌ؒΛ ೥݄೔Ͱฦ͠·͢

  39. getCompletedTime ׬ྃ࣌ؒΛ ೥݄೔Ͱฦ͠·͢ • 完了時間、何秒かまで欲しいんだけど…

  40. getCompletedTime ׬ྃ࣌ؒΛ ೥݄೔Ͱฦ͠·͢ • 完了時間、何秒かまで欲しいんだけど… ⇒ 期待と実装にギャップがある ⇒ 名前を実装に寄せる or

    実装と名前を直す
  41. 名前を⼤切に • インターフェースの名前から役割が連想でき るようにする • 名前で不⼗分な場合はコメント等で補完する • できれば⽂書化せずに済む名前を考えよう • 引数や返り値(PHP7)のtypehintも活⽤しよう

  42. 1. 名前が役割を適切に⽰しているか 2. 最⼩か完全か 3. 単純か複雑か

  43. 最⼩か完全か • 最⼩ • ⽬的を実現するために必要最⼩限なメソッド だけ揃っている • 完全 • ⽬的を実現するために必要なメソッドが全て

    揃っている
  44. Example • Fileを扱うInterfaceを考える • 以下の⽬的があるとする • ファイルを1⾏ずつ読み込みたい

  45. <?php interface File { public function open(string $filename); public function

    read(int $count); public function close(); } Example - 最⼩のInterface
  46. <?php interface File { public function open(string $filename); public function

    read(int $count); public function close(); } Example - 最⼩のInterface open, read, close関数を駆使すれば 1⾏ずつ読み込むことができる
  47. <?php interface File { public function open(string $filename); public function

    read(int $count); public function readLine(int $lineNum); public function close(); } Example - 完全なInterface
  48. <?php interface File { public function open(string $filename); public function

    read(int $count); public function readLine(int $lineNum); public function close(); } Example - 完全なInterface readLine関数で⼀⾏ずつ読むのは簡単!
  49. <?php interface File { public function open(string $filename); public function

    read(int $count); public function readLine(int $lineNum); public function close(); } Example - 完全なInterface readLine関数で⼀⾏ずつ読むのは簡単! ҰํͰ࣮૷ͱςετͷ௥Ճɺ ͦΕͷอकίετ͕ൃੜ͢Δ
  50. トレードオフ • 最⼩ • 実装がシンプルに保たれる • 使う側でコード量が増える可能性がある • 完全 •

    呼び出し側がシンプルになる • テストや実装の追加、保守が必要
  51. 1. 名前が役割を適切に⽰しているか 2. 最⼩か完全か 3. 単純か複雑か

  52. 単純か複雑か • 単純 • 主な機能のメソッドのみ提供される • 複雑 • より細かい操作を可能にするメソッドが提供 される

  53. Example • 画像を扱うInterfaceを考える • 様々な変更を加えるメソッドを⽣やす

  54. <?php interface Image { public function setWidth(int $width); public function

    setHeight(int $height); public function setName(string $name); } Example - 単純なInterface
  55. <?php interface Image { public function setWidth(int $width); public function

    setHeight(int $height); public function setName(string $name); } Example - 単純なInterface 縦と横のサイズ、名前だけ変更できる
  56. <?php interface Image { public function setWidth(int $width); public function

    setHeight(int $height); public function setName(string $name); public function setBrightness(int $level); public function setPixel(int $pixel); public function setEffect(string $effect); public function setExtension(string $ext); } Example - 複雑なInterface
  57. <?php interface Image { public function setWidth(int $width); public function

    setHeight(int $height); public function setName(string $name); public function setBrightness(int $level); public function setPixel(int $pixel); public function setEffect(string $effect); public function setExtension(string $ext); } Example - 複雑なInterface 画像に対して細やかな調整が可能!
  58. <?php interface Image { public function setWidth(int $width); public function

    setHeight(int $height); public function setName(string $name); public function setBrightness(int $level); public function setPixel(int $pixel); public function setEffect(string $effect); public function setExtension(string $ext); } Example - 複雑なInterface 画像に対して細やかな調整が可能! ҰํͰΠϯλʔϑΣʔεΛ ཧղ͢Δͷ͕೉͍͠
  59. トレードオフ • 単純 • メソッド数が少なく、理解が容易 • 細やかな変化に対応できない • 複雑 •

    細やかな操作が呼び出し側で可能 • インターフェースの理理解が難しい
  60. 2章まとめ • インターフェースの評価基準 • 役割を⽰す名前 • 最⼩/完全、単純/複雑のトレードオフ

  61. 3章 インターフェースの作り⽅

  62. インターフェース作りに挑戦 • ⼈を表現するInterfaceが欲しい • ⼈は名前を持つ • ⼈は役割を持つ • その⼈が特定の役割を持つか確かめる⼿段が 欲しい

  63. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function getRole(): string; } 作ってみた
  64. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function getRole(): string; } 作ってみた ⼈を表す名前に
  65. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function getRole(): string; } 作ってみた 名前を情報として持つ
  66. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function getRole(): string; } 作ってみた 役割を取得できるように
  67. インターフェース作りに挑戦 • ⼈を表現するInterfaceが欲しい • ✅⼈は名前を持つ • ✅⼈は役割を持つ • ✅その⼈が特定の役割を持つか確かめる⼿段 が欲しい

  68. Q. これがベストのInterface?

  69. A. わからない

  70. ベストなInterfaceとは • Interfaceに正解はない • これを導⼊すればよい、というテクニックもない • 実現したいことによって最善は変わる • ⾃分が何を作るのか熟考必要がある •

    コード設計と同じこと
  71. 提案したい設計指針 • 要件から考える • 保守性から考える • 未来予測から考える

  72. 要件から考える • 必要な機能を考える • そのInterfaceを使う側に⽴って考える

  73. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function getRole(): string; } Person Interface
  74. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function getRole(): string; } Person Interface
  75. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function getRole(): string; } Person Interface おさらい:実現したいこと 「その⼈が特定の役割を持つか確かめる⼿段が欲しい」
  76. <?php class Manager { public function assign(Person $person) { if

    ($person->getRole() === 'admin') { // } } } 使い⼿の気持ちになる
  77. <?php class Manager { public function assign(Person $person) { if

    ($person->getRole() === 'admin') { // } } } 使い⼿の気持ちになる getRole()を駆使しないと 「その⼈が特定の役割を持つか確かめる⼿段が欲しい」 が実現できない
  78. <?php class Manager { public function assign(Person $person) { if

    ($person->getRole() === 'admin') { // } } } 使い⼿の気持ちになる 最⼩/完全という観点で⾒ると最⼩と⾔えそうだが そもそも現時点でgetRole()を求められていない
  79. <?php class Manager { public function assign(Person $person) { if

    ($person->isRole('admin')) { // } } } 改良版 1つメソッドを呼ぶだけで要件を実現できた 名前も今回求める役割を表現できてる
  80. <?php class Manager { public function assign(Person $person) { if

    ($person->isAdmin()) { // } } } 改良版 最⼩のインターフェースから完全へ
  81. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function isRole(string $role): bool; public function isAdmin(): bool; } Person Interface
  82. 保守性から考える • 実装⼯数や運⽤を意識する • メソッドの数が多ければ多いほど保守性は下 がる

  83. <?php interface Person { public function getName(): string; public function

    setName(string $name); public function isRole(string $role): bool; public function isAdmin(): bool; } Person Interface これは必要…? 今回の要件には含まれていない
  84. 1つ1つに意味を与える • 経験則やベストプラクティスからメソッドを 定義しない • 全てのメソッドは意味を持つ • 「そのメソッドはなぜ必要ですか?」に答え られないメソッドは⾒直すべき

  85. <?php interface Person { public function getName(): string; // public

    function setName(string $name); public function isRole(string $role): bool; public function isAdmin(): bool; } Person Interface
  86. 未来予測から考える • 予測できる範囲の近い未来を考える • 確定された未来がないことを肝に刻んで考え る

  87. 例えば • 現在の仕様で存在する役割がadmin, sysadmin, generalの3種類だったらこんな Interfaceもあり

  88. <?php interface Person { public function getName(): string; public function

    isRole(string $role): bool; public function isAdmin(): bool; public function isSysAdmin(): bool; public function isGeneral(): bool; } Person Interface
  89. <?php interface Person { public function getName(): string; public function

    isRole(string $role): bool; public function isAdmin(): bool; public function isSysAdmin(): bool; public function isGeneral(): bool; } Person Interface 予測できる未来として「役割の種類が増える」ことが 考えられる
  90. <?php interface Person { public function getName(): string; public function

    isRole(string $role): bool; public function isAdmin(): bool; public function isSysAdmin(): bool; public function isGeneral(): bool; } Person Interface 仮に役割が増えるとすると、 Interfaceの完全さを保とうとすればするほど メソッドは増え、保守性は下がる
  91. 3章まとめ • 最適なインターフェースはその時々で変わる • 要件を理解し、未来予想や保守性、様々なト レードオフを考慮して設計していく

  92. 最後に

  93. 最⾼のインターフェースへの 道のりは⻑い • 最適解は時と場合で変わっていく • ⼤事なのは引き出しを多く持つこと • たくさんコードを書き、読むことで引き出し を増やしていきましょう •

    必要に応じて論理武装するとよい
  94. まとめ • インターフェースのメリットとは「裏側を知 らずに」⽬的を実現できること • 様々なパターンとトレードオフが存在する • 要件や様々な問題を知識を駆使してInterface を設計していくことが⼤事

  95. Thank you