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

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

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

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

Sota Sugiura

July 14, 2018
Tweet

More Decks by Sota Sugiura

Other Decks in Technology

Transcript

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

    View Slide

  2. var_dump($me);
    • Sota Sugiura(きりん)
    • @sota1235
    • Mercari, Inc.
    • 将来の夢はJavaScript
    になることです

    View Slide

  3. インターフェース

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. 何か = いろいろ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. class User
    {
    public function getName()
    {
    $rp = new Repository();
    $name = $rp->getByName();
    return $name;
    }
    }
    3FQPTJUPSZ
    6TFS
    HFU#Z/BNF
    中のロジックは
    知らなくていい

    View Slide

  17. インターフェースとは
    • ⽬的を果たすために⾃分以外のものへ作⽤す
    るときの窓⼝
    • 窓⼝の先は知らなくてもいい
    • 窓⼝を知っていれば⽬的が果たせる

    View Slide

  18. PHPのInterfaceって必要?

    View Slide

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

    View Slide

  20. 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

    View Slide

  21. 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クラス
    に依存している

    View Slide

  22. 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Λ௨ͯ͡σʔλΛฦ͢ʯԿ͔

    View Slide

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

    View Slide

  24. 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と
    役割をコードで表現
    できる

    View Slide

  25. 1章まとめ
    • インターフェースとは裏側で何が起きている
    か把握せずに作⽤することができる窓⼝
    • PHPのInterfaceを使うことでインターフェー
    スに期待する動作をコードで表現できる

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. getLoginSession

    View Slide

  32. getLoginSession
    -PHJO4FTTJPOΛ
    औಘ͠·͢

    View Slide

  33. getData

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. getCompletedTime

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  49. interface File
    {
    public function open(string $filename);
    public function read(int $count);
    public function readLine(int $lineNum);
    public function close();
    }
    Example - 完全なInterface
    readLine関数で⼀⾏ずつ読むのは簡単!
    ҰํͰ࣮૷ͱςετͷ௥Ճɺ
    ͦΕͷอकίετ͕ൃੜ͢Δ

    View Slide

  50. トレードオフ
    • 最⼩

    実装がシンプルに保たれる

    使う側でコード量が増える可能性がある
    • 完全

    呼び出し側がシンプルになる

    テストや実装の追加、保守が必要

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. 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

    View Slide

  57. 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
    画像に対して細やかな調整が可能!

    View Slide

  58. 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
    画像に対して細やかな調整が可能!
    ҰํͰΠϯλʔϑΣʔεΛ
    ཧղ͢Δͷ͕೉͍͠

    View Slide

  59. トレードオフ
    • 単純

    メソッド数が少なく、理解が容易

    細やかな変化に対応できない
    • 複雑

    細やかな操作が呼び出し側で可能
    • インターフェースの理理解が難しい

    View Slide

  60. 2章まとめ
    • インターフェースの評価基準
    • 役割を⽰す名前
    • 最⼩/完全、単純/複雑のトレードオフ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  66. interface Person
    {
    public function getName(): string;
    public function setName(string $name);
    public function getRole(): string;
    }
    作ってみた
    役割を取得できるように

    View Slide

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

    View Slide

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

    View Slide

  69. A. わからない

    View Slide

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

    View Slide

  71. 提案したい設計指針
    • 要件から考える
    • 保守性から考える
    • 未来予測から考える

    View Slide

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

    View Slide

  73. interface Person
    {
    public function getName(): string;
    public function setName(string $name);
    public function getRole(): string;
    }
    Person Interface

    View Slide

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

    View Slide

  75. interface Person
    {
    public function getName(): string;
    public function setName(string $name);
    public function getRole(): string;
    }
    Person Interface
    おさらい:実現したいこと
    「その⼈が特定の役割を持つか確かめる⼿段が欲しい」

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  80. class Manager
    {
    public function assign(Person $person)
    {
    if ($person->isAdmin()) {
    //
    }
    }
    }
    改良版
    最⼩のインターフェースから完全へ

    View Slide

  81. interface Person
    {
    public function getName(): string;
    public function setName(string $name);
    public function isRole(string $role): bool;
    public function isAdmin(): bool;
    }
    Person Interface

    View Slide

  82. 保守性から考える
    • 実装⼯数や運⽤を意識する
    • メソッドの数が多ければ多いほど保守性は下
    がる

    View Slide

  83. interface Person
    {
    public function getName(): string;
    public function setName(string $name);
    public function isRole(string $role): bool;
    public function isAdmin(): bool;
    }
    Person Interface
    これは必要…?
    今回の要件には含まれていない

    View Slide

  84. 1つ1つに意味を与える
    • 経験則やベストプラクティスからメソッドを
    定義しない
    • 全てのメソッドは意味を持つ
    • 「そのメソッドはなぜ必要ですか?」に答え
    られないメソッドは⾒直すべき

    View Slide

  85. interface Person
    {
    public function getName(): string;
    // public function setName(string $name);
    public function isRole(string $role): bool;
    public function isAdmin(): bool;
    }
    Person Interface

    View Slide

  86. 未来予測から考える
    • 予測できる範囲の近い未来を考える
    • 確定された未来がないことを肝に刻んで考え

    View Slide

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

    View Slide

  88. 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

    View Slide

  89. 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
    予測できる未来として「役割の種類が増える」ことが
    考えられる

    View Slide

  90. 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の完全さを保とうとすればするほど
    メソッドは増え、保守性は下がる

    View Slide

  91. 3章まとめ
    • 最適なインターフェースはその時々で変わる
    • 要件を理解し、未来予想や保守性、様々なト
    レードオフを考慮して設計していく

    View Slide

  92. 最後に

    View Slide

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

    View Slide

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

    View Slide

  95. Thank you

    View Slide