Slide 1

Slide 1 text

導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について 五十嵐 進士 / sji / sj-i / @sji_ch

Slide 2

Slide 2 text

自己紹介 @sji_ch SNS 上でのアイコンは GitHub が自動生成した奴

Slide 3

Slide 3 text

生まれも育ちも仙台

Slide 4

Slide 4 text

PHP カンファレンス仙台とかやった

Slide 5

Slide 5 text

ふつうのサラリーマン 株式会社インフィニットループ仙台支社所属 スマホゲーのサーバサイドプログラマ

Slide 6

Slide 6 text

二歳児の父 かわいい 絵本好き 最近わりと会話が成立する

Slide 7

Slide 7 text

WEB+DB PRESS の現 PHP 連載担当 昨年 6 月から WEB+DB PRESS の PHP 連載 だいたいわりと真面目な話をしてる

Slide 8

Slide 8 text

Agenda trait とは trait の性質 trait の使いどころ trait の将来

Slide 9

Slide 9 text

その前に

Slide 10

Slide 10 text

PHP 8.2 に日本人が乱数拡張入れてたよ!

Slide 11

Slide 11 text

twitter の TL に RFC の話と phpcon の話と両方同時にでてきた

Slide 12

Slide 12 text

そうだ締め切り駆動で PHP の RFC も投げよう??

Slide 13

Slide 13 text

よし Trait で定数定義できるようにしたろ!

Slide 14

Slide 14 text

議論フェーズに目立った反対もない、これは勝つる、投票開始や

Slide 15

Slide 15 text

ちょ待てよ

Slide 16

Slide 16 text

ちょ

Slide 17

Slide 17 text

投票が始まってから吹き上がる反対

Slide 18

Slide 18 text

なんやかんやあって結局 PHP 8.2 への取り込みはされました!

Slide 19

Slide 19 text

trait とは

Slide 20

Slide 20 text

