はじめに
バグを直しても同じくらい溜まる。技術的負債を返そうとしたのにさらに増える。効率化したのに障害が増えた。
同じ問題が繰り返されるなら、それは個人の問題ではなく仕組み(構造)の問題かもしれません。
この記事では、「システム思考」の考え方を使って、「なぜそうなるのか」を構造として捉えるための道具を紹介します。
TL;DR
- 繰り返される問題は「構造」に原因がある
- ストック(蓄積)とフロー(増減)で、変化が遅い理由がわかる
- 安定させる力(バランシング)と加速させる力(リインフォーシング)のどちらが支配的かで振る舞いが決まる
- フィードバックの「遅れ」が過剰修正を生む
- 効率化とレジリエンス(回復力)はトレードオフ
1. 「誰かのせい」にしていませんか -- 構造という視点
問題が起きたとき、私たちが最初にやること
問題が起きたとき、自分たちはまず「人」に原因を求めます。
- バグが本番に出たら → 「誰がレビューした?」「なぜテストを書かなかった?」
- 納期に遅れたら → 「あの人の見積もりが甘かった」「もっと頑張ればよかった」
ごく自然な反応です。
ただ、同じ問題が何度も繰り返されるなら、犯人探しでは根本的な解決にならない可能性があります。
問題を生み出しているのは特定の個人ではなく、 その問題を繰り返し生み出す仕組み かもしれないからです。
「仕組み」を読み解く道具 -- システムの3要素
では、その「仕組み」をどうやって見ればいいのでしょうか。
ここで役に立つのが システム という捉え方です。
システムとは、何かを達成するために組織化された、相互に連結された要素の集合のことです。
構成要素は3つあります。
┌────────────────────────────────────────────┐
│ システムの3要素 │
│ │
│ 要素 ……… 構成するパーツ │
│ 相互連結 … 要素同士のつながり方 │
│ 目的 ……… 全体として何を達成しようとするか │
└────────────────────────────────────────────┘
ソフトウェア開発チームをこの3要素で分解してみます。
| 要素 | 相互連結 | 目的 |
|---|---|---|
| エンジニア、コード、CI/CDパイプライン、Slackチャンネル | コーディング規約、レビュープロセス、デプロイフロー、スタンドアップミーティング | (公言)ユーザーに価値を届ける / (実態)リリースを遅らせないこと...? |
面白いのは「目的」の欄です。
公言されている目的と、チームの振る舞いから推測される実際の目的が、ずれていることは珍しくありません。
「ユーザーに価値を届ける」と掲げていても、実際の判断基準が「リリース日を守ること」に偏っているなら、
チームの本当の目的はそちらに近いかもしれません。
システムの目的は、宣言された言葉からではなく、実際の振る舞いから推測するのが確実です。
「品質第一」と言いながらテストを省略するチームがあれば、
実際の目的は品質ではなく速度に寄っている可能性があります。
要素を変えてもシステムは変わらない
ここで一つ、直感に反する話があります。
チームメンバーを全員入れ替えても、同じルール(相互連結)と同じ目的が残っていれば、同じ問題が再発します。
逆に、メンバーが同じでも、ルールや目的を変えれば振る舞いが変わります。
つまり、3要素には影響力の大小があります。
影響力: 大 影響力: 小
┌────────┐ ┌────────────┐ ┌────────┐
│ 目的 │ > │ 相互連結 │ > │ 要素 │
└────────┘ └────────────┘ └────────┘
「人を変える」のは最も影響が小さい介入です。
「ルールを変える」「目的を変える」方がシステムの振る舞いには大きく効きます。
ここで言いたいのは「人を責めても仕方ない」という精神論ではありません。
「構造を見れば、より効果的な介入点が見つかる」 ということです。
2. なぜいつも同じ問題が繰り返されるのか -- 蓄積と変化の速さ
「問題を減らしたはずなのに、気づいたらまた同じ状態に戻っている」。
この「元に戻る感覚」の正体を、もう少し掘り下げてみます。
「溜まっているもの」と「増減の速さ」で見る
問題を理解するとき、2つの観点を分けて考えると見通しが良くなります。
- 今どのくらい溜まっているか(量)
- どのくらいの速さで増えたり減ったりしているか(速さ)
システム思考では、前者を ストック(蓄積量)、後者を フロー(流入・流出の速さ)と呼びます。
浴槽で考えると分かりやすいです。
蛇口(流入)
│
▼
┌──────────┐
│ │ ← 水 = ストック(蓄積量)
│~~~~~~~~~~│
│ │
└────┬─────┘
│
▼
排水口(流出)
蛇口を閉めても、浴槽の水はすぐには空になりません。
排水口を全開にしても、一瞬で空にはならない。
ストックは急には変化しない というのが重要な性質です。
バグ数をストックとフローで見る
これをバグ数に当てはめてみます。
- ストック: バグトラッカーに溜まっている未解決バグの総数
- 流入(増えるフロー): 新機能開発で生まれるバグ、既存コードの経年劣化による不具合
- 流出(減るフロー): バグ修正、不要機能の廃止
「バグを集中的に直す週を作ろう」と決めても、同時に新機能開発が続けば流入は止まりません。
排水口(修正)を広げても、蛇口(新規バグの発生)を絞らなければ、水はそこまで減らないのです。
なぜバグが減らないのか の答えはシンプルで、
流入の速さが流出の速さを上回っている状態が常態化しているからです。
ストックが教えてくれる「変化には時間がかかる」
ストックの「急には変化しない」という性質は、他の場面でも当てはまります。
- 新しいコーディング規約を導入しても、既存コードの品質がすぐに上がるわけではない
- チームの信頼関係を壊すのは一瞬だが、築き直すには長い時間がかかる
この「慣性」を理解していれば、2つの判断ができるようになります。
-
改善の効果が出るまで「もう少し待つ」という判断
- 規約を入れて1スプリントで成果が出ないからといって、すぐに止めるのは早計かもしれない
-
流出だけでなく流入を減らす(蛇口を絞る)という発想
- バグを直すだけでなく、バグを生みにくい仕組み(ペアプロ、型チェックの強化など)で流入を抑える
3. なぜバグは「減らした」のに減らないのか -- 元に戻そうとする力
ストックとフローで問題の「量」と「速さ」が見えるようになりました。
次は、その量を安定させようとする仕組み、 フィードバックループ の話です。
目標に向かって引き戻す力 -- バランシング・フィードバックループ
バランシング・フィードバックループとは、ストックを特定の状態に保とうとする仕組みのことです。
ストックの現在値と目標値のギャップを検知し、ギャップを埋める方向にフローを調整します。
エアコンの温度調節が分かりやすい例です。
室温(ストック)が設定温度(目標)より高ければ冷房を強め、近づけば弱める。
このループが繰り返されることで、室温は設定温度の付近に保たれます。
コードレビューとテストはバランシング・ループ
実は、開発チームにもバランシング・ループがいくつも組み込まれています。
| 仕組み | 何を安定させるか | どう機能するか |
|---|---|---|
| コードレビュー | コードの品質 | 品質が目標を下回る → レビューで指摘が増える → 修正で品質が戻る |
| 自動テスト(CI) | テストの通過率 | コード変更でテストが壊れる → CIが失敗を検知 → マージがブロックされ修正が行われる |
| アラート/監視 | 本番のエラー率 | エラー率が閾値を超える → 通知が飛ぶ → 対応でエラー率が下がる |
これらはすべて「品質を目標値に引き戻そうとする力」です。
バランシング・ループがあるのに、なぜ品質は安定しないのか
ループが存在するだけでは、品質は安定しません。
ループが正しく機能するには、いくつかの条件が揃っている必要があります。
- ストックの現在値を正確に把握できている(情報が正しい)
- 目標値が明確に定義されている
- ギャップを検知してから対処するまでの遅れが小さい
- 対処のための資源(時間・人)が十分にある
現場でよく見る失敗パターンは、このどれかが欠けているケースです。
- テストカバレッジが低く、品質低下を検知できない(条件1の欠如)
- 「品質」の定義が曖昧で、何がOKで何がNGか分からない(条件2の欠如)
- レビューが溜まっていて、問題発見まで数日かかる(条件3の欠如)
- レビュー指摘を直す時間がなく「次のスプリントで」と先送りされる(条件4の欠如)
フィードバックの「遅れ」がすべてをややこしくする
4つの条件の中でも、特に厄介なのが 条件3: 遅れ です。
フィードバックの遅れとは、問題が発生してから、それを検知し、対処が完了するまでのタイムラグのこと。
遅れがあると、次の2つの問題が起きます。
- 問題が蓄積してから初めて気づく ため、対処が大がかりになる
- 「対処したのにまだ悪い」と感じてさらに対処を強めた結果、後で効きすぎる(過剰修正)
ソフトウェア開発で、遅れの違いがどれだけ大きいか見てみます。
| 検知の仕組み | 遅れ | 影響 |
|---|---|---|
| 手動テストのみ | 数日〜数週間 | バグが蓄積してから発覚。修正の手戻りが大きい |
| CI/CD | 数分〜数時間 | 変更直後に検知。修正コストが小さい |
CI/CDを「テストの自動化」ではなく「フィードバックの遅れの短縮」として捉えると、なぜCIが重要なのかがより明確になります。
CI/CDの本質的な価値は、問題の検知から対処までのタイムラグを劇的に縮めることにあります。
4. なぜ技術的負債は「少しだけ」では済まないのか -- 雪だるま式の罠
バランシング・ループが「安定させようとする力」だとすれば、
ここで紹介するのは正反対の力、 変化の方向をさらに加速させる力 です。
雪だるま式に増える仕組み -- リインフォーシング・フィードバックループ
リインフォーシング・フィードバックループとは、ストックに既にあるものが多いほど、さらに増える(少ないほど、さらに減る)仕組みのことです。
バランシング・ループとの違いを整理します。
| 種類 | 動き方 | 役割 |
|---|---|---|
| バランシング | ギャップを埋めて安定させようとする | 目標追求型 |
| リインフォーシング | 変化の方向をさらに強める | 自己増幅型 |
口コミの広がりで考えると分かりやすいです。
- 良い評判が広がる → ユーザーが増える → さらに評判が広がる(好循環)
- 悪い評判が広がる → ユーザーが減る → さらに評判が悪化する(悪循環)
同じ構造が、回り始めた方向によって好循環にも悪循環にもなる という点が重要です。
技術的負債の悪循環を構造で見る
「少しくらいの負債なら大丈夫」と思っていたのに、
気づいたら手がつけられないほど膨らんでいた。
そのメカニズムを、リインフォーシング・ループで描いてみます。
このループは 指数関数的 に変化します。
負債が少ないうちは変化が緩やかで気づきにくいのですが、
ある時点から急激に悪化します。
「まだ大丈夫」と感じているときこそ、実は介入すべきタイミングです。
負債が小さいうちに手を打てば、ループの加速を食い止められます。
逆に回せば好循環になる
同じ構造を、プラスの方向に回すこともできます。
現実のシステムでは、バランシング・ループとリインフォーシング・ループが同時に働いています。
コードレビュー(バランシング)が技術的負債の悪循環(リインフォーシング)にブレーキをかけ、
テストの好循環(リインフォーシング)が品質を押し上げる。
どちらのループが支配的かで、システム全体の振る舞いが決まります。
リインフォーシング・ループが悪循環方向に回り始めてから止めるのは、
好循環方向に回し始めるよりずっと難しいです。
「まだ大丈夫」のうちにバランシング・ループを整備しておくことが、構造的な予防になります。
5. なぜ効率化したのにシステムは脆くなったのか -- 回復力の話
ここまでは、問題を「ループの構造」で捉える話でした。
ここからは少し視点を変えて、「効率」と「安定」の間にあるトレードオフを考えてみます。
回復力(レジリエンス)とは何か
レジリエンス とは、外部からの衝撃を受けても元の状態に戻れる力のことです。
ここで注意したいのは、レジリエンスは「普段安定しているかどうか」とは別の話だということです。
普段は何の問題も起きないシステムが、レジリエントであるとは限りません。
レジリエンスは 「壊れたときに戻れるかどうか」 の話であり、壊れるまで見えにくい特性です。
レジリエンスの源は、 異なるメカニズムで、異なる時間スケールで機能する、複数のフィードバックループ です。
1つのループが失敗しても、別のループが機能する。
この冗長性がレジリエンスを支えています。
開発チームで言えば、こんな多層構造です。
品質を守るフィードバックループの多層構造
第1層: コードレビュー
↓ 漏れたら
第2層: CI(自動テスト)
↓ 壊れていたら
第3層: ステージング環境での手動テスト
↓ それも漏れたら
第4層: 本番監視 + アラート
どれか1つが欠けても他がカバーできる。
この「層の厚さ」がレジリエンスです。
効率化がレジリエンスを削るメカニズム
効率化とは、多くの場合 冗長性の削減 を意味します。
- テストの一部をスキップして開発速度を上げる
- 在庫を最小限にして運用コストを下げる
- 1人のエンジニアが複数領域を兼任して人件費を抑える
通常時は、こうした効率化はうまく機能します。
ただし、想定外の事態が起きたとき、回復の余裕がなくなっている可能性があります。
ソフトウェア開発に置き換えてみます。
| 効率化の施策 | 通常時の効果 | 障害時のリスク |
|---|---|---|
| テスト省略 | 開発速度が上がる | デグレに気づけない |
| 属人化(特定の人だけが知っている) | 普段は素早く進む | その人が休むとチーム全体が止まる |
| 冗長なプロセスの削除 | 手間が減る | 障害時のセーフティネットが消える |
「無駄を省いた」「スリムにした」はずなのに、ちょっとした障害でチーム全体が止まる。
そんな経験があれば、それは冗長性の削りすぎかもしれません。
レジリエンスは「管理すべき資源」
レジリエンスは無限ではありません。使い続ければ枯渇します。
人体も病気が続けば免疫力が下がりますし、
チームも障害対応が続けばメンバーが疲弊します。
「効率」と「レジリエンス」はトレードオフの関係にあります。
重要なのは「どちらかを選ぶ」ではなく、 「どこでバランスを取るか」を意識的に決めること です。
エンジニアとして現場でできることを2つ挙げるとすれば、次の通りです。
- プロセスを削る判断をするとき、「これを削ったら何が壊れたときに困るか」と問いかけてみる
- 「効率化」の提案には、失われるレジリエンスを併記する習慣をつける
6. なぜチームは「勝手に」うまくいく時とダメな時があるのか -- 組織と構造
明確なルールがなくてもうまく回るチームと、ルールを決めても混乱するチーム。
その違いは何でしょうか。
ここでは 自己組織化 と 階層構造 という2つの視点で考えてみます。
単純なルールから生まれる秩序 -- 自己組織化
自己組織化 とは、システムが自らの構造をより複雑にしていく能力のことです。
開発チームで言えば、こんな現象が当てはまります。
- チームが自発的にペアプロを始める
- 障害対応の暗黙のフローができあがる
- 勉強会が有機的に立ち上がる
興味深いのは、自己組織化は「自由と実験」から生まれるという点です。
少数の単純なルール(「本番に影響するPRは2人以上のレビュー必須」など)から、
精巧なワークフローが自然に発達していく。
OSSコミュニティが好例です。
貢献ガイドラインという最小限のルールから、数千人規模のプロジェクトが運営されています。
逆に、ルールが多すぎると自己組織化は阻害されます。
「すべての判断にマネージャーの承認が必要」という仕組みでは、
チームが自発的に工夫する余地がなくなってしまいます。
入れ子の構造が効率を生む -- 階層構造
階層構造 とは、サブシステムがさらに大きなサブシステムに組み込まれている構造のことです。
階層構造がなぜ有効なのか、ソフトウェアのビルドで考えてみます。
| アプローチ | やり方 | 失敗したら |
|---|---|---|
| 全体を一度にビルド | 全モジュールをまとめてビルド・デプロイ | 1箇所の失敗で全体がやり直し |
| モジュールごとにビルド | 各モジュールを独立してビルド・テストし、最後に統合 | 失敗したモジュールだけやり直し |
階層構造は、失敗の影響範囲を限定します。
マイクロサービスアーキテクチャは、まさにこの設計思想です。
- サービスごとに独立してデプロイ・スケールできる
- 1つのサービスの障害が全体に波及しにくい(理想的には)
- サービス内部のコード(要素)よりも、サービス間のAPI契約(相互連結)が重要
セクション1で触れた「相互連結 > 要素」という重要度の順序が、
ここでも当てはまっているのが分かります。
部分最適の罠 -- サブオプティマイゼーション
ただし、階層構造にも落とし穴があります。
サブオプティマイゼーション(部分最適) です。
部分最適とは、各部分が自分の目標を追求した結果、
全体の目標が達成されなくなる現象のことです。
マイクロサービスでよくある部分最適の例を見てみます。
| 各チームの行動 | 結果 |
|---|---|
| 自分のサービスの可用性を最大化 | サービス間の連携テストがおろそかになり、統合障害が頻発 |
| 独自の技術スタックを選択 | チーム間の人材流動性が下がり、組織全体のレジリエンスが低下 |
モノリスとマイクロサービスの対比で考えると、構造的なトレードオフが見えてきます。
- モノリス: 部分最適が起きにくいが、変更影響範囲が大きい(レジリエンスが低くなりがち)
- マイクロサービス: 各部分の自律性が高いが、部分最適のリスクがある
どちらが良いかという話ではなく、
「全体最適と部分の自律性のバランスをどこに置くか」が設計判断 です。
階層構造はボトムアップで進化するのが自然な姿です。
上層の目的は、下層が仕事をしやすくすることにあります。
組織に当てはめれば、マネジメントの役割は「現場が仕事をしやすくすること」に他なりません。
おわりに -- 「構造を見る目」を持つということ
この記事では、現場の「なぜ」に答えるための道具を紹介しました。
- ストックとフロー: 蓄積と変化の速さで問題を捉える
- バランシング・ループ: 安定させようとする力
- リインフォーシング・ループ: 加速させようとする力
- フィードバックの遅れ: 問題を拡大する要因
- レジリエンス: 効率とのトレードオフで意識的に管理する
- 階層構造: 部分最適と全体最適のバランス
明日から使える問いかけを、最後に4つ残しておきます。
- 問題が起きたとき → 「誰のせい?」ではなく「どんな構造がこの問題を生んでいる?」
- 同じ問題が繰り返されるとき → フィードバックループを探す
- 変化が遅いと感じるとき → ストックの慣性を疑う
- 効率化が裏目に出たとき → 削られたレジリエンスを探す
構造が振る舞いを決める。
構造を見る目を持つことが、エンジニアとして問題を解決する最初の一歩になると、自分は考えています。