なぜ遡る必要があるのか?
※ 本記事はソフトウェア開発の経験が浅い駆け出しエンジニアの学生によるものです。内容の正確性は保証しません。記事の執筆にあたり、生成AIを用いています。自分が書いた文章は緑色のnoteで囲ってます!
ソフトウェア開発の歴史は、「増大し続けるシステムの複雑さ」といかに戦うかという歴史そのものです。
この記事では、プログラミングパラダイムがどのように進化し、現代のマルチパラダイム(オブジェクト指向と関数型の融合)へと至ったのか、その必然的な流れを「構造化プログラミング」まで遡って解説します。
意外とですが、プログラミングのパラダイム(主流の考え方や概念)の変遷について書いている記事が少ないです。ネットだとそのパラダイムシフトについていけない、もしくは古いパラダイムしか知らない人 vs 新しいパラダイムしか知らない人で喧嘩っぽくなってることも多いので、一応知っておくとそういった議論に巻き込まれなくて済んで良いかなーと。オブジェクト指向と関数型プログラミングで戦ってたり、アーキテクチャのあれこれで戦っている人とか、色々あります。
1. 混沌からの脱却:構造化プログラミング (1960s - 1970s)
💀 背景:スパゲッティコードの時代
初期のプログラミング(アセンブリ言語や初期の FORTRAN など)では、処理の流れを制御するために GOTO 文が乱用されていました。
プログラムの規模が大きくなるにつれ、あちこちに処理が飛ぶコードは 「スパゲッティコード」 となり、可読性と保守性は壊滅的でした。
GOTO文とは、何行目に飛んでね〜っていう可読性が低いものです。COBOL, FORTRAN,C, C++, BASICみたいな古い低レイヤーまで書ける言語にGOTOが残っていることが多いです。
ですが、C++, Cなどでは著しく可読性を下げるので、使うのはおすすめされてないっぽいです。自分は調べるまで見たことなかったです。
💡 解決策:制御構造の導入
この状況を一変させたのが、1968 年の Edsger W. Dijkstra による提言「Go To Statement Considered Harmful(GOTO 文は有害である)」と構造化プログラミングの概念です。
基本原理: あらゆるプログラムは、以下の 3 つの制御構造だけで記述できる
- 順次 (Sequence): 上から下へ実行する
-
選択 (Selection):
if、switchなどによる分岐 -
反復 (Iteration):
for、whileなどによる繰り返し
🎯 達成したこと
無制限なジャンプを排除し、プログラムを論理的なブロックとして扱えるようにしました。
これにより、コードの流れが予測可能になり、大規模なアルゴリズムの実装が可能になりました(C 言語や Pascal の隆盛)。
やっぱり、goto文でコードの可読性や保守性が低くなるので、ダイクストラさんがこういったパラダイムを導入したのだと思います。COBOLエンジニアの給料が高いみたいな話を巷では聞きますが、正直こういった言語を書きたいと思う人は少ないのかなーと思います。
2. 状態管理の危機:オブジェクト指向プログラミング (1980s - 1990s)
💀 背景:グローバル変数の地獄
構造化プログラミングによってロジックの流れは整理されましたが、GUI(グラフィカルユーザーインターフェース)の登場やシステムの大規模化に伴い、新たな問題が発生しました。
それは、「データ(状態)」の管理です。
多くの関数がグローバル変数を共有し変更し合う構造は、どこでデータが壊れたのかを追跡することを困難にしました。
自分は大規模でも、1ファイル1000-2000行のプログラムしか書いたことないので、そこまでグローバル変数が管理しにくくて、状態が分かりにくい!って思ったことはないですが、3000行ぐらいからそういう問題が起きるのかなーと思います。
💡 解決策:データと手続きのカプセル化
この複雑さに対抗するために普及したのが、オブジェクト指向プログラミング (OOP) です(Smalltalk, C++, Java)。
基本原理: 現実世界の事象を「モノ(Object)」としてモデル化する
-
カプセル化 (Encapsulation)
データ(状態)と、それを操作する手続き(メソッド)を一つのクラスの中に閉じ込めること。
外部からは許可されたメソッド経由でしかデータを触れないようにすることで、「誰がデータを変更したか」 を明確にしました。
状態を管理するために、オブジェクト指向的なパラダイムを入れたのは、いいんですが、GOTO文の二の舞になってしまったという感じです。
なぜなら、大規模になることを防ぐためにオブジェクトで管理しようとしているのに、オブジェクトごとにファイルを分けるわけですから、ファイル移動が激しくなりました。なんか本末転倒感があります。
この本末転倒感を後々、払拭するのが現在のReactの思想だと思います。コンポーネントとしてまとめてよう、機能+見た目+状態を一つのファイルにまとめてJSXにしよう、みたいなパラダイムはここのアンチが増えたからできてます。
🎯 達成したこと
- 再利用性: 継承やポリモーフィズムにより、コードの重複を削減。
- 保守性: 責務をオブジェクト単位に分割することで、巨大なシステムを人間が理解可能なサイズに分割統治しました。
ここが一番の問題です。オブジェクトが増えるとコード記述があちこち飛ぶので、はっきりいってクッソ保守がしずらいです。しかもそれぞれのオブジェクトが
別に、適度(これが難しいけど)にオブジェクト指向を導入する分には、メリットの方が多いと感じているのですが、javaといえば、オブジェクト指向でしょ!みたいな感じで、とりあえずのオブジェクト指向をしている現場が多く、本末転倒なのかなーとか思っています。わかりやすくするための抽象化なのに、やりすぎて、難しくなりすぎる。オブジェクト指向自体は、すごい概念ですが、人間の頭が追いついてないみたいな感じで、実務だとそこまで厳密にやるとわかりづらいのかな。
ある程度、状態の変化が激しいところはオブジェクト同士ではなく、大きな一つのオブジェクトとして内部でまとめる方が圧倒的にわかりやすいです。
3. 並行処理の壁と信頼性:関数型プログラミングの再評価 (2000s - 現在)
💀 背景:ムーアの法則の限界と副作用
2000 年代以降、CPU のクロック周波数の向上は頭打ちとなり、パフォーマンス向上の鍵はマルチコア・分散処理へと移行しました。
ここで OOP の「状態を持つ(Mutable State)」という特性が足かせとなります。
複数のスレッドが同時に一つのオブジェクトの状態を変更しようとすると、競合状態 (Race Condition) やデッドロックが発生しやすくなるのです。
ここら辺は知らないです、そうなんですかね。
💡 解決策:不変性と参照透過性
ここで、学術界や一部のコミュニティで使われていた関数型プログラミング (FP) の概念が、実用的な解決策として注目されました(Haskell, Erlang, Scala の登場)。
基本原理: プログラムを数学的な関数の評価として記述する
-
不変性 (Immutability)
一度作ったデータは変更しない。変更が必要なら新しいデータを作る。
これにより、スレッドセーフがデフォルトで保証されます。 -
副作用の排除
関数の出力は入力のみによって決まる(参照透過性)。
これにより、テストが容易になり、挙動が予測可能になります。
🎯 達成したこと
複雑な並行処理やデータ変換ロジックを、安全かつ宣言的(What to do) に記述できるようになりました。
なんか一部の研究者がそういっているだけで、持ち上げすぎな気もする、、、
でも実際、一部の厳密な?変数の管理が必要な場合は、関数型プログラミング言語を使ってたみたいです。
数学界隈?みたいなところだと、多分言語の綺麗さだとかですごい持ち上げられている?まあ概念自体は面白い?けど全然、実用性を感じない。
その反面、厨二病みたいな原理主義者も多く沸いているイメージです。
実際に、そういう人にリアルであったことないので、会ってみたい。
4. 現代の到達点:マルチパラダイムによる統合
🤝 対立から融合へ
かつては「OOP vs FP」といった対立構造で語られることもありましたが、現代のモダンな開発においては、これらは相互補完的な関係にあります。
ここの対立が結構ネットで見かける対立です。
一長一短ではあるんだろうけど、実際じゃあ実務でどうやって使い分けるの?みたいなところで言うと、答えが出るまでに長い経験が必要だと思います。多分、ほとんどのエンジニアが答えられない気がする、知らんけど。
多くの現代的な言語(Scala, Kotlin, TypeScript, Swift, Rust, C# など)は、マルチパラダイム言語 として設計されています。
ここら辺の言語は、実用性!!!!実務で使える!!!!みたいなところで、関数型とオブジェクト指向型のいいとこどりをしています。だから人気です。
🏗️ 現代的なアーキテクチャの標準形
両者の「いいとこ取り」をする設計が、現在のデファクトスタンダードになりつつあります。
| 領域 | 採用するパラダイム | 理由 |
|---|---|---|
| システム構造・コンポーネント定義 | オブジェクト指向 (OOP) | ドメインモデルの表現、依存性の注入 (DI)、インターフェースによる抽象化には、クラスベースの設計が適しているため。 |
| データ処理・ロジック記述 | 関数型 (FP) | データの変換、フィルタリング、非同期処理の連鎖などは、不変データと高階関数(map / filter / reduce)を使った方が簡潔でバグが混入しにくいため。 |
この表から分かる通り、それぞれ分かりやすく書ける分野が違うので、使い分けましょう〜ってことが結論になるのかな?と思います。
でも、以前としてその使い分けは、これ!と汎用的に言える基準みたいなのが多く、難しいです。
TPOによるとしか言えないので、使い分けについては経験と深い洞察が必要です。
そこら辺をまとめている人がいたら知りたいですね。
📝 コード例:融合のイメージ (Scala/TypeScript 風)
// 構造(OOP):データ構造をクラス/型で定義
case class User(id: Int, name: String, isActive: Boolean)
class UserRepository {
// ロジック(FP):データ処理は関数型のアプローチで記述
def getActiveUserNames(users: List[User]): List[String] = {
users
.filter(user => user.isActive) // 宣言的:条件に合うものを抽出
.map(user => user.name) // 宣言的:名前だけを取り出す
}
}
javaの後継?みたいな言語のscalaが結構いいところどりの言語としては最初で有名みたいです。
まとめ
プログラミングパラダイムの変遷は、流行り廃りではなく、ハードウェアの進化とソフトウェアの巨大化に対する「生存戦略」 でした。
-
構造化プログラミングは、無秩序なロジックに秩序を与えました。
-
オブジェクト指向は、肥大化する 状態(データ) 管理に秩序を与えました。
-
関数型プログラミングは、複雑化する並行処理とデータフローに秩序を与えました。
私たちは今、これらの強力な武器をすべて使える恵まれた時代にいます。
重要なのは「どれが優れているか」を決めることではなく、「目の前の課題に対して、どのパラダイムの道具を使えば最もシンプルに解決できるか」 を見極める力です。
以上になります。まあ、ネットでそう言う対立があったら、大抵の場合は、どちらかがパラダイムシフトへの変遷について知らないとか、実用性ではなく綺麗な記述に価値を起きすぎているとか、そう言う場合が多いので、実務で役にたつのは今回の記事でのまとめを参考にして、そう言う極端なのは面白いけど無視するべきかなーと思います。AIが言っていることは結構正しい。