Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Avatar for shimabox shimabox
November 08, 2025

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

PHPカンファレンス福岡2025 の発表資料になります
#phpconfuk #hall_hz

Avatar for shimabox

shimabox

November 08, 2025
Tweet

More Decks by shimabox

Other Decks in Programming

Transcript

  1. NAME: "しまぶ" SNS: "@shimabox" TAMASHII: "沖縄" COMPANY: "カオナビ" SKILL: -

    "PHP" - "Go" FUKUOKA: "SB⚾, アビスパ⚽おめ" whoami.yml 2 はいさい
  2. 詳細 • DB, フレームワーク, UI, 外部ライブラリ(API), テスト, などなど • 本質的な目的に直接は貢献しないが、目的を実

    現するのに必要な技術的なツールや仕組み • 詳細は目的ではなく、手段 14 詳細とはなんでしょう
  3. { "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 (正常系)
  4. 26 1. 境界を作る // インターフェースで境界を作る interface GourmetSpotInterface { public function

    findAll(): array; } ポイント • 「何をするか」だけ定義 • 「どうやるか」は決めない
  5. 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設計は知らんけど、動く!
  6. 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 { // 住所から「中央区」「博多区」などを抽出 } }
  7. // フィードバックを得た上で実装 class GourmetSpot implements GourmetSpotInterface { public function findAll():

    array { // この中でDBを触っていく // インターフェースを守っていれば、 試行錯誤が可能 // ----- 以下の選択肢がある ----- // DBじゃなくていいかも // 誰でも編集できるようにファイルでいいかも } } 31 3. フィードバックを得てから本実装 選択肢を残せる / Controllerの修正は不要
  8. 32 よくある 詳細の決定を遅らせる • Week 1 ◦ API定義を決める • Week

    2 ◦ DB設計、アーキテクチャを考 える • Week 3 ◦ ようやく実装開始 • Week 4 ◦ そしてデモ ↓ 1ヶ月後にやっとフィードバック • Week 1 ◦ API定義を決める • Week 2 ◦ インターフェース定義 ◦ メモリ実装 ◦ デモ ↓ 2週間目からフィードバック! • Week 3以降 ◦ 確信を持って設計、本実装
  9. 37 1. 境界を作る interface WeatherServiceInterface { // モデルを返す(配列でもいいけど) public function

    getWeather(): Weather; } ポイント • OpenWeatherMap? WeatherAPI? 気象庁API? → 後で決める!
  10. 38 2. かんたんな実装 class MockWeatherService implements WeatherServiceInterface { public function

    getWeather(): Weather { // APIは呼ばずにモデルを返す return new Weather( temperature: 23, weather: 'sunny' ); } } APIの仕様は知らんけど、動かす!
  11. 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 => '今日も一日頑張りましょう' }; } } モデルは単独でテストが書ける!
  12. 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 依存先を変える ここでもテストは書いておく
  13. 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 {}; } 差し替えるだけ
  14. 43 よくある 詳細の決定を遅らせる • Week 1-2 ◦ どのAPIを使うか悩む ◦ 料金は?

    レスポンス速度は? • Week 3 ◦ 実装開始 • Week 4 ◦ デモ ↓ 「やっぱり、いらないです」🔥 • Week 1 ◦ 仮実装でデモ ◦ 「やっぱり、いらないです」✅ または、 • Week 1 ◦ 仮実装でデモ「よいですね!」 • Week 2-3 ◦ 仕様明確 → API選定👍 → 実装 フィードバックが早いと、 無駄なし!