『Clean Architecture』から学ぶ、知っておくと便利なプログラミングパラダイムの基本

2024/07/18に公開

こんにちは。最近ペルー産コーヒーのもつ豊かな酸味についてあらためて気づきました、天地人Webアプリケーションエンジニア兼エンジニアリングマネージャの高瀬(@k_tacafe)です。

今回のテーマは「プログラミングパラダイム」に関する話です。
最近、『Clean Architecture』積ん読から引っ張り出してきて初めてちゃんと 読んで感銘を受けました。本書の第3章から第6章では主要な3つのプログラミングパラダイムに関して言及されているのですが、よく耳にする代表的なプログラミングパラダイムの基本が再確認できるとともに、プログラム設計におけるそれぞれの価値についても理解が深まりました。気持ちが高まっているうちにまとめておきたいと思います。

知っておくと便利な3つのプログラミングパラダイム

プログラミングパラダイム をまずはwkipediaで調べると

プログラミングパラダイム(英: programming paradigm)とは、プログラミングにおける模範である。

と定義されていました。(2024年7月現在)

規範ですね~なるほど~!と思いつつ、もう少しやわらかい表現で把握できるように調べていったところ

「プログラミングパラダイム」とは、プログラミングの基本的な概念、考え方やルール、記述方法などの枠組みとなるものです。

という感じの理解でよさそうです。

調べてみると、世の中には非常に多くの「~プログラミング」というパラダイムがあるようですが、その中でも知っておくと何かと便利なものは 「構造化プログラミング」「オブジェクト指向プログラミング」「関数型プログラミング」 の3つです。

構造化プログラミング

このパラダイム以前のプログラムでは、goto文とラベル(ラベルの場所に次の処理が飛んでいく)を用いて分岐やループ処理を実装していたため、プログラム規模が大きくなると人間にはメンテナンスが辛い状態に陥るといった反省がありました。

エドガーダイクストラ(Edsger Wybe Dijkstra) 氏によって、プログラムに「制御構造(control structures)」が導入されます。これは「制御構文」とも呼ばれるもので、以下「順次」「選択」「反復」の3つの構造があります。

  • 順次(sequence): 命令を順番に実行する構造
  • 選択(selection): 条件に基づいて次に実行するプログラムを選択して分岐する構造
  • 反復(repetition): 一連の処理を繰り返し実行する構造

この3つの制御構造を主体としたプログラミングが「構造化プログラミング」であり、以下の恩恵がありました。

  • 分割統治: プログラムを小さなモジュールや関数に分割し、それぞれを個別に設計、開発、テストすることが可能
  • 入れ子構造(ネスト): 制御構造をネストすることによって、複雑な条件の選択処理がシンプルに表現することが可能
  • goto文の排除: 大規模なプログラムの保守性が高まった

現代においてもプログラミングを支える基本中の基本となるパラダイムですね。

オブジェクト指向プログラミング

オブジェクト指向プログラミング(Object Oriented Programming)とは何か?について、日常的にこれを使いこなせているつもりですが、改めて聞かれると明快に答えられないですよね。

「オブジェクト」は様々な文脈で用いられる広い概念ですが、プログラミングパラダイムにおける「オブジェクト」は データ(属性)と機能(メソッド)を持ち、何らかの操作の対象となるもの であり、相互に作用しあうオブジェクトを組み合わせてプログラムを設計するのが「オブジェクト指向プログラミング」というパラダイムです。Object Oriented Programmingの頭文字を取って OOP と書かれることも多いです。

オブジェクト指向プログラミングにおける一番代表的なスタイルが「クラスベースOOP」です。オブジェクトのデータや機能(「状態」と「振る舞い」とも呼ばれる)を定義した設計図のようなものを「クラス」として扱い、そのクラスから生成(インスタンス化)された実体をオブジェクト(クラスインスタンス)として扱います。

そしてこの「クラス」を用いて 「カプセル化」「継承」「ポリモーフィズム」 といった強力なメカニズムを容易に扱うことができます。

  • カプセル化(Encapsulation)

    • カプセル化は、データと機能を一つの単位(オブジェクト)にまとめることで、外部から直接アクセスできないようにすることです。これにより、データの不整合や不正アクセスを防ぎます。
  • 継承(Inheritance)

    • 継承は、既存のクラス(親クラスまたはスーパークラス)の特性を新しいクラス(子クラスまたはサブクラス)が引き継ぐことです。これにより、コードの再利用が容易になります。
  • ポリモーフィズム(Polymorphism)

    • ポリモーフィズムは「多態性」と和訳され、異なるクラスのオブジェクトが同じインターフェースを持つメソッドを共有することです。これにより、異なるオブジェクトを一貫した方法で扱うことができます。

※ これらは必ずしもOOPだけに備わっている特性ではありません。
※「カプセル化」「継承」「ポリモーフィズム」それぞれについてのより詳しい解説は、他の技術記事をご参考ください。

一般的には、オブジェクト指向プログラミングの武器として上記3つが強調されますが、『Clean Architecture』 の中では「ポリモーフィズム」こそ最も価値が高いことを主張しています。

とまで言い切るほどです。プログラミング設計の観点で、これが如何に激熱ポイントなのかはまた別の機会に詳しく紹介したいと思います。

関数型プログラミング

「関数型プログラミング」とは、 参照透過性 という性質を持った「関数」を主軸としたプログラミングスタイルです。参照透過性 とは関数に対して同じ値を与えた場合に、常に同じ結果が返されるという性質です。

例えば、以下のような add2 関数は、 add2(2) として 2 を与えると必ず 4 という結果が返されるので参照透過性があります。

