🚀 ニフティ’s Notion

設計の目的・オブジェクト指向とは?

目次

設計(アーキテクチャ)の目的

ソフトウェアアーキテクチャの目的は、変更容易性を高めるためにある

  • 変更の必要な労力が少なく、システムのライフサイクル全体で低く保たれているなら、その設計は優れている
  • 変更を加えていけばいくほど、コードが汚くなっていき、変更が難しくなっていく
    • ソースコードのエントロピーが増大していく
開発・保守の中で起きるソフトウェアの変更を楽にしたいという気持ちによって生まれるのが設計
設計を行わない弊害
  • 仕様変更でコードを変更すると別の場所でバグが発生した
    • 処理と処理が依存し合っているため、変更を加えると別の処理が壊れる
  • 別の場所でバグが発生しないかどうか、変更の影響範囲を調べていたら日が暮れてしまった
    • 処理を追うのにどこのコードを読んでいけばいいかわかりづらい
  • バグを埋め込みやすい
    • 変更による影響範囲が予測しにくい、思わぬところにバグを埋め込んでしまう
  • 簡単な仕様変更・バグ修正に何日もかかった
    • どこの処理を変えればいいのかその調査に時間がかかったりする
レガシーコード・技術的負債
  • 設計をおこなわなければ、いずれコードがレガシーコードになり、技術的負債になる
  • エンジニアの技術力向上につながらず、モチベーションが下がる
何が悪かったのか?
image block
  • 優れた・クリーンな・うまく設計されたコードの重要性を理解していない
    • 〆切まで時間がないので、とりあえず動くものを作ろうという気持ちになる
      • 「とりあえずモノを完成させて、後でコードを綺麗にするか」と言って、結局時間がなくやらない
    • 競合に追いつくためには機能を追加し続けなければならない
      • 今、この間にも競合はどんどん先を行っている
      • 後からコードを綺麗にする時間なんてない
  • 崩壊したコードを書けば短期的には開発速度が上がると信じている
    • 最初はそれでいいかもしれないが…
    • どんどん変更が難しくなり、長期的には開発速度が下がる

ソフトウェアの価値

ソフトウェアには2つの価値がある

  • 振る舞い
  • アーキテクチャ
振る舞い
  • 振る舞いとはシステムの具体的な動作のこと
    • ユーザー登録処理だったり、コメントを投稿する処理だったり
  • 外から見えやすく、とりあえず動くものを作ろうということで、アーキテクチャよりも振る舞いを優先しがち
    • 結果として変更のコストがメリットを上回り、事実上変更できないシステムが生まれる。(ソフトウェアの「ハードウェア化」)

アーキテクチャ
  • アーキテクチャとは、システムの構造のこと
    • IT分野では〇〇アーキテクチャがたくさん存在する
      • インフラであればインフラアーキテクチャ
      • ネットワークであればネットワークアーキテクチャ
    • 〇〇についての構成要素、依存関係について示したものと考えれば良い
  • 今回で言うと、アーキテクチャはソフトウェアアーキテクチャにあたる
    • つまり、ソフトウェアの内部構造について考えていきましょうということ
    • どのようにコードを整理するかルールを考えていく
    • 決めておいたアーキテクチャを崩さないようコードを書いていく
  • 整理整頓された環境だと作業しやすいよという当たり前の話
  • システム開発では、最初はやりやすいが、後半になればなるほど改修がやりにくくなりがち
    • アーキテクチャを決めておくと崩壊しにくい
ℹ️
アーキテクチャを考えないと、長期的に大幅なコストアップにつながる

・雑な実装より、正しく実装した方が結局早い

・アーキテクチャは価値であり、変更不能なシステムになるのを回避する

ソフトウェアアーキテクチャ

ソフトウェアアーキテクチャのモチベーション
  • 以下が達成されるようなルールを考え、変更しやすいシステムを作る
    • ソフトウェアが持つ複雑性を軽減させる
      • 業務で扱うようなシステムは仕様がややこしい
      • 問題を細かく分割して、人間にとって考えやすくする
    • 分割した要素同士の関係性をコントロールする
      • 要素の関係性を整理して、変更の影響度を小さくする
