Slide 1

Slide 1 text

「”誤った使い方をすることが 困難”な設計」 taniguhey PHPカンファレンス小田原2025 で 良いコードの基礎を固めよう

Slide 2

Slide 2 text

自己紹介 ■ taniguhey / たにぐち ■ PHP 5年 / Java 1年 ■ 所属:NE株式会社 - ECの運営支援ツール開発 - BtoBのマーケットプレイス開発 ■ 特技:オタク特有の早口 ■ カンファレンス現地初参加・初登壇 X @TanigUhey

Slide 3

Slide 3 text

もくじ ● はじめに ● サンプルコードを使ったテクニック紹介 ○ 制約をクラス内部に持たせる ○ 設計者の意図をコードで伝える ● 設計を実践するにあたって ● まとめ

Slide 4

Slide 4 text

もくじ ● はじめに ● サンプルコードを使ったテクニック紹介 ○ 制約をクラス内部に持たせる ○ 設計者の意図をコードで伝える ● 設計を実践するにあたって ● まとめ

Slide 5

Slide 5 text

はじめに ”誤った使い方をすることが困難”とは

Slide 6

Slide 6 text

”誤った使い方をすることが困難”とは 53. 正しい使い方を簡単に、誤った使い方を困難に 要約すると ソフトウェア開発にはインターフェースを決める タイミングが必ずある。 👎 悪いインターフェースはストレスやミスが増える 👍 良いインターフェースは生産性が上がる https://プログラマが知るべき 97のこと.com/エッセイ/正しい使い方を簡単に、誤った使い方を困難に ● 正しく使用する方が操作ミスをするより簡単 ● 誤った使い方をすることが困難

Slide 7

Slide 7 text

「”誤った使い方をすることが困難”な設計」によって... ストレスやミスを減らす ● バグを発生させにくくする ● チームメンバーの誰でも扱いやすい ● 時間が経っても意図を理解しやすい 👎 悪いインターフェース 👍 良いインターフェース

Slide 8

Slide 8 text

この発表の前提や対象 ● この発表で扱うインターフェース ○ 異なる要素同士をつなぐ契約や約束事 ⭕ 自作クラスのメソッドやコンストラクタの引数・戻り値 ❌ ユーザーインターフェース・APIインターフェース ❌ PHPの言語機能としてのInterface

Slide 9

Slide 9 text

この発表の前提や対象 設計する人 あなた ▼ チーム内の開発者 ▼ 使う人 未来のあなた ▼

Slide 10

Slide 10 text

つまりこの発表では あなた が機能を開発する時に使える バグを起こしづらく、チームメンバーに伝わりやすい設計 を メソッドの作り方 ~ クラスの作り方 くらいの話題で テクニックを紹介します。

Slide 11

Slide 11 text

それでは本編始まるのだ

Slide 12

Slide 12 text

もくじ ● はじめに ● サンプルコードを使ったテクニック紹介 ○ 制約をクラス内部に持たせる ○ 設計者の意図をコードで伝える ● 設計を実践するにあたって ● まとめ

Slide 13

Slide 13 text

サンプルコードの題材 ● ECサービスのWebアプリケーション(Amaz◯n・メ◯カリ) ○ 商品 ■ 商品名(string:1~20文字) ■ 価格(int:0~PHP_INT_MAX) ○ 商品を作成できる ○ 作成した商品は価格を変更できる ○ 商品を購入する時、特定の割引率によって割引される

Slide 14

Slide 14 text

サンプルコードの題材 ● ECサービスのWebアプリケーション(Amaz◯n・メ◯カリ) ○ 商品 ■ 商品名(string:1~20文字) ■ 価格(int:0~PHP_INT_MAX) ○ 商品を作成できる ○ 作成した商品は価格を変更できる ○ 商品を購入する時、特定の割引率によって割引される

Slide 15

Slide 15 text

商品作成処理 ありがちな悪い例

Slide 16

Slide 16 text

商品モデル 商品作成処理 商品作成処理 ありがちな悪い例

Slide 17

Slide 17 text

商品作成処理 ● 何文字のnameが有効か示されていない ● priceがマイナスかをチェックしていない 👉 誤りの定義がない ありがちな悪い例

Slide 18

Slide 18 text

バリデーションを追加

Slide 19

Slide 19 text

バリデーションを追加 newする前にバリデーション 👍 めでたしめでたし ではない

Slide 20

Slide 20 text

誤った使い方を探す 😈誤った使い方 バリデーションを呼び出さずに商品 を新規作成する createProductを使ってくださいと伝 えて回るのだろうか new Productするのは ここだけだろうか

Slide 21

Slide 21 text

テクニック1 制約をクラス内部に持たせる

Slide 22

Slide 22 text

制約をクラス内部に持たせる 不自然な値でインスタンス化できないようにコンストラクタを作る 変更においても不完全な状態にならないようにする 実装の中で不完全なインスタンスが存在しないように 作る