def add2(a):
  a += 2
  return a

print(add2(2)) # 4
print(add2(2)) # 4
print(add2(2)) # 4

以下のようなcount_up()関数は、(同じプロセス内で)呼び出す度に結果が変わるため、参照透過性を持った関数ではありません。

count = 0
def count_up():
    global count
    count += 1
    return count

print(count_up()) # 1
print(count_up()) # 2
print(count_up()) # 3

参照透過性を持った関数が主軸のプログラミングスタイルは、どの関数も「状態」を持たないことが保証されており、「データに(関数を通じて)何らかの処理を行い、他のデータに加工する」という記述(「式を評価する」とも呼ばれる)が中心となります。これにより

  • プログラムのテストが実装しやすい
  • 安全で予測可能なプログラムが構築しやすい

などのメリットがあります。

一昔前は、Python, JavaScript, Ruby, C#, Java, C++などの「オブジェクト指向プログラミング言語」が人気を集めていました。近年人気を集めているRustGoといったモダンなプログラミング言語は、性能面で秀でているのはもちろんですが、所謂「オブジェクト指向言語」ではなく、オブジェクト指向的なアプローチも関数型プログラミング的なアプローチも可能な「マルチパラダイム言語」としての側面があり、広く使われ始めた一因になっているのではないでしょうか。

新しい言語だけでなく「モダンなJavaScript」と呼ばれるES6以降のJavaScriptでも、関数型プログラミング的な追加機能が盛り込まれましたね。

// 従来のJavaScript
function add2(a) {
  a += 2;
  return a
}

////////////////////////////////
// ES6以降の関数型プログラミング的な機能の例

// アロー関数
const add2 = a => a += 2;

// 配列のメソッド
const even = numbers.filter(n => n % 2 === 0);

昨今はオブジェクト指向プログラミングの良いところを活かしつつ、関数型プログラミングの良いところも多く活用できるようなアップデートが、JavaScriptだけでなくPythonなど様々なプログラミング言語で見られます。人類のソフトウェアエンジニアリングの進歩を感じるとともに、私たちのプログラミング体験がもっと豊かになていくと思うとワクワクしますね!

そして伝説(Clean Architecture)へ

これまでは3つのプログラミングパラダイムについて概観してきましたが、今日の本題は実はここからです。

これらのプログラミングパラダイムがもたす真の価値は、プログラミングにおいて「新しい能力を提供する」ことではなく、我々から何かの「能力を削除している」ことであり、プログラマに「何をすべきでないか」を伝えることにある、と『Clean Architecture』は主張しています。


構造化プログラミングでは「順次」「選択」「反復」とう制御構造がもたらされていますが、これは我々が 「goto文」を使わなくても良くなった(奪われた) ということです。

すなわち、goto文を使ってあっちこっちに制御を飛ばさずに制御構造を使うべし!という規律です。


オブジェクト指向プログラミングでは、データと機能を持ち合わせた「クラス(オブジェクト)」を扱うことが出来ますが、これは我々が 「関数ポインタ」を奪われた ということになります。必要な処理の主体となる関数はオブジェクト内に隠蔽されており、我々はオブジェクトに用意されたメソッドを呼び出すことで、処理の主体となる関数(その関数を参照するメモリのアドレス = 関数ポインタ)に対して間接的にアクセスすることになるからです。

すなわち、必要な処理を行うための関数をあなたが直接操らなくて良いです。必要なことはオブジェクトにお願いしちゃいましょう!という規律です。


関数型プログラミングでは、参照透過性を持った関数呼び出しを重ねることで意図しないデータ変更が発生しない安全なプログラムを書くことができますが、これは我々が 代入を奪われた ということになります。変数にデータを保存することは可能ですが、その変数は不変な(値を更新されない)ものとして扱うことが原則です。

すなわち、一度用意したデータは(極力)書き換えてないようにしましょう!という規律です。

これら3つのパラダイムがもたらす「規律」たちが本書の第Ⅲ部へと繋がり、かの有名な SOLIDの原則 を始めとする数々の「設計の原則」に関する学びが展開していきます。その先の話はまたの機会にしたいと思います。

まとめ

保守性や拡張性が高く、良い設計のプログラムを組めるように切磋琢磨しておりますが『Clean Architecture』はソフトウェアの設計における有用な哲学や原則について学ぶことができる名著ではないでしょうか。

本書の前半部分(第Ⅱ部)では、主要な3つのプログラミングパラダイムについて、それぞれが成立した歴史的背景やパラダイムの特徴とともに、ソフトウェア構造にもたらした本質的な価値に対する鋭い洞察を与えています。

プログラミングパラダイムは「新しい武器」をもたらすものではなく、人類がソフトウェアというものをより上手に扱えるようになるための知見の結晶であり、「これはやらない方が良いかもね」という反省の集大成である、という切り口が非常に面白いと思いました。

第6章の結びの言葉として、このようにソフトウェアの本質を喝破している一方で、

「序文」の結びとしているこの言葉は『Clean Architecture』という本の価値を再認識できる力強い言葉であり、改めて深い感銘を受けました。


株式会社天地人では、人工衛星などの宇宙ビッグデータを活用し、地球規模の課題に取り組むためのオンラインGISプラットフォーム天地人コンパスを開発しています。

私たちと一緒に天地人コンパスを開発してくれる仲間を募集しております。ご興味のある方は以下のページよりエンジニアリングの募集の求人にてご確認下さい。

https://www.wantedly.com/companies/company_5025838/projects

Discussion