エドガー・W・ダイクストラの構造化プログラミングの考えは、チームや機能、システムが成長しても、規律あるシンプルなコードが正しく保たれ、保守可能であり続ける理由を説明する。

ソフトウェアが失敗するのは、書けないからではまれにしかありません。失敗するのは、1年後に誰も安全に変更できなくなるからです。
コードベースが大きくなると、あらゆる「小さな」変更が波及します:バグ修正が遠くの機能を壊す、新しい要件が書き直しを強いる、単純なリファクタが一週間の綿密な調整に変わる。問題はコードを追加することではなく、周囲が変わっても振る舞いを予測可能に保つことです。
エドガー・ダイクストラは、正しさと単純さを単なる“良いもの”としてではなく、最優先の目標にすべきだと主張しました。その効果は学問的な議論に留まりません。システムの理解が容易になると、チームは火消しに費やす時間を減らし、構築に集中できます。
人々がソフトウェアの「スケール」を語るとき、多くはパフォーマンスを指します。ダイクストラの指摘は別物です:複雑さもスケールするのです。
スケールは次のように現れます:
構造化プログラミングは単に厳格さを求めるものではありません。次の二つを簡単に答えられるように制御フローと分解を選ぶことです:
振る舞いが予測可能であれば、変更は危険ではなく日常になります。だからダイクストラは今でも重要です:彼の規律は成長するソフトウェアの本当のボトルネック——十分に理解して改善できること——を狙っています。
エドガー・W・ダイクストラ(1930–2002)はオランダの計算機科学者で、信頼できるソフトウェアを作るための考え方に影響を与えました。初期のオペレーティングシステムに携わり、最短経路アルゴリズムなどの成果を残し、日常の開発者にとって最も重要なのは「プログラミングは結果が出るまで試すものではなく、論理的に説明できるものであるべきだ」という考えを推進したことです。
ダイクストラは、プログラムがいくつかの例で正しい出力を出すかどうかよりも、重要なケースについて「なぜ正しいか」を説明できるかを重視しました。
コードの目的を述べられるなら、そのコードが実際にそれを行うことを段階的に議論できるはずです。この考え方は、読みやすく、レビューしやすく、英雄的なデバッグに依存しないコードにつながります。
ダイクストラの文章は厳格に感じられることがあります。彼は「巧妙な」トリック、いい加減な制御フロー、推論を難しくするコーディング習慣を批判しました。厳格さはスタイルの監視ではなく曖昧さを減らすことが目的です。コードの意味が明確であれば、意図の議論に時間を費やす代わりに振る舞いの検証に時間を使えます。
構造化プログラミングは、プログラムをごちゃごちゃしたジャンプではなく、順序、選択(if/else)、反復(ループ)のような限られた明確な制御構造から組み立てる実践です。目的は単純:プログラムの経路を理解しやすくして、自信を持って説明、保守、変更できるようにすることです。
人々はしばしばソフトウェア品質を「速い」「美しい」「機能豊富」と表現します。ユーザーが体験する正しさは違った形で現れます:アプリが驚かさないという静かな自信です。正しさがあれば誰も気にしません。欠けていれば他はどうでもよくなります。
「今動く」はいくつかの経路を試して期待した結果が得られたことを意味します。「ずっと動く」はリファクタ後や新しい統合、高負荷、別の開発者が触った後でも意図通りに振る舞うことを意味します。
ある機能は「今動く」一方で脆弱かもしれません:
正しさはこれらの隠れた仮定を取り除くか、明示的にすることです。
小さなバグはソフトウェアが成長するとほとんど小さいままでいません。ひとつの不正確な状態、オフバイワン、曖昧なエラーハンドリングルールが新しいモジュールにコピーされ、他のサービスに包まれ、キャッシュされ、再試行され、回避策で覆われます。時間が経つとチームは「何が真か?」を問う代わりに「普段はどうなるか?」を前提にするようになります。そこでインシデント対応が考古学になります。
増幅の要因は依存関係です:小さな誤動作が多くの下流の誤動作に拡大し、それぞれが部分的な修正を生み出します。
明確なコードはコミュニケーションを改善するため、正しさを高めます:
正しさはこうです:我々がサポートすると主張する入力と状況に対して、システムが一貫して約束した結果を生み出し、できない場合は予測可能で説明可能な方法で失敗すること。
単純さはコードを「かわいく」したり最小化したりすることではありません。振る舞いを予測し、説明し、恐れずに変更できるようにすることです。ダイクストラが単純さを重視したのは、それがプログラムを論理的に考えやすくするからであり、特にコードベースとチームが成長したときに効きます。
単純なコードは同時に扱うアイデアの数が少ない:明確なデータフロー、明確な制御フロー、明確な責務。読者に多数の代替経路を頭の中でシミュレートさせないコードです。
単純さは以下ではありません:
多くのシステムが変更困難になるのは、ドメインが本質的に複雑だからではなく、偶発的複雑さを導入してしまうからです:相互に作用するフラグ、削除されない特例パッチ、以前の決定の回避のために存在する層。
各特例は理解に対する税です。コストは後で現れます:誰かがバグを直そうとして変更すると、一部を変更しただけで他が微妙に壊れることに気づくのです。
設計が単純なら進歩は着実な作業から来ます:レビュー可能な小さな差分、緊急修正の減少。チームはすべてを記憶する“ヒーロー”に頼る必要がなくなり、通常の人間の注意範囲でシステムが支えられます。
実用的なテスト:例外(「ただし…」「〜の場合を除く」「この顧客のみ」)を増やし続けているなら、偶発的複雑さを蓄積しています。振る舞いの分岐を減らす解決を好んでください。たとえその一貫した規則が最初に想像したより一般的でも、五つの特例よりは一つの一貫した規則が望ましいことが多いです。
構造化プログラミングは実行経路を追いやすく書くという単純なアイデアで、大きな影響を持ちます。多くのプログラムは三つの構成要素—sequence(順序)、selection(選択)、repetition(反復)—から構築できます。絡まったジャンプに頼る必要はほとんどありません。
if/else, switch)。for, while)。これらの構造から制御フローを構成すると、上から下へ読めばプログラムの動作を説明できることが多く、ファイル中を“瞬間移動”する必要が減ります。
構造化プログラミングが標準になる前、多くのコードベースは任意のジャンプ(古典的な goto スタイル)に頼っていました。問題はジャンプ自体が常に悪いのではなく、無制限のジャンプが実行経路を予測困難にすることです。すると「ここにはどうやって来たか?」「この変数の状態は?」といった問いにコードが明確に答えられなくなります。
明確な制御フローは、人間が正しいメンタルモデルを作るのを助けます。そのモデルはデバッグ、プルリクのレビュー、時間制約の下で振る舞いを変えるときに頼るものです。
構造が一貫していると変更は安全になります:ある分岐を変えても別の分岐に影響しにくく、ループをリファクタしても隠れた抜け道を見落としにくい。可読性は単なる美学ではなく、既存の動作を壊さずに振る舞いを変える基盤です。
ダイクストラは単純な主張をしました:コードが正しい理由を説明できれば、それを恐れずに変更できるということ。三つの小さな推論ツールが実務で有効です——チームを数学者にする必要はありません。
不変条件は、特にループ内でコードが実行されている間に常に成り立つ事実です。
例:カート内の価格を合計しているとします。役に立つ不変条件は「total はこれまで処理した項目の合計に等しい」です。その不変条件が各ステップで保たれていれば、ループ終了時の結果は信頼できるものになります。
不変条件は「次に何が起きるべきか」ではなく「決して壊れてはいけない事実」に注意を集中させるため強力です。
前提条件(precondition) は関数が実行される前に成り立っているべきことです。事後条件(postcondition) は関数が終わった後に保証することです。
日常例:
コードでは、前提条件が「入力リストがソート済み」で、事後条件が「出力リストはソート済みで、元の要素に挿入要素が加わっている」といった具合です。
これらを(非形式的でも)書き留めると設計が鋭くなります:関数が期待することと約束することを決めるので、自然と小さく焦点の合ったものになります。
レビューでは議論がスタイル(「こう書きたい」)から正しさ(「この不変条件を維持しているか?」「前提条件を強制するか文書化するか?」)へと移ります。
形式的な証明は不要です。バグが多いループや複雑な状態更新を一つ選び、その上に一行の不変条件コメントを追加してください。将来誰かが編集する際、そのコメントはガードレールのように働きます:もし変更でその事実が壊れるなら、コードは安全ではなくなります。
テストと推論は同じ結果(意図した振る舞い)を目指しますが、アプローチが異なります。テストは例を試して問題を発見します。推論は論理を明示化して問題のカテゴリを予防します。
テストは実用的な安全網です。回帰を捕まえ、現実的なシナリオを検証し、チーム全員が実行できる形で期待を文書化します。
しかしテストはバグの存在を示せるだけで、存在しないことは示せません。すべての入力、すべてのタイミング変動、すべての機能間の相互作用を網羅することは不可能です。多くの「自分の環境では動く」問題は未テストの組み合わせ——稀な入力、特定の操作順序、いくつかのステップを経て初めて現れる微妙な状態——から起きます。
推論はコードの性質を証明することに関係します:「このループは常に終了する」「この変数は決して負にならない」「この関数は無効なオブジェクトを返さない」など。うまく行えば境界やエッジケースに関する欠陥の大きな分類を排除できます。
制約は労力と適用範囲です。製品全体に対する完全な形式証明はほとんど経済的ではありません。推論は選択的に使うのが有効です:コアアルゴリズム、セキュリティに関わるフロー、金銭処理、並行処理など失敗コストが高い箇所。
テストを広く使い、失敗が高コストな場所には深い推論を適用します。
両者の橋渡しとなる実用的手法は、意図を実行可能にすることです:
これらはテストの代わりではなく、網を締める技術です。漠然とした期待を検査可能なルールに変え、バグを書きにくくし、診断を容易にします。
「巧妙」なコードは瞬間的には勝利に感じられます:行数が少ない、きれいなトリック、気分が良くなるワンライナー。しかし巧妙さは時間や人を跨いでスケールしません。6か月後に作者がトリックを忘れ、新しいメンバーが文字通り読んで隠れた仮定を見落とし、振る舞いを壊します。それが巧妙さ負債です:短期的スピードを長期的混乱で買うこと。
ダイクストラの要点は「退屈なコードを書け」ではなく、規律ある制約がプログラムを論理的に考えやすくするということです。チームでは制約が意思決定疲労を減らします。もし命名規則、関数の構造、「完了」の定義といったデフォルトが決まっていれば、毎回のプルリクで基本事項を再議論する必要がなくなり、その時間はプロダクト作業に戻ります。
規律は日常の習慣として現れます:
巧妙さ負債を蓄積しない具体的習慣:
calculate_total() は do_it() より優先)。規律は完璧さではなく、次に来る変更を予測可能にすることです。
モジュール性は単に「コードをファイルに分ける」ことではありません。決定を明確な境界の背後に隠し、他が内部の詳細を知らなくていいようにすることです。モジュールは厄介な部分(データ構造、エッジケース、性能上の工夫)を隠し、小さく安定した公開面を提供します。
変更要求が来たとき理想は:1つのモジュールが変わり、他は触られないこと。これが「変更を局所化する」の実用的意味です。境界は偶発的結合を防ぎます。ある機能を更新したら三つの他が壊れる、という状況を避けられます。
良い境界は推論も容易にします。モジュールが何を保証するか述べられれば、毎回実装全体を読み返さなくても大きなプログラムを論理的に扱えます。
インターフェースは約束です:「この入力を与えれば、この出力を作り、このルールを守る」。その約束が明確ならチームは並列に作業できます:
これは官僚的なことではなく、大きくなるコードベースで安全な協調ポイントを作ることです。
大規模なアーキテクチャレビューをする必要はありません。軽量なチェックを試してください:
よく引かれた境界は「変更」をシステム全体のイベントから局所的な編集に変えます。
ソフトウェアが小さいうちはすべてを頭の中に入れておけます。スケールするとそれは不可能になり、失敗モードが明らかになります。
よくある症状:
ダイクストラの中心的な賭けは「人間がボトルネックである」ということです。明確な制御フロー、小さく定義された単位、論理的に考えられるコードは審美的選択ではなく、容量を増やす手段です。
大きなコードベースでは、構造は理解のための圧縮のように働きます。関数に明示的な入出力があり、モジュールに名前を付けられる境界があり、ハッピーパスがすべてのエッジケースと絡み合っていなければ、開発者は意図を再構築する時間を減らし、意図的な変化に時間を使えます。
チームが大きくなるとコミュニケーションコストは行数より速く増えます。規律ある可読なコードは貢献に必要な部族知識を減らします。
これはオンボーディングで即座に現れます:新しいエンジニアは予測可能なパターンを辿り、少数の慣習を学び、長い“トラップ”の説明なしに変更できます。コード自体がシステムを教えてくれるのです。
インシデント時は時間が乏しく信頼が弱い。前提条件や意味のあるチェック、まっすぐな制御フローのあるコードはプレッシャー下で追跡しやすい。
さらに重要なのは、規律ある変更は巻き戻しもしやすいことです。小さく局所的な編集と明確な境界は、ロールバックが新しい障害を生む可能性を減らします。結果は完璧ではなくても、驚きが少なく、回復が速く、年と貢献者が増えても保守可能なシステムです。
ダイクストラの主張は「古い書き方で書け」ではなく「説明できるコードを書け」です。この考え方はすべてを形式証明にすることなく採用できます。
推論を安くする選択から始めてください:
良いヒューリスティック:関数が何を保証するかを一文で要約できないなら、多分やりすぎです。
大規模なリファクタは不要です。継ぎ目で構造を追加してください:
isEligibleForRefund)。これらは段階的な改善で、次の変更の認知負荷を下げます。
変更をレビュー(または作成)するとき、自問してください:
レビュワーがすぐに答えられないなら、コードは隠れた依存を示しています。
コードをそのまま説明するコメントは古くなります。代わりになぜ正しいかを書いてください:主要な仮定、守っているエッジケース、仮定が変わったら何が壊れるかの要点。短い注記(例:「不変条件:total は常に処理済み項目の合計」)は長い説明文より価値があります。
これらの習慣を収める軽量な場所が欲しいなら、共通のチェックリストを作って /blog/practical-checklist-for-disciplined-code にまとめてください。
現代のチームはAIで開発を加速しています。リスクはおなじみです:今日のスピードが説明できない生成コードによる明日の混乱になること。
ダイクストラ的にAIを使う方法は、AIを「構造的思考の加速装置」として扱い、置き換えにしないことです。たとえば、チャットでウェブやバックエンド、モバイルを作るようなプラットフォーム(例:Koder.ai)で作る場合、次のように意図とレビューを明確にしてください:
最終的にソースをエクスポートして別の場所で動かすとしても、生成されたコードは説明できるコードであるべきです。
これは軽量な「ダイクストラ寄り」チェックリストで、レビュー、リファクタ、マージ前に使えます。これは一日中証明を書くためではなく、正しさと明快さをデフォルトにするためのものです。
total は処理済み項目の合計" のような一行が助けになる。一つの乱れたモジュールを選び、まず制御フローを整理してください:
その後、新しい境界の周りに集中したテストを数件追加してください。関連パターンをもっと見たい場合は /blog を参照してください。
なぜコードベースが成長するときにボトルネックになるのは「書くこと」ではなく「理解すること」です。ダイクストラが重視した予測可能な制御フロー、明確な契約、正しさへの注力は、「小さな変更」が別の場所で予期せぬ振る舞いを引き起こすリスクを減らし、時間が経ってもチームが安全に変更できるようにします。
この文脈での「スケール」はパフォーマンスではなく、複雑さの増大を指します:
これらの力が働くと、理性的に考え予測する力が重要になります。
実務的には、構造化プログラミングは小さく明確な制御構造を優先することです:
if/else, switch)for, while)目的は堅苦しさではなく、実行経路を追いやすくして振る舞いを説明・レビュー・デバッグしやすくすることです。
問題は「無制限のジャンプ」が実行経路を予測不可能にする点です。制御が入り組むと「ここにはどうやって来たか」「変数はどんな状態か」といった基本的な問いに答えるのが難しくなり、保守が大変になります。
現代の類似例としては、深いネスト分岐、散在する早期リターン、暗黙の状態変更などがあります。
ユーザーが体験する「正しさ」とは目立たない機能です:期待通りに動くことが当たり前であり、欠けると他の全てが意味を失います。正しさとは、主張する入力・状況に対して一貫した結果を出し、できない場合は予測可能で説明可能に失敗することです。
依存関係が誤りを増幅するからです。小さな状態の誤りや境界条件のバグは、別モジュールにコピーされたり、キャッシュされ、再試行され、回避策で包まれます。時間が経つとチームは「何が真か?」を問わなくなり、「普段はどうなるか」を前提に動き、障害調査が考古学になってしまいます。
ここでの簡潔さは「同時に動いているアイデアの数が少ない」ことを意味します:明確な責務、データフロー、制御フロー、最小限の特例。行動を予測しやすくするための戦略であり、行数を減らすことや巧妙なワンライナーを賞賛することではありません。
試金石は、要件が変わったときに振る舞いが予測しやすいかどうかです。新しいケースごとに「ただし…」が増えるなら偶発的複雑さを蓄積しています。
不変条件(インバリアント)はループや状態遷移中に常に成り立つ事実です。実用的な軽量利用法:
total はこれまで処理した項目の合計に等しい)これにより後からの編集が安全になります。
テストは例を試してバグを見つけるのに優れています。推論(reasoning)はロジックを明示的にして特定の欠陥クラスを防ぐ力があります。テストは欠陥の存在を示せますが、不在は示せません。
実務的なバランスは:広範なテスト+重要箇所での深い推論。兵站としてはアサーション、前提/事後条件、ループ不変条件などを使って意図を実行可能にします。
小さな反復的な改善で始めましょう:
isEligibleForRefund のようなわかりやすい述語に置き換えるこれらはリファクタリングの大掛かりな中断なしに認知負荷を下げます。