アーキテクチャの導入
  • コーディングに入る前に、アーキテクチャを考えていく
  • アーキテクチャを1から考える必要はなく、先人たちが考えたパターンを参考にしていく
    • システムやサービスの規模・特性や、エンジニアの成熟度、アーキテクチャ自体の学習難易度、使用言語の特性などを考えて、適切なパターンを選択する
  • 自分達のシステムに合わせてカスタマイズしていく
    • 考えたアーキテクチャは図で可視化して、他人と議論しやすくする
  • 最初から正解に辿り着こうと思わず、開発を進めていく中で理想的なアーキテクチャに近づけていく
    • 後からアーキテクチャを変更できるように作っておくことも重要
      アーキテクチャには正解も間違いもない。ただトレードオフがあるだけだ。(Neal Ford)
  • アーキテクチャの例
    • クリーンアーキテクチャ
    • ヘキサゴナルアーキテクチャ
    • MVCアーキテクチャ
image block
クリーンアーキテクチャ
graph LR
  Model --> Controller
	Controller --> Model
  View --> Controller
	Controller --> View
MVCアーキテクチャ

アーキテクチャの共通思想
  • どのパターンも共通しているのは、(1)責務を考え分離する、(2)依存関係を考えるということになる
責務を考える
  • 対象物の果たすべき役割がなんなのかを考え、余計な処理を入れないようにしたり、ロジックがバラバラにならないようにする
  • 責務を考える対象
    • 関数・クラス・変数・構造体
    • 機能ごとに関連する関数・クラス・変数・構造体などをまとめたグループ
      • 例えばレイヤードアーキテクチャでは以下のようなグループに分けるとされている
UI層 表示に関する要素を入れる HTTP
Application層 Domain層やInfrastructure層と協働して、ユースケースを実現する要素を入れる ユーザー登録、認証など
Domain層 システムの根幹となる概念をモデリングした要素を入れる ユーザー、IDに関する情報など
Infrastructure層 技術詳細に関する要素を入れる RDBやAPIへのアクセスなど
image block
  • 各要素・グループは自分の責務だけを全うすることに集中し、余計なことはしない
    • 例えば、画面に文字列を表示する関数にファイルを削除する副作用を入れる
    • 思わぬバグにつながる
  • 責務を分離するメリット
    • 改修をする際に、どこを変更すればいいか見通しが立てやすい
    • エンジニア同士で、どこに手を入れればいいか共通認識を生みやすくなり、開発が効率化できる
    • ソフトウェアの複雑度を減らすことができる
依存関係を考える
  • 依存関係を考えるというのは、変更の影響範囲が小さくなるように、グループ同士の関係性を検討しようということ
    • 変更を楽にするためには依存関係をコントロールする必要がある
    • ソフトウェアは一つのもので完結することは少ない
      • 必ず依存したり、依存されたりする
    • 依存関係は矢印で表現する
      • AはBに依存している(AにとってはBが必要)
        • AやBは関数、変数、クラスなどのコード上に出てくる要素だと思ってください
        graph LR 
          A --> B
    • 依存の矢印の逆は変更の影響範囲を示している
      • Bが変更されるとAを変更する必要が出てくることを示している
      • コードを修正すると別の部分が壊れることがあるが、このせい
      graph LR 
        A -- 依存 --> B
        B -. 変更の影響 .-> A
      graph LR
        A -- 依存 --> B
        B -- 依存 --> C
        C -- 依存 --> D
        D -. 変更の影響 .-> C
        C -. 変更の影響 .-> B
        B -. 変更の影響 .-> A
      
    • 依存関係によっては変更を加えた際に辛くなる
      • (例) 循環参照
        • AはBに、BはAに依存している
        • 変更の影響も相互に矢印が向いているので、修正が大変になる
          • Bを変更して、Aを変更して、Bを変更して、Aを変更して、…
          • 何度も修正する必要が出てくる
        graph LR
          A -- 依存 --> B
          B -- 依存 --> A
        graph LR
          A -. 変更の影響 .-> B
          B -. 変更の影響 .-> A
  • 依存関係が複雑にならないように考えていく
    • 依存関係によって、要素が破壊・変更された際の影響範囲が変わってくる
    • 人間にとって依存関係を追いやすい構造にする
  • 依存関係を考えるメリット
    • 変更した際の影響範囲を小さくなるようにすると、安心して変更できるようになる
