【.NETのエンタープライズアプリケーションアーキテクチャ】の第一部まとめ

はじめに

  • .NETプラットフォームに関する、優れたソフトウェアアーキテクチャ(複雑さを制御し、保守性を維持)のベストプラクティスを説明。
  • 優れたソフトウェアアーキテクチャを実施するためには、ビジネスドメインを深く理解することが重要。

第1部 基礎

概要

  • ソフトウェアアーキテクチャの以下の基礎的な部分を取り上げ。
    • アーキテクトの役割。
    • ソフトウェアプロジェクトに独特の力学。
    • 最高品質のソフトウェアに変化させるテスト容易性や読みやすさの側面。

第1章 現代のアーキテクトとアーキテクチャ

1.1 ソフトウェアアーキテクチャとは何か

1.2 アーキテクトと呼ばれる人々

  • アーキテクトの主な責務は、以下の通り。

    • 要件の承認
    • システムのサブシステムの分解
    • テクノロジの特定と評価
    • 仕様の策定
  • アーキテクトはコードに大きく関わる立場にあり、アーキテクトはシステムの設計を考え出すが、一方でそれが正しく実装されるように開発者と緊密に協力する立場になる。

第2章 成功のための設計

2.1 ビッグ・ボール・オブ・マッド(BBM

  • BBMとは、大部分が構造化されておらず、コンポーネント間の依存関係がこっそり埋め込まれ、データとコードは重複だらけで、レイヤーと関心が明確に識別されていないシステムを指す。つまり特盛のスパゲティコードのこと。

  • BBMの兆候

    • 変更を加えたのが1つのクラスだったとしても、変更による影響は依存関係にあるクラスに伝わってしまう。
    • ソフトウェアに変更を加えると、あちこちが正常に動作しなくなる。

2.2 ソフトウェアプロジェクトの力学

  • ソフトウェアプロジェクトの成功または失敗を実際に決定するのは個人であり、個人どうしの実際のやり取りである。ただ、組織の構造や全体的な文化も、最終的な結果に影響を与える。

2.3 窮地を脱する

  • 新しい開発を休止して、出来の悪いコードをはるかに管理しやすいものに変える。
  • やっかいなコードを分離→分割して、やっかいな領域をできるだけ小さくする。
  • 複数レイヤーにまたがる自動テストを実施して、継続的なリファクタリングを習慣にする。

第3章 ソフトウェアの設計原則

  • システムを設計するときに最も優先しなければならないのは保守性
  • 保守性は、原則や一般的なパターンを採用したり、コードの明確さ、読みやすさ、テスト容易性に配慮するなど、様々な要因の結果。
  • この章は一般的なプラクティスをまとめている。

3.1 ソフトウェアの普遍的な設計原則

スパゲティコードからラザニアコードへ

  • ジャンプやリターンが複雑に絡み合ったGOTOベースのコードをスパゲティコードと呼ばれるようになった。
  • 構造化プログラミングを用いて、サブルーチンを使ってコードを分割し、再利用しやすいコードをラザニアコードと呼ぶ。
  • 設計はコーディングに先立つものではなく、コーディングと並行で実施。コーディングが進むに従い、理想的な設計が少しずつ明らかになり、早い段階でリファクタリングを実施。
  • 設計とコーディングをともに進化させると、簡単かつ安全に更新できるコードになり、保守性の高いコードとなる。
  • 凝集性は、論理的に関連のある操作を表す限られた数のメソッドで構成された、きわめて専門性が高いクラスを作成することを推奨。メソッド間の論理的な距離が広がった場合は、新しいクラスを作成する。
  • 結合性は、2つのクラスなど、2つのソフトウェアモジュールの間に依存する依存関係に度合を表す。モジュール間のやり取りは可能だが、明確に定義された安定したインターフェースを通して実施。
  • 結合性が低く、凝集性が高くなるように設計されたシステムは、読みやすく、保守しやすく、テストしやすく、再利用しやすいという要件をたいてい満たしている。

関心の分離

  • 関心とは、ビジネスロジックやプレゼンテーションといった、ソフトウェアの様々な機能のこと。
  • 関心の分離とは、一度に1つの関心に焦点を合わせること。

3.2 オブジェクト指向設計

  • オブジェクト指向設計の要点
    • 関連のあるオブジェクトをクラスに組み入れ、正しい粒度を判断し、責務を割り当てる。
    • 相互にやり取りするオブジェクトの結合性を最小限に抑える。
    • コードを再利用しやすくする。

関連のあるクラス

  • 要件とユースケースから素材を洗い出し、それらを関連のあるクラスの階層としてまとめる必要がある。
    • 顧客クラスは、名前・住所・誕生日が関連など。
  • 顧客が注文一覧を取得する振る舞いをどこに配置するか?。顧客クラスの一部になるのか?。答えは、クラスが設計されている論理的背景、設計に使用されているパターン、レイヤー間で達成可能な分離のレベル、開発者やアーキテクトのスキルと戦略的ビジョンによって決まる。

合成と継承

  • クラスの継承では、派生クラスは親クラスのコードを継承するだけでなく、コンテキストを継承し、そこから親クラスの状態に対する可視性を手に入れる。これにより、親クラスが変更されると派生クラスが動作しなくなる可能性がある。
  • オブジェクトの合成では、基底型のインスタンスを保持する新しいラッパークラスを作成する。ラッパークラスは、基底クラスのメンバーにアクセスしたり、振る舞いを変更することができない。ラッパークラスが基底クラスのどの部分をどのように公開するかを決める

オブジェクト指向のもう一つの勢力

  • 効果的なコードを確実に書くためには、開発者が以下3つの点に集中すべき。
    • 変化する可能性が高い部分を切り離す。
    • クラスが必要ではないことがある。関数があればそれでよい場合は関数を使用するだけでよい。
    • 現実の世界がモデルではなくイベントを表すことに注意する。イベントはまさにデータを運ぶもの。

3.3 開発ベクトルと設計ベクトル

SOLID原則

  • 単一責務の原則(SRP)

    • クラスを変更する理由は常に1つでなければならない。
    • コードを書くときには、クラスをできるだけシンプルに保ち、コアタスクに焦点を合わせるようにする。
  • 開放/閉鎖の原則(OCP)

    • モジュールは拡張に対して開いてなければならず、修正に対して閉じていなければならない。
    • 拡張に対して開いているとは、他の関連する機能を構築するためのベースを使用できること。
    • 修正に対して閉じているとは、他の関連する機能を実装するために既存のコードを変更しないこと。
    • OCPは、合成、インターフェース、ジェネリックといったプログラミングメカニズムを使用。
  • リスコフの置換原則(LSP)

    • 継承利用なので、パス。
  • インターフェース分離の原則(ISP

    • インターフェースの関数を最小限に保ち、肥大化をさせない。
    • ISPを正しく適用すると、インターフェースが何種類かの関数の集まりに分割され、各クライアントと本当に必要な関数の集まりがマッチングできる。
  • 依存関係逆転の原則(DIP

    • DIパターンやServiceLocatorパターンの理論的根拠となる原則。
    • DIPは、実装ではなくインターフェースへのプログラミングという原則の背後にある概念の形式化したもの。

コーディングベクトル

  • KISS(Keep It Simple, Stupid)

    • ソフトウェアの世界では、開発者が機能を追加しすぎる傾向にあるので、実装に含まれるロジックがどれ1つとして欠けてはならないシステムを作成すること。
    • 単純な言い方をすると、付け加えるものがないときではなく、取り除くものがないとき。が表現できたシステムのこと。
  • YAGNI(You Ain't Gonna Need It)

    • 機能を実際に必要なときに実装すること。
    • 必要になるかもしれないと予測して実装すると、コーディング、テストの量が増え、その存在自体が将来の拡張に対する足かせになるかもしれない。
  • DRY(Don't Repeat Yourself)

    • コードの重複を避ける
  • Tell Don't Ask

    • データを包み込み、振る舞いを公開すること。
    • 日常会話の具体的な例として、嫁から「今何してる?忙しい?」と聞かれるより、「ごみを出してきてー」と言われるほうがコミュニケーションの仕方。この考え方をオブジェクト間のやり取りに利用。

3.4 防御的プログラミング

  • 防御的プログラミングとは、入力が予想外のものであったとしても、ソフトウェアを予測可能な方法で動作させること。
  • 具体的には、事前条件・事後条件・不変条件を明確にする。 +事前条件は、メソッドの処理開始時に、メソッドが実行できるかどうかを判定する条件のこと。
    • 事後条件は、メソッドによって生成される出力と、オブジェクトの状態に対する変更のことで、メソッドから制御を戻すときに適用される条件のこと。
    • 不変条件は、コンストラクターやセッターなど、パブリックメソッドの実行中に変化しないクラスメンバーに適用される条件のこと。オブジェクトが論理的に矛盾した状態になるのを避けること。

第4章 高品質なソフトウェアの作成

  • ソフトウェアが使用されている間は保守が必要で、よいソフトウェアとは、効果的なリファクタリングに適したソフトウェアのこと。
  • リファクタリングの効果は、テスト容易性・拡張性・読みやすさの3つの要素と、デグレードの疑いがあるものをすべて捕獲するテストによって決まる。

4.1 テストしやすいコードの記述

  • ソフトウェアを自動的にテストすることの必要性が認識された結果、テストしやすいソフトウェアを記述するという重要な考え方が必要になった。
  • アプリケーションのロジックを左右する重要な決断が下される場所で、テストを重点的に行うべき。特にドメイン層を重点的に。
  • テスト容易性とは何か

  • テスト容易性とは、コードをテストするときの容易さ。

  • コードが期待どおりに動作するかどうかを明示的に、自動的に、繰り返し判断することが可能。
  • テスト容易性の原則を満たしていると、本質的にテストしやすく、読みやすく、理解しやすく、よって保守しやすいシステムとなる。
  • テスト容易性の原則は、以下3つの特性を定義。
    • 制御
      • ソフトウェアはどのようなパラメータが要求され、どのような戻り値が生成されるのかが明確になるような方法で作成する。
      • 依存関係を抽象化し、外部の呼び出し元からそれらを自由に注入できるようにするための手段を提供。
    • 可視性
      • テスト対象のソフトウェアの現在の状態と、ソフトウェアが生成できる出力を観察する能力として定義。
      • 要するに、メソッドの実行後に検証させる事後条件のこと。
    • 単純さ
      • テストしなければならないものが少ないほど、より確実で迅速なテストが可能となるため、きわめて凝集性が高い単純なコンポーネントはテストしやすい。

4.2 コードの拡張性に関するプラクティス

インターフェースに基づく設計

  • インターフェースは、2つのソフトウェアコンポーネント間で設定される取り決めに相当する。
  • クラスなどのソフトウェアコンポーネントで特定のインターフェースに対処できることを宣言すると、事実上、同じインターフェースをサポートする他のすべてのコンポーネントにも対処できる。拡張可能なコードはこの考え方の上に成り立っている。
  • すべてのクラスを拡張可能なクラスとして設計する戦略は、オーバーエンジニアリングとなる。そのため、本当に価値がある状況を見つけて、それに対処することが大事。

4.3 他人が読めるコードを書く

ソフトウェアの特性としての読みやすさ

  • コードが読みにくいということは、コードに手を加えている開発者がそのコードをよく理解していなければ、コードを良くするどころか悪くする可能性がある。
  • 読みやすさを向上させるための実用的なルール

  • 読みやすさの定義は、コメント・整合性・明確さの3つのルールとして定義。

    • コメントは、コードにおいて下される自明ではない決断を説明する文。が良いコメント。
    • 整合性は、コーディングガイドラインを設けて、社内全体で共通としていることが理想。
    • 明確さは、コードがすらすら読めるようなスタイルが適用されている。

第1部は、理論的な話で、ひたすら保守性が大事と言ってる気がするし、個人的には保守性が一番大事なので、すげー納得できた。 ただ、実際の開発現場だと、

  • 保守と意識した設計・実装する。
  • プロダクトを早くリリースする。

の2つのバランスを上手にとる必要があると思う。

第2部は実践的な話っぽいので、楽しみ。 僕の好きなDDDの話題もあるし。 以上。