Slide 23

Slide 23 text

制約をクラス内部に持たせる 不自然な値でインスタンス化できないようにコンストラクタを作る 変更においても不完全な状態にならないようにする 実装の中で不完全なインスタンスが存在しないように 作る

Slide 24

Slide 24 text

制約をコンストラクタに移す

Slide 25

Slide 25 text

コンストラクタに バリデーションを移動 👍 いつどこでnew Productしても 不完全な商品は作れない 制約をコンストラクタに移す

Slide 26

Slide 26 text

サンプルコードの題材(再掲) ● ECサービスのWebアプリケーション(Amaz◯n・メ◯カリ) ○ 商品 ■ 商品名(string:1~20文字) ■ 価格(int:0~PHP_INT_MAX) ○ ユーザーは商品を作成できる ○ 作成した商品は価格を変更できる ○ 商品を購入する時、特定の割引率によって割引される

Slide 27

Slide 27 text

価格の変更処理 ありがちな悪い例

Slide 28

Slide 28 text

😈誤った使い方 価格のバリデーションを 呼び出さずに価格を変更する 価格の変更処理 ありがちな悪い例 変更したい価格がマイナスでなければ 商品を更新して保存

Slide 29

Slide 29 text

制約をクラス内部に持たせる(再掲) 不自然な値でインスタンス化できないようにコンストラクタを作る 変更においても不完全な状態にならないようにする 実装の中で不完全なインスタンスが存在しないように 作る

Slide 30

Slide 30 text

変更処理にも制約を書く

Slide 31

Slide 31 text

クラス内部に持たせる 外部から変更できないように privateにして 変更処理にも制約を書く

Slide 32

Slide 32 text

👍 いつ変更しても 不完全な商品にならない 変更処理にも制約を書く 途中で変更しても価格は マイナスにできない

Slide 33

Slide 33 text

コンストラクタ 変更 ここまでのまとめ 不完全なインスタンスは作らせない

Slide 34

Slide 34 text

データ バリデーション 同じクラス内に閉じ込める ポイント 誤った使い方をする方が難しい ここまでのまとめ

Slide 35

Slide 35 text

Productクラスの問題点 メソッドの中身を読まないとどんな $price を入れてよいかわからない ❌ そもそも何がOKで何がNGとしている かの意図が伝わりづらい

Slide 36

Slide 36 text

Productクラスの問題点 判定処理が重複している ❌ そもそも何がOKで何がNGとしている かの意図が伝わりづらい ❌ 0円もNGにしたくなったとき、 changePrice()を修正し忘れる誤り メソッドの中身を読まないとどんな $price を入れてよいかわからない

Slide 37

Slide 37 text

判定処理が重複している ❌ そもそも何がOKで何がNGとしている かの意図が伝わりづらい ❌ 0円もNGにしたくなったとき、 changePrice()を修正し忘れる誤り Productクラスの問題点 メソッドの中身を読まないとどんな $price を入れてよいかわからない

Slide 38

Slide 38 text

テクニック2 設計者の意図をコードで伝える

Slide 39

Slide 39 text

設計において意図を伝えるには インターフェース ● changePriceを行うには、int型のpriceを与え ● changePriceを行った結果として、何も返さない (void) より狭い意味を持つ値をクラスで表現する

Slide 40

Slide 40 text

より狭い意味を持つクラス

Slide 41

Slide 41 text

商品の価格を表すProductPriceを作る 制約を持たせて マイナスにならないようにする より狭い意味を持つクラス

Slide 42

Slide 42 text

より狭い意味を持つクラス

Slide 43

Slide 43 text

引数にProductPriceを取るようにする より狭い意味を持つクラス

Slide 44

Slide 44 text

設計において意図を伝えるには インターフェース(After) ● changePriceを行うには、ProductPriceを渡す ● changePriceを行った結果として、何も返さない(void)

Slide 45

Slide 45 text

何が変わったか Before ● 成功するかどうかは事前にわからない ○ 必ず成功させたいならif ($price < 0)判定を呼び出し側にも書かなければならない After ● 渡した値がProductPriceであれば必ず変更できる ○ (少なくとも$priceが原因による失敗はない)

Slide 46

Slide 46 text

ここまでのまとめ

Slide 47

Slide 47 text

ここまでのまとめ コンストラクタも変更 情報量の少ない intやstringは使わない

Slide 48

Slide 48 text

ここまでのまとめ 情報量の少ない intやstringは使わない コンストラクタも変更 判定処理も1箇所だけに

Slide 49

Slide 49 text

誤った使い方をする方が難しい ここまでのまとめ 使い方がわかるような引数にする ポイント

Slide 50

Slide 50 text