👍
アーキテクチャのパターンはさまざまありますが、責務と依存関係についてどう取り扱っていくかが変わっていきます
アーキテクチャ設計の正解はなく、最善だと思われる物を選択していきましょう

プログラミングパラダイム

  • アーキテクチャを実装する上で、プログラミングパラダイムについて考えてみる
    • プログラミングパラダイムとは、プログラミングの際の基本的なスタイル、概念のこと
  • プログラミングパラダイムは大きく分けて3つある
    • 構造化プログラミング
    • オブジェクト指向プログラミング
    • 関数型プログラミング
  • 現代のプログラミング言語の多くは複数のプログラミングパラダイムを取り入れている
    • そのような言語をマルチパラダイム言語と呼ぶ
    • オブジェクト指向ではないけどオブジェクト指向っぽい、関数型ではないけど関数型っぽくかけるというように、一部を取り入れることも多い
構造化プログラミング(ほとんどの言語)
  • 頻出パターンをif/elseやfor/whileなど、特定の制御構文で置き換え
  • goto文によって、直接的な処理の移動を禁止
    • スパゲッティプログラムによるコードの崩壊を防止

直接的な制御の移行 に規律を課した

オブジェクト指向プログラミング(Pythonなど)
  • データアクセスの制限・実装の隠蔽
  • 安全に関数ポインタを取り扱う
  • ポリモーフィズムの発見

間接的な制御の移行 に規律を課した

関数型プログラミング(Haskellなど)
  • 変数の再代入を禁止・または大きく制限
    • 変数の不変化
    • 並列処理に強み

代入 に規律を課した

💸
なぜプログラミングパラダイムは生まれたか?
  • いずれもプログラマができることを 制限 するもの
    • goto、代入、関数ポインタを奪う
  • 何をすべきかではなく、 何をすべきでないか を伝えている
  • 仕組みによって、人間の過ちを防止する

オブジェクト指向

システム全体を、モノに見立てた「オブジェクト」と呼ばれる構成単位の組み合わせとして捉え、システムの振る舞いをオブジェクト間の相互作用としてとらえる考え方。

複雑なシステム記述、巨大なライブラリ(特に部品間で緊密で複雑な相互関係を持つもの)の記述においては、オブジェクト指向の考え方は必須である。

オブジェクト指向のメリット
  • 複数人で並行作業するチーム開発や業務で取り扱う大規模システムにメリットが現れやすい
👥
分業しやすい
  • オブジェクト単位で作業ができるため複数人での並行作業がしやすい。
  • システムの規模が大きくなる程、プログラムの実装は膨大になるため、複数人で開発作業を並行して行う必要がある。
🌀
拡張性が高い
  • ビジネスの加速に併せて、すぐに拡張・新機能が追加できる。
再利用性が高い
  • オブジェクトを使いまわせる。
  • 同じ処理を何度も書く必要がなくて楽
    • ただし、本質的には別のものを共通化する危険性があるのでやりすぎは良くない
👀
可読性が高い
  • 業務では自分が書いてないコードを読んで改修していく
🧑‍🏭
保守性が高い
  • 不具合が発生した場合の問題箇所の特定もしやすい。
主要な概念(以降解説)
カプセル化
👨‍👨‍👦
継承・委譲
😸
ポリモーフィズム

まとめ

設計(アーキテクチャ)の目的は変更容易なシステムを作ることにある
オブジェクト指向とは、システムをオブジェクトと呼ばれる構成単位を組み合わせとして捉え、オブジェクト間が相互作用することによって振る舞いが生まれるとする考え方
分業化しやすい、拡張性が高いなどのメリットによってチーム開発で採用されやすい。
ポリモーフィズム、カプセル化、継承などが主要な概念。