Slide 1

Slide 1 text

詳細の決定を遅らせつつ 実装を早くする 2025/11/08 PHPカンファレンス福岡2025 しまぶ@shimabox

Slide 2

Slide 2 text

NAME: "しまぶ" SNS: "@shimabox" TAMASHII: "沖縄" COMPANY: "カオナビ" SKILL: - "PHP" - "Go" FUKUOKA: "SB⚾, アビスパ⚽おめ" whoami.yml 2 はいさい

Slide 3

Slide 3 text

こんな経験はありませんか 3 ステークホルダー XXX画面作成の進捗は どうでしょうか? ステークホルダー (まだ!!?) おれたち ただいま絶賛DB設計中で...

Slide 4

Slide 4 text

こんな経験はありませんか 4 ステークホルダー XXX画面作成の進捗は どうでしょうか? ステークホルダー (まだ!!?) おれたち ただいま絶賛DB設計中で... まれによくある

Slide 5

Slide 5 text

「完璧なDB設計になるまで  実装を始められない」 「どの外部APIを選ぶべきか  決められず1ヶ月が経過」 「仕様が決まらないので  手を動かせない」 まれによくある 5

Slide 6

Slide 6 text

1. 境界を作る(インターフェース) 2. かんたんな実装で動かす(デモする) 3. フィードバックを得てから本実装 6 この3つのステップを使って解決していきます

Slide 7

Slide 7 text

なぜ、手が止まるのか 7 そもそも

Slide 8

Slide 8 text

8 なぜ手が止まるのか 完璧を求めすぎている (最初からいいものを作ろうとしている)

Slide 9

Slide 9 text

● 「最初から正解を出さないと...」 ● 「失敗したくない...」 ● 「後から変更できないと思っている」 9 なぜ手が止まるのか

Slide 10

Slide 10 text

完璧なもの、正解なんてものはない 10 気持ちはわかる、でもね

Slide 11

Slide 11 text

要求は変わる 11 完璧なもの、正解なんてものはない このサイクルを回したい 作 る 試 す 直 す → →

Slide 12

Slide 12 text

詳細の決定を遅らせつつ 実装を早くする 12 そこで

Slide 13

Slide 13 text

13 詳細の決定は後回し Clean Architecture 達人に学ぶソフトウェアの構造と設計 詳細の決定を遅らせろ ● 詳細の決定は後回しにできる ● いや、後回しにすべきだと 言っている

Slide 14

Slide 14 text

詳細 ● DB, フレームワーク, UI, 外部ライブラリ(API), テスト, などなど ● 本質的な目的に直接は貢献しないが、目的を実 現するのに必要な技術的なツールや仕組み ● 詳細は目的ではなく、手段 14 詳細とはなんでしょう

Slide 15

Slide 15 text

これらは詳細 なので後回し 「完璧なDB設計になるまで  実装を始められない」 「どの外部APIを選ぶべきか  決められず1ヶ月が経過」 「仕様が決まらないので  手を動かせない」 15 まれによくある

Slide 16

Slide 16 text

なぜこれで実装が早くなるのか 16 詳細の決定を遅らせる

Slide 17

Slide 17 text

グルメ情報サイトを作る (福岡市に特化) 17 かんたんな例で見ていきます

Slide 18

Slide 18 text

イメージ 18 福岡市のグルメスポット

Slide 19

Slide 19 text

API定義を決める ↓ DB設計を考える ↓ アーキテクチャを考える ↓ 実装 19 よくある 実装まで終わっ てから、フィー ドバックをもら う

Slide 20

Slide 20 text

● 「何を受け取って、何を返すか」 ● OpenAPI Specification (OAS) を用意したりする 20 API定義を決める

Slide 21

Slide 21 text