サンプルコードの題材(再掲) ● ECサービスのWebアプリケーション(Amaz◯n・メ◯カリ) ○ 商品 ■ 商品名(string:1~20文字) ■ 価格(int:0~PHP_INT_MAX) ○ ユーザーは商品を作成できる ○ 作成した商品は価格を変更できる ○ 商品を購入する時、特定の割引率によって割引される

Slide 51

Slide 51 text

購入時に割引される処理 割引後の価格を計算 決済システムに送信

Slide 52

Slide 52 text

設計者の考えを残したい ● ○ 避ける方針にしたい ● ○ 割引が適用されていないProductPriceを受け取れてしまう 他の値と区別するためにクラスを作る

Slide 53

Slide 53 text

他と区別するためのクラス 中身は一旦ProductPriceと同じ

Slide 54

Slide 54 text

他と区別するためのクラス

Slide 55

Slide 55 text

他と区別するためのクラス 割引後の価格が必要だとわかる 使う側は割引後の価格を 用意しなければならなくなった

Slide 56

Slide 56 text

他と区別するためのクラス

Slide 57

Slide 57 text

他と区別するためのクラス 割引していない値で newされるかもしれない

Slide 58

Slide 58 text

他と区別するためのクラス 制約だけでは表せていない

Slide 59

Slide 59 text

他と区別するためのクラス

Slide 60

Slide 60 text

他と区別するためのクラス 割引額が何から導き 出されているかを明確にする

Slide 61

Slide 61 text

他と区別するためのクラス 誤った使い方をする方が難しい 独断で詰め替えることが難しい 割引額が何から導き 出されているかを明確にする

Slide 62

Slide 62 text

一旦まとめ 割引を計算してから決済す る必要がある... ▲ 使う人

Slide 63

Slide 63 text

一旦まとめ 割引額を出すには価格と 割引率の両方が必要 ... ▲ 使う人

Slide 64

Slide 64 text

一旦まとめ ▲ 使う人 何を受け取るか?の候補は たくさんある

Slide 65

Slide 65 text

一旦まとめ ▲ 使う人 何を受け取るか?の候補は たくさんある 決済サービスには 計算ロジックを 入れたくないようだ...

Slide 66

Slide 66 text

もくじ ● はじめに ● サンプルコードを使ったテクニック紹介 ○ 制約をクラス内部に持たせる ○ 設計者の意図をコードで伝える ● 設計を実践するにあたって ● まとめ

Slide 67

Slide 67 text

設計を実践するにあたって Q. 既存コードに適用するには? 数も多いし影響範囲が広いです。 どれから手をつけていいかわかりません

Slide 68

Slide 68 text

設計を実践するにあたって Q. 既存コードに適用するには? 数も多いし影響範囲が広いです。 どれから手をつけていいかわかりません そもそも設計を導入するなら ● 当たり前のレベルを上げることを目標に ● チーム全体での心理的な変化をもたらす

Slide 69

Slide 69 text

設計を実践するにあたって Q. 既存コードに適用するには? 数も多いし影響範囲が広いです。 どれから手をつけていいかわかりません A. 影響範囲が狭く、 仕様をよく理解しているところから テクニックを適用しやすく 慣れてない人でもやりやすい

Slide 70

Slide 70 text

設計を実践するにあたって ● 今日のテクニックをマスターするには ○ 作る前に使ってみる ■ テスト駆動開発:テストコードと並行して作る ○ そのまま実装して誤用されるかを観察 ■ コードレビューでの修正指摘 ■ リリース後の不具合

Slide 71

Slide 71 text

設計を実践するにあたって ● もっと深く学びたい人へ ○ 使わなかった言葉 ■ フールプルーフ・値オブジェクト・完全コンストラクタ・フェイルファスト・ 契約的プログラミング・表明・ドメインモデル・プリミティブ型執着・ カプセル化・イミュータブル・カプセル化・単一責任・関心の分離・事前条件・ 事後条件・不変条件 ○ ドンピシャな言葉もあれば、考え方を応用したものもある

Slide 72

Slide 72 text

もくじ ● はじめに ● サンプルコードを使ったテクニック紹介 ○ 制約をクラス内部に持たせる ○ 設計者の意図をコードで伝える ● 設計を実践するにあたって ● まとめ

Slide 73

Slide 73 text

まとめ ● ”誤った使い方をする方が難しい”インターフェースは良いインターフェース ○ 制約をクラス内部に持たせる ■ 不自然な値でインスタンス化できないようにコンストラクタを作る ■ 変更においても不完全な状態にならないようにする ○ 設計者の意図をコードで伝える ■ より狭い意味を持つ値をクラスで表現する ■ 他の値と区別するためにクラスを作る ● まずは影響範囲が狭く、仕様をよく理解しているところから始める ● 作る前に使う・誤用するか観察する ● 基礎が身についたらさらに深い設計へステップアップ

Slide 74

Slide 74 text

ご清聴ありがとうございました 次の発表 taniguhey X @TanigUhey 最近東方ブームが来ています