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

組織もソフトウェアも難しく考えない、もっとシンプルな考え方で設計する #phpconfuk

組織もソフトウェアも難しく考えない、もっとシンプルな考え方で設計する #phpconfuk

PHPカンファレンス福岡2025の発表資料です。
https://fortee.jp/phpcon-fukuoka-2025/proposal/01c22f2b-e1bc-467d-ad9c-f696535c9466

Avatar for hideki kinjyo

hideki kinjyo PRO

November 03, 2025
Tweet

More Decks by hideki kinjyo

Other Decks in Programming

Transcript

  1. 発表に入る前に: 環境テスト 本資料の最小文字サイズ・最大表示領域・色をご確認ください。 [見えにくい場合] 資料を公開済みなので、ぜひお手元にご用意ください => https://speakerdeck.com/o0h/phpconfuk-2025/ or Fortee or

    ↓から 縦 幅 は 最 大 で こ こ ま で 使 い ま す ( 下 ま で 見 切 れ ず に 見 え ま す か ? ) 文字サイズ、色やオブジェクトの例(一部) ・カラー1・カラー2・カラー3・カラー4  スライド番号この辺 hello ポストお願いします!ありがとうございます!!→ #phpconfuk #hall_hz
  2. おしながき §1 定義:フィードバックって何だ!? @5分 §2 活用:フィードバックを設計に活かす @10分 §3 発展:組織もソフトウェアも @8分

    §4 エピローグ @3分 8 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 0 1 /30
  3. 自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏

    : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • 最近はPodcastをやっています • ハッシュタグ: #readlinefm  ⏳estimate: 大 1 /30
  4. [ソ]フィードバック③ try { $fp = fopen($file, $mode); } catch (Throwable

    $e) { $ex = new RuntimeException( sprintf( 'Unable to open %s w/%s', $file, $mode, ), previous: $e); }  例外のthrowもフィードバック 反応が返る 何かをする ⏳estimate: 大 3 4 /30
  5. フィードバックの働き 33 ⚡ 呟きチャンス!1発いかがっすか? #phpconfuk #hall_hz お、良いボタンがある。 押してみようかな。ポチ〜 押すだけで 1000万円

    当たるボタン 押す ◯◯サービスに 登録されました (月額30万円) 視覚に訴える (変化) ⏳estimate: 大 4 5 /30
  6. フィードバックの働き 34 ⚡ 呟きチャンス!1発いかがっすか? #phpconfuk #hall_hz お、良いボタンがある。 押してみようかな。ポチ〜 押すだけで 1000万円

    当たるボタン 押す ◯◯サービスに 登録されました (月額30万円) 視覚に訴える めちゃヤバ案件 (変化)
  7. フィードバックの働き -より俯瞰して- 39 行動の結果として フィードバックを得る 次の行動は、 「目的」に照らし合わせて 調整される 「FFBなう」っ て呟いて!

    たくさん流れ てきた!! 登壇した痕跡 残してぇ〜〜 RePost.. RePost.. RePost.. RePost.. ⚡ 呟きチャンス!1発いかがっすか? #phpconfuk #hall_hz ⏳estimate: 大 5 6 /30
  8. フィードバックの働き -より俯瞰して- 40 行動の結果として フィードバックを得る 次の行動は、 「目的」に照らし合わせて 調整される 登壇した痕跡 残してぇ〜〜

    「FFBなう」っ て呟いて! あんま流れて こない。。。 早く!! 「FFBなう」って呟 いて! ⚡ 呟きチャンス!1発いかがっすか? #phpconfuk #hall_hz ⏳estimate: 大 6 /30
  9. 契約書の 雛形を 更新したよ! 使いにくい! 組織で見るフィードバック 46 ★ 目的に合わせて、フィードバックを次のアクションにつなげる 契約書の 雛形を

    更新したよ! 共有・説明会を開こう! 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 6 7 /30 業務効率を 上げるぞ!
  10. 契約書の 雛形を 更新したよ! 契約書の 雛形を 更新したよ! 組織で見るフィードバック 47 ★ 目的に影響を与えないフィードバックは影響を及ぼさない

    経費精算 大変すぎる〜 ふーん 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 7 /30 業務効率を 上げるぞ!
  11. [ソ]フィードバック $version = $this-> getVersion(); if ($version->lt($minVersion)) { return false;

    } return true;  「バージョンどうですか?」 の入力 $version = $this->getVersion(); $version->lt($minVersion) ⏳estimate: 大 7 /30
  12. [ソ]フィードバック $version = $this->getVersion(); if ($version->lt($minVersion)) { return false; }

    return true;  true or falseの フィードバック $version->lt($minVersion) ⏳estimate: 大 7 8 /30
  13. [ソ]フィードバック $version = $this->getVersion(); if ($version->lt($minVersion)) { return false; }

    return true;  入力者側が、 フィードバック内容を見て 次のアクションを行う return false; return true; ⏳estimate: 大 7 8 /30
  14. v [ソ]フィードバック function isSupported() { $version = $this->getVersion(); if (

    $version->lt($minVersion) ) { return false; } return true; }  この手続きは 「バージョンで判定する」 という目的がある function isSupported() ⏳estimate: 大 7 8 /30
  15. 分析結果を踏まえて • 目的: 良い企画を考える(企画会議で提案する) • 現状の実装: CSからデータを取り寄せ、自分で解釈する • オーバーヘッド: •

    受け取り後の分析コスト • CSとの一往復(欲しい内容の指示、出力結果の受信) 73 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 11 12 /30
  16. 分析結果を踏まえて • 目的: 良い企画を考える(企画会議で提案する) • 現状の実装: CSからデータを取り寄せ、自分で解釈する • オーバーヘッド: •

    受け取り後の分析コスト • CSとの一往復(欲しい内容の指示、出力結果の受信) 74 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ここのコストが高さが、 「組織の構造のからくる問題」の高さ ⏳estimate: 大 12 /30
  17. 分析結果を踏まえて • 目的: 良い企画を考える(企画会議で提案する) • 現状の実装: CSからデータを取り寄せ、自分で解釈する • オーバーヘッド: •

    受け取り後の分析コスト • CSとの一往復(欲しい内容の指示、出力結果の受信) 75 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz この構造の修正によって、 コストの総和を下げられないか? ⏳estimate: 大 12 /30
  18. ソフトウェアで見るフィードバック $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  ⏳estimate: 大 12 13 /30
  19. ソフトウェアで見るフィードバック $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  $this->productApiClient->request($productId); API Clientで 商品を取ってくる ⏳estimate: 大 13 /30
  20. ソフトウェアで見るフィードバック $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  $statusCode = $result->getStatusCode(); if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } HTTPステータスから エラーを判定&処理を中断する ⏳estimate: 大 13 /30
  21. ソフトウェアで見るフィードバック $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } 応答本文から エラーを判定&処理を中断する ⏳estimate: 大 13 14 /30
  22. ソフトウェアで見るフィードバック $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  return $this->success(['product' => $data['detail']]); 商品情報を返す ⏳estimate: 大 13 14 /30
  23. 受け取った後に「何をしているか」を分析する コントローラーが持っている仕事 • HTTPステータスからエラーを判定=>エラーを返す • 応答本文からエラーを判定=>エラーを返す • 商品情報を返す 83 🗯

    お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz Controller API Client 商品情報くれ〜 取ってきた結果〜 中身を精査しないと ⏳estimate: 大 14 15 /30
  24. 本来の目的と非本来的なコスト $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  return $this->success(['product' => $data['detail']]); $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); return $this->error('Please try again later'); return $this->error('Unable to load product', 400); return $this->error('Product unavailable', 404); このあたりが「本題」で ⏳estimate: 大 15 /30
  25. 本来の目的と非本来的なコスト $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  } $statusCode = $result->getStatusCode(); if ($statusCode >= 500) { このあたりが 「本題以外」と言える } elseif ($statusCode >= 400) { $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { } ⏳estimate: 大 15 16 /30
  26. 本来の目的と非本来的なコスト $productId = $request->get('product_id'); $result = $this->productApiClient->request($productId); $statusCode = $result->getStatusCode();

    if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error('Unable to load product', 400); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error('Product unavailable', 404); } return $this->success(['product' => $data['detail']]);  「本題」は 全体の半分に満たない! ⏳estimate: 大 15 16 /30
  27. 分析から見えてきたこと どんな問題が見えて来たのか • フィードバックを受け取ってからの処理が長い、煩雑 • => 目的のために利用するコストが高い(良いフィードバックになっていない) • 利用するために必要な知識が多様 •

    HTTPステータス・APIレスポンスの形式、と複数のレイヤーにまたがる • => 知識の多様化は高機能化をもたらし、目的を曇らせやすい 89 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 15 16 /30
  28. ProductApiGateway: fetchメソッド public function fetch(int $productId): ProductApiResult { $result =

    $this->productApiClient->request($productId); $statusCode = $result->getStatusCode(); if ($statusCode >= 500) { return ProductApiResult::error('Please try again later'); } elseif ($statusCode >= 400) { return ProductApiResult::error('Unable to load product'); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return ProductApiResult::error('Product unavailable'); } return ProductApiResult::success($data['detail']); }  ⏳estimate: 大 16 /30
  29. ProductApiGateway: fetchメソッド public function fetch(int $productId): ProductApiResult { $result =

    $this->productApiClient->request($productId); $statusCode = $result->getStatusCode(); if ($statusCode >= 500) { return ProductApiResult::error('Please try again later'); } elseif ($statusCode >= 400) { return ProductApiResult::error('Unable to load product'); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return ProductApiResult::error('Product unavailable'); } return ProductApiResult::success($data['detail']); }  $statusCode = $result->getStatusCode(); if ($statusCode >= 500) { return ProductApiResult::error('Please try again later'); } elseif ($statusCode >= 400) { return ProductApiResult::error('Unable to load product'); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return ProductApiResult::error('Product unavailable'); } 中身は コントローラーで やっていた処理と 大体同じ ⏳estimate: 大 16 /30
  30. ProductApiGateway: fetchメソッド public function fetch(int $productId): ProductApiResult { $result =

    $this->productApiClient->request($productId); $statusCode = $result->getStatusCode(); if ($statusCode >= 500) { return ProductApiResult::error('Please try again later'); } elseif ($statusCode >= 400) { return ProductApiResult::error('Unable to load product'); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return ProductApiResult::error('Product unavailable'); } return ProductApiResult::success($data['detail']); }  ProductApiResult ProductApiResult::error('Please try again later'); ProductApiResult::error('Unable to load product'); ProductApiResult::error('Product unavailable'); ProductApiResult::success($data['detail']); 利用目的に合わせた 「ハイレベル」な表現で 結果を返す ⏳estimate: 大 16 17 /30
  31. Controller before/After $productId = $request ->get('product_id'); $result = $this ->gateway

    ->fetch($productId); if (!$result->isOk) { $this->error($result->reason); } else { $this->success([ 'product' => $data['detail'] ]); }  $productId = $request->get('product_id'); $result = $this ->productApiClient ->request($productId); $statusCode = $result->getStatusCode(); if ($statusCode >= 500) { return $this->error('Please try again later'); } elseif ($statusCode >= 400) { return $this->error( 'Unable to load product', 400 ); } $body = $result->getBody()->getContents(); $data = json_decode($body, true); if ($data['status'] === 'invalid') { return $this->error( 'Product unavailable', 404 ); } return $this->success(['product' => $data['detail']]); ⏳estimate: 大 16 17 /30
  32. 「良いフィードバック」のための調整が行われ、均等化・減少した 構造が変更される 96 Controller Gateway 商品情報くれ〜 取ってきた結果〜 Result API Client

    中身を精査済み★ コスト激低 コスト低 コスト低 コスト激低 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 17 /30
  33. 「ドメインモデル貧血症」的な見方 問題の形: • getterを提供しているだけでデータを使う知識がない • (悪い)結合や、(悪い)トランザクションスクリプトを誘発する なぜならば: • CSに「プロダクトを一緒に作る」という役割がない •

    ゆえに、「ただgetterを公開しているだけ」に成り下がっている それゆえ: • フィードバックを利用するためのコストを高めている 113 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 20 21 /30
  34. 「機能の横恋慕的」な見方 問題の形: • 自身よりも、他のオブジェクトの機能やデータにばかり関心がある • 低凝集なオブジェクトの典型的な症状(不吉な臭い) なぜならば: • 「顧客の反応にアクセスする」のは「CSの機能」という思い込み •

    ゆえに、「わざわざCSを経由する」という教条的なコミュニケーションに陥る それゆえ: • 必要なデータを得る際にオーバーヘッドが生じている 114 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 21 /30
  35. 組織構造のアーキタイプ これらの要素を組み合わせて、組織の「構造」が作られる • 公式な組織 - 部門、部署 • 組織図上に現れる単位 • 準公式な組織

    - 時限的なプロジェクト、委員会 • 部署・部門と境界線を同一としない集約 • 会議体 / 会議 - 情報共有、意思決定のための集まり • アジェンダありきの単発・連続の集合 • 非公式なつながり - 偶発的な交流、(構造に用いる場合には)意図的な交流の仕組み • 業務上の結合に閉ざされない関係 • 組織パターン「冷水機」 スクラムパターン「おやつ神社」、Slackのtimesなども 115 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz ⏳estimate: 大 21 22 /30
  36. CS 1. 「CS」を「プロダクト開発ドメイン」に組み込む • 「プロダクト開発ドメイン」の拡張: CSの参画 • アーキタイプ「会議」や「チーム」でリファクタ 117 🗯

    お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz 企画会議 顧客の声 インストール済み グロース全体像 インストール済み 次の施策を考えるぞ〜 ⏳estimate: 大 22 /30 PdM
  37. 2. 「顧客の声へのアクセス」を「CS」以外にも開放する • 「ロール-アクセスpermission」の見直し: PdMロールへの機能付与 • PdM-CS間の結合の解消 118 🗯 お、こんな所で目が合いました?呟いてね〜!

    #phpconfuk #hall_hz 最近多い顧客の声を教えて 集計データどうぞ! 次の施策を考えるぞ〜 企画会議 データを取り出して 読み解く俺・・ ⏳estimate: 大 22 23 /30
  38. 「システム」 一般に対するアプローチ • システムには • 「要素」がある • 要素同士の「繋がり」がある • 繋がった者同士の「相互作用」がある

    • 分析した要素の1つ1つだけを見ても、システムの理解は得難い • 代わりに、「繋がり」「相互作用」を見る必要がある 147
  39. G・M・ワインバーグの「一般システム思考入門」 • 「観察」を行う主体もシステムの一部であるという立場 • ("ネオ・サイバネティクス"と通ずるものがありそう • システムは、(依存関係にある)対象を「観察」して、自身の変化・適応を自律 的に行うもの • また、フラクタルな構造を持ち、ミクロなシステムは全て上位の「環境」の一

    部である、という論も展開している • 「コード→パッケージ→ソフトウェア→インフラを含めたITシステム→ビジネス要求→ マーケットや現実世界→自然法則・宇宙」という感じに • 下位だけを見ると「変更」であっても、上位に対する「適応」にしか過ぎない 151 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz
  40. ノーマンの「行為の7段階理論」 • ゴールの形成 → 意図の形成 → 行為の詳細化 → 行為の実行 →

    外 界の状況の知覚 → 外界の状況の解釈 → 結果の評価 • これも「フィードバックがあったときに、それを拾って、判断し、次 の行動に」という世界観と一致する 152 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz
  41. ヴァージニア・サティアの交流モデル • 取り込み→意味づけ→意義付け→反応という 構造 • 刺激(この発表で言うとフィードバックみた いなやつ)→それを受け取った人の解釈・評 価→結果に基づく行動 という構造としてモ デル化されている

    • 「ワインバーグのシステム洞察法」か「パー フェクトソフトウェア」あたりを読んでくだ さい 154 🗯 お、こんな所で目が合いました?呟いてね〜! #phpconfuk #hall_hz