trait ってこういうやつ 2012 年リリースの PHP 5.4 で導入 クラスへ追加するメンバーを切り出して定義 プロパティ、メソッド、定数(8.2 から)を定義可能 クラスや trait 、Enum から use use 先へコピペされたよう振る舞う 直接インスタンスを作れない 複数 trait を同時に use できる 型宣言に使えない trait T { // トレイトの定義 public string $property = self::class; public function method(): void { echo $this->property, PHP_EOL; } } class C { use T; // トレイトの利用 }

Slide 21

Slide 21 text

trait の来歴

Slide 22

Slide 22 text

もともと PHP 専用とかではない 2002 年に Nathanael Schärli らが ECOOP2002 向けに出した論文で提案 いくつかの言語でアイディアが取り入れられた https://www.cs.cmu.edu/~aldrich/courses/819/Scha03aTraits.pdf

Slide 23

Slide 23 text

単一継承と多重継承 単一継承: 一つの親クラスしか持てない 多重継承: 複数の親クラスを持てる それぞれ別の面倒くささがある

Slide 24

Slide 24 text

多重継承の反省からtrait が生まれた

Slide 25

Slide 25 text

ちなみに当初論文の案ではtrait は状態を持たなかった 多重継承は状態の衝突への対応が難しいので、あえて外されてた プロパティが定義できるのは PHP が独自に入れた変更 元論文では trait が abstract で実装クラス側へアクセッサ提供を要求する形を提案 それだとボイラープレートが増えるので辛いとはみんな思ってた アカデミアのほうでも Stateful Traits のような拡張が後追いで提案された https://link.springer.com/chapter/10.1007/978-3-540-71836-9_4

Slide 26

Slide 26 text

trait の性質

Slide 27

Slide 27 text

ソフトウェアの部品が満たすべき性質: 品質について trait はクラスの部品 クラスはプログラムの部品 プログラムは部品を組み合わせて作る 良いプログラムは良い部品の良い組み合わせ方で作られる 良いといっても基準は色々 合目的性と言い換えるとスッキリ? 良さは品質とも呼ばれる

Slide 28

Slide 28 text

品質とは バートランド・メイヤーさんの『オブジェクト指向入門 第2 版 原則・コンセプト』(訳:酒匂寛さん)は品質とはなんぞ やで始まる 品質: 外的要因 + 内的要因 外的要因: 性能とか使いやすさとか、ユーザが認識する部分 内的要因: 外的要因の達成に必要な専門家だけが見る部分 達成すべきは目標は外的要因

Slide 29

Slide 29 text

品質の外的要因 正確さ 頑丈さ 拡張性 再利用性 互換性 効率性 可搬性 使いやすさ 機能性 適時性 そのほか(実証性、統合性、修復性、経済性)

Slide 30

Slide 30 text

品質要因のトレードオフ 機能を増やそうとしたらお金がかかるとか バランスをとる必要がある

Slide 31

Slide 31 text

特に気にする部分 信頼性 正確さ 頑丈さ モジュール性 拡張性 再利用性

Slide 32

Slide 32 text

正確さ 仕様で定義されているとおりに仕事を実行するソフトウェア製品の能力 ちゃんと動かないと全部が台無し 仕様を厳密に記述するというのがまず難しい 仕様への合致度を検証する方法も必要 小分けに関心を分離して「この範囲では正しい」という保証を重ねる テストとか型とかアサーションとか

Slide 33

Slide 33 text

頑丈さ 異常な条件に対して適切に対応するソフトウェアシステムの能力 仕様に書かれてないからって鼻から悪魔が出ていいわけがない エラー吐いてさっさと行儀よく終了してくれるほうが嬉しかったり

Slide 34

Slide 34 text

拡張性 仕様の変更に対するソフトウェア製品の適応のしやすさ 自然のなりゆきでは規模が大きくなるほど修正の影響を見積もりづらくなる より設計を単純に より構成する部品それぞれの独立性を高く

Slide 35

Slide 35 text

再利用性 多数多様なアプリケーションの構築に使うことのできる、ソフトウェア要素の能力 コードが減ると他の品質にコストを使えるようにもなる

Slide 36

Slide 36 text

5 つの基準、5 つの規則、5 つの原則 『オブジェクト指向入門』の重点はモジュール性(拡張性と再利用性) モジュール性についての 5 * 3 の 15 の確認事項

Slide 37

Slide 37 text

5 つの基準(criteria ) 部品の設計手法に求められるもの 分解しやすさ 組み合わせやすさ 分かりやすさ 連続性 保護性

Slide 38

Slide 38 text

5 つの規則(rule ) 5 つの基準から導き出されるもの 部品の良さを保証するために守らねばならないルール 問題領域の直接的な写像であること 少ないインタフェース 小さなインタフェース 明示的なインタフェース 情報隠蔽

Slide 39

Slide 39 text

5 つの原則(principle ) 5 つの基準と 5 つの規則から導き出されるもの ソフトウェア構築にあたって守るべき原則 言語としてのモジュール単位の原則 自己文書化の原則 統一形式の原則 開放/ 閉鎖の原則 単一責任選択の原則

Slide 40

Slide 40 text

trait の性質をあてはめてみる

Slide 41

Slide 41 text

5 つの基準から見る trait 分解しやすさ: ○ 組み合わせやすさ: ○ 分かりやすさ: ☓ trait は利用クラスの中でメンバの名前空間を共有 自身の中にスコープの閉じたメンバを持てない プロパティは名前衝突時に定義がマージ 自身の定義したプロパティを操作するだけで他部品の不変 条件が壊れる可能性がある 連続性: ○ 保護性: △ trait HeightModifiable { // 身長操作 trait private int $value; public function modifyHeight(int $value): void { $this->value += $value } } trait WeightModifiable { // 体重操作 trait private int $value; public function modifyWeight(int $value): void { $this->value += $value } } class C { // わかりづらいことが起きる! use HeightModifiable, WeightModifiable; }

Slide 42

Slide 42 text

5 つの規則から見る trait 問題領域の直接的な写像: ○ 少ないインタフェース: △ 小さいインタフェース: ☓ 利用クラス内で全てのメンバへのアクセスが全開放 同居する他 trait のメンバにも自由にアクセス 明示的なインタフェース: ☓ 利用クラス側の private メンバへのアクセスさえ断りなく可能 プロパティの名前衝突のマージに問題がある場合も気づけない 情報隠蔽: ☓ trait local なメンバを定義できない private / protected / public は利用クラス側の可視性としてコピペ展開される

Slide 43

Slide 43 text

5 つの原則と trait

Slide 44

Slide 44 text

5 つの原則と trait: 言語としてのモジュール単位の原則: ○ 部品は使用される言語の構文単位として表されなければならないという原則 trait はまさにそれそのもの

Slide 45

Slide 45 text

5 つの原則と trait: 自己文書化の原則: ☓ 部品についてのすべての情報を部品の一部として持つようにするという原則 かなりクラスに近い表現力はある PHP 8.2 からは定数定義もできる 「特定の interface を実装するために使われるもの」をコメントでしか書けない 信頼性高く書けるクラスより弱い

Slide 46

Slide 46 text

5 つの原則と trait: 統一形式アクセスの原則: △ プロパティの値を読み書きする時もメソッドを呼ぶ時も同じ表記を使うという原則 できないが、クラスもできないのでクラスより悪くはない 将来クラスで可能になれば trait でも可能になる internals で Property Accessors の議論を進めている人もいる

Slide 47

Slide 47 text

5 つの原則と trait: 開放/ 閉鎖の原則: ☓ 既存の部品への修正をせず部品を拡張できるという原則 abstract メソッドで trait 中心の部品構成でも一応可能 interface 側と trait 側で同じシグネチャを記述するようなこ とが起きがち 特定の interface を実装していることの要求をできるクラス とくらべるとやや弱い trait T1 { abstract public function f1(): void; public function f2(): void { f1(); } } trait T2 { use T1; public function f1(): void { echo 'T2';} } trait T3 { use T1; public function f1(): void { echo 'T3';} }

Slide 48

Slide 48 text

5 つの原則と trait: 単一責任選択の原則: ☓ 選択肢を提供する際、システム内の 1 つの部品だけがその選択肢のすべてを把握すべきという原則 言語的にサポートされているとは言い難い どの trait を使うかが利用側の各クラスなどへ静的に use で分散して埋め込まれる use 先を分岐するような仕組みが trait にはない

Slide 49

Slide 49 text

trait の性質まとめ クラス(の継承)より良いところ 部品を小分けにして組み合わせられる ただし DI / 合成が同等の能力 クラス(の継承)より悪いところ 意図しない部品間の干渉を防ぐ仕組みが弱い interface が付けられない

Slide 50

Slide 50 text

trait の使いどころ

Slide 51

Slide 51 text

その前に前提として DI (合成)で済むならそっちのほうがよい DTO っぽいのにちょっとデータ取得メソッド生やしたいとか には DI より向く 他に頼れるやつがいない時に使う最終兵器 // これより class C1 { use FunctionalityTrait1; use FunctionalityTrait2; } // クラスの DI にするほうが楽 class C2 { public function __construct( private FunctionalityClass1 $functionality1, private FunctionalityClass2 $functionality2, ) { } }

Slide 52

Slide 52 text

インタフェースのデフォルト実装 リッチインタフェースを利用者が扱いやすい形で採用可能 利用コードへ影響を与えずにインタフェースをあとから拡張 可能 PSR-3 の LoggerAwareInterface と LoggerAwareTrait 、 LoggerInterface と LoggerTrait など interface HogeInterface { public function method1(): void; } trait HogeDefaultImplementation { public function method1(): void { echo 'hoge'; } } class Hoge implements HogeInterface { use HogeDefaultImplementation; }

Slide 53

Slide 53 text

インタフェースとあわせての実質的な多重継承の実現 trait は型を伴わない実装 interface は実装を伴わない型 2 つあわせて(だいたい)多重継承 // 単一継承だとこういうのを小分けにできない // trait なら小分けに分割できる class P { public function common_function(): void {} // A 、 B にのみ必要な機能が C 、 D にも導入 public function ab_function(): void {} // C 、 D にのみ必要な機能が A 、 B にも導入 public function cd_function(): void {} } class A extends P {} class B extends P {} class C extends P {} class D extends P {}

Slide 54

Slide 54 text

クラスの分割実装 機械生成コードに手書きのコードを足したい場合などに嬉し い 直接修正するとスキーマ変更時の再生成で上書きされる ORM とか IDL からのコード生成とかへデータアクセス用の 処理をちょっと足したいとか // 機械生成コードをクラス側に置く構成も可 trait MachineGeneratedCodes { // 中略(機械生成されたコード) } // ↓ 別ファイルで定義 // 手書きコードをトレイト側に置く構成も可 class HandWrittenCodes { use MachineGeneratedCodes; // 中略(手書きのコード) }

Slide 55

Slide 55 text

類似機能を持つ他言語からのコード移植 Java や Kotlin の interface のメソッドデフォルト実装 C++ や Python の多重継承 Scala や Rust の trait C# の partial

Slide 56

Slide 56 text

偶然同じ機能を持つクラスの実装の共通化 複数のクラスへ同じ機能を与えたい が、同じ型を与えたいわけではない Laravel の Macroable など コードのユーザに複数クラスの統一インタフェースを提供したいだけの時

Slide 57

Slide 57 text

trait の将来 たぶんなくなることはない アカンけど捨てるわけにもいかんよね、は trait 定数の RFC で再確認できた メンバの可視性を縛る方法とか interface と紐付ける機能とかほしい

Slide 58

Slide 58 text

おしまい

Slide 59

Slide 59 text

- 宣伝 - WEB+DB PRESS vol.130 の PHP 連載でも trait の扱いについて書いてます! このトークと被る部分もありつつ、切り口は違い、文章としてまとめてあります 興味があればぜひ https://gihyo.jp/magazine/wdpress/archive/2022/vol130