{ "data": [ { "area_name": "中央区", "spots": [ { "id": 1, "name": "◯蘭 天神店", "category": "ラーメン", "location": "福岡市中央区天神x-x-x", "description": "天然とんこつラーメン専門店", "note": "xxxxx", "image_url": "/images/xxxxx.jpg" }, ] }, { "area_name": "博多区", "spots": [ {// 〜 }, {// 〜 }, {// 〜 } ] }, ] } 21 レスポンスJson (正常系)

Slide 22

Slide 22 text

22 よくある思考 「テーブル設計、クエリはどうしよ うかな」 「正規化は?インデックスは?」 ↓ 1週間経過...まだエンドポイントが 叩けない DB設計

Slide 23

Slide 23 text

これは詳細 「テーブル設計、クエリはどうしよ うかな」 「正規化は?インデックスは?」 23 DB設計

Slide 24

Slide 24 text

1. 境界を作る(インターフェース) 2. かんたんな実装で動かす(デモする) 3. フィードバックを得てから本実装 24 そこで、この3つのステップ

Slide 25

Slide 25 text

● 「何を受け取って、何を返すか」 25 API定義を決める(従来通り)

Slide 26

Slide 26 text

26 1. 境界を作る // インターフェースで境界を作る interface GourmetSpotInterface { public function findAll(): array; } ポイント ● 「何をするか」だけ定義 ● 「どうやるか」は決めない

Slide 27

Slide 27 text

27 2. かんたんな実装 class InMemoryGourmetSpot implements GourmetSpotInterface { private array $gourmetSpots = [ [ 'id' => 1, 'name' => '◯蘭 天神店', 'location' => '福岡市中央区天神x-x-x', // 〜 ], [ 'id' => 2, 'name' => 'うどん◯', 'location' => '福岡市博多区住吉x-xx-xxx', // 〜 ], [// 〜], [// 〜], , , ]; public function findAll(): array { return $this->gourmetSpots; } } DB設計は知らんけど、動く!

Slide 28

Slide 28 text

28 2.5 依存先を変える ここでテストを書いておくと良い class GourmetSpotController { public function __construct( private GourmetSpotInterface $gourmetSpot ) {} public function index(): JsonResponse { $gourmetSpots = $this->gourmetSpot->findAll(); // エリア別にグルーピング → ビジネスロジック $groupedByArea = $this->groupByArea($gourmetSpots); return response()->json(['data' => $groupedByArea]); } private function groupByArea(array $gourmetSpots): array { // 住所から「中央区」「博多区」などを抽出 } }

Slide 29

Slide 29 text

29 デモをしてフィードバックを得る おれたち 動くもの作ったので 見てもらえますか? ステークホルダー うーん、料金とか評価とか 項目をもう少し増やしたいです おれたち 了解です!

Slide 30

Slide 30 text

デモをしてフィードバックを得る おれたち (とりあえず配列いじればいいか) (PHPの配列マジで神) 修正楽勝だな〜 おれたち 修正したので 見てもらえますか? ステークホルダー ぺきかん! 30

Slide 31

Slide 31 text

// フィードバックを得た上で実装 class GourmetSpot implements GourmetSpotInterface { public function findAll(): array { // この中でDBを触っていく // インターフェースを守っていれば、 試行錯誤が可能 // ----- 以下の選択肢がある ----- // DBじゃなくていいかも // 誰でも編集できるようにファイルでいいかも } } 31 3. フィードバックを得てから本実装 選択肢を残せる / Controllerの修正は不要

Slide 32

Slide 32 text

32 よくある 詳細の決定を遅らせる ● Week 1 ○ API定義を決める ● Week 2 ○ DB設計、アーキテクチャを考 える ● Week 3 ○ ようやく実装開始 ● Week 4 ○ そしてデモ ↓ 1ヶ月後にやっとフィードバック ● Week 1 ○ API定義を決める ● Week 2 ○ インターフェース定義 ○ メモリ実装 ○ デモ ↓ 2週間目からフィードバック! ● Week 3以降 ○ 確信を持って設計、本実装

Slide 33

Slide 33 text

ステークホルダー 福岡市のお天気情報を表示す るようにしてください おれたち (お天気情報はどこから?) (そもそも必要なんか?) APIは何を使いますか? それが...まだ決まってないです なにかが降ってきた 33

Slide 34

Slide 34 text

イメージ 34 福岡市のグルメスポット ☀ 23℃(お出かけ日和です) これ

Slide 35

Slide 35 text

35 やっぱり、この3つのステップ 1. 境界を作る 2. かんたんな実装で動かす 3. フィードバックを得てから本実装

Slide 36

Slide 36 text

{ "temperature": 23, "weather": "sunny", // これで画像を決める "message": "お出かけ日和です" } 36 API定義を決める

Slide 37

Slide 37 text

37 1. 境界を作る interface WeatherServiceInterface { // モデルを返す(配列でもいいけど) public function getWeather(): Weather; } ポイント ● OpenWeatherMap? WeatherAPI? 気象庁API? → 後で決める!

Slide 38

Slide 38 text

38 2. かんたんな実装 class MockWeatherService implements WeatherServiceInterface { public function getWeather(): Weather { // APIは呼ばずにモデルを返す return new Weather( temperature: 23, weather: 'sunny' ); } } APIの仕様は知らんけど、動かす!

Slide 39

Slide 39 text

39 2. かんたんな実装 final class Weather { public function __construct( public readonly int $temperature, public readonly string $weather ) {} public function toArray(): array { return [ 'temperature' => $this->temperature, 'weather' => $this->weather, 'message' => $this->message(), ]; } // ビジネスロジック private function message(): string { return match ($this->weather) { 'sunny' => $this->temperature > 20 ? 'お出かけ日和です' : '少し肌寒いです', default => '今日も一日頑張りましょう' }; } } モデルは単独でテストが書ける!

Slide 40

Slide 40 text

class GourmetSpotController { public function __construct( private GourmetSpotInterface $gourmetSpot, private WeatherServiceInterface $weatherService ) {} public function index(): JsonResponse { $gourmetSpots = $this->gourmetSpot->findAll(); $groupedByArea = $this->groupByArea($gourmetSpots); $weather = $this->weatherService->getWeather(); return response()->json([ 'data' => $groupedByArea, 'weather' => $weather->toArray() ]); } private function groupByArea(array $gourmetSpots): array {} } 40 2.5 依存先を変える ここでもテストは書いておく

Slide 41

Slide 41 text

うーん、、いらないです 了解です!(やっぱりな) デモをしてフィードバックをもらう 41 仮実装のおかげで ● 外部API契約してない ● 複雑な実装はしてない ● ✅ すぐ削除できる

Slide 42

Slide 42 text

42 3. 採用となってもフィードバックを得ているので実装に入れる class WeatherService implements WeatherServiceInterface { public function getWeather(): Weather { // 外部APIを叩いて本物のデータを取得 // キャッシュしたりとかも // (実際はHTTPクライアント使う) $data = file_get_contents('https://api.xxxx.org/...'); return $this->createWeather($data); } private function createWeather($data): Weather {}; } 差し替えるだけ

Slide 43

Slide 43 text

43 よくある 詳細の決定を遅らせる ● Week 1-2 ○ どのAPIを使うか悩む ○ 料金は? レスポンス速度は? ● Week 3 ○ 実装開始 ● Week 4 ○ デモ ↓ 「やっぱり、いらないです」🔥 ● Week 1 ○ 仮実装でデモ ○ 「やっぱり、いらないです」✅ または、 ● Week 1 ○ 仮実装でデモ「よいですね!」 ● Week 2-3 ○ 仕様明確 → API選定👍 → 実装 フィードバックが早いと、 無駄なし!

Slide 44

Slide 44 text

白状します。 外部APIの選定に1ヶ月以上かけたことがあります。 APIの使い方や、料金、レスポンス速度ばかり調べてい ました。 今となればもう少しうまく進めることが出来たんじゃな いかなぁと反省しながら喋っています。 44 昔の思い出

Slide 45

Slide 45 text

閑話休題 45

Slide 46

Slide 46 text

46 アプローチをふりかえる

Slide 47

Slide 47 text

47 アプローチをふりかえる 1. 境界を作る (詳細の決定は後回し)

Slide 48

Slide 48 text

48 アプローチをふりかえる 2. 仮実装 (動くものを早く作る)

Slide 49

Slide 49 text

49 アプローチをふりかえる 3. 本実装 (確信を持って実装)

Slide 50

Slide 50 text

質問 「必ずインターフェースを用意しな いといけないの?」 50 唐突にQ&A

Slide 51

Slide 51 text

51 回答:必ずしも必要ではない ベタ書きでもOK ● まずは動かすことが大事 ● 変わらなそうであればコントローラにベタ書き 詳細はあとでじっくり考えたいところ ● 変わりそうなものと、変わらないものを分ける ● そこに境界を作る

Slide 52

Slide 52 text

52 境界(抽象)をひくのは難しい ● 変わりそうなものと、変わらないものを分ける ● そして、変わらないものに依存したい ● 抽象は変わらないものとして用意したい 抽象は安定化させたい(が、これが難しい)

Slide 53

Slide 53 text

53 完成までは難しい ● PHPの配列は最強だからといって、そのままだ と辛い、Immutableにしたい ● グルメスポット一覧は、`GourmetSpot[]` な どで表現できるようになるといい ● 動くものは早く作れるけど、完成が早くなると は言っていない

Slide 54

Slide 54 text

まとめ 54

Slide 55

Slide 55 text

55 今日話したこと ✅ 詳細の決定は後回しにできる  (今まで先に悩んでいたところ) ✅ インターフェースで境界を作れば   実装の置き換えが可能、試行錯誤も可能 ✅ 動くものを早く作ると、フィードバックも早まる

Slide 56

Slide 56 text

56 動くものを早く作ると、フィードバックも早まる ↓ 早期のフィードバックで間違った方向へ進むのを 防げる だいじなこと

Slide 57

Slide 57 text

57 1. 境界を作る 2. かんたんな実装で動かす 3. フィードバックを得てから本実装 このパターンを身につければ 仕様が未確定でも、手は動かせるはず💪 この3つのステップを試してみよう

Slide 58

Slide 58 text

58 完璧なもの、正解なんてものはない 手を動かしていきましょう💪 この3つのステップで何かを作ってみてほしい 1. 境界を作る 2. かんたんな実装で動かす 3. フィードバックを得てから本実装

Slide 59

Slide 59 text

X @shimabox 59 おわりに この発表も「完璧を目指さず」 作られました。 私自身、まだ実践の途中です。 フィードバックをお待ちしてい ます。

Slide 60

Slide 60 text

60 📖 共著で本を書いています 📖(時間があれば宣伝)

Slide 61

Slide 61 text

ご清聴ありがとうございました 61