)ソフトウェア工学プログラミングシステム構築・プログラミングにおいて「疎結合化」は品質向上に欠かせない要素ですが、ワード本来の意味の広さもあり、色々な文脈で語られがです。様々なWeb文書を見る限り、IT界隈では主として1ソースコードにおける疎結合化と、2システム・アーキテクチャにおける疎結合化に大別できるように感じます。今回は、この2つの疎結合化の内、特に1(ソースコードに関すること)について、具体例を交えながら個別に内容を考えていきたいと思います。筆者の失敗談:MVCパターン開発の誤解と反省(前提)モデル層には、ORマッパの受け皿の他に「独自の計算」の役割がある財務計算システムの開発で、計算ロジックとDBを密結合させて大失敗この財務計算ロジック、他の環境でも役立ちそうなのにDBから引き剥がせない本来関係ない「計算ロジック」と「DB」をコード上で結合させたのが最大のミス失敗を踏まえ構成を再検討。DBとの結合箇所を再構成する。記事筆者へのお問い合わせ、仕事のご依頼ソースコード論、アーキテクチャ論、2つの「疎結合化」に共通する考え方アーキテクチャにしても、ソースコードにしても、他の何かと密結合(疎結合の反対語)していると、結合した片方の影響(例えばシステム障害や改修など)が結合相手にも波及してしまい、不効率です。何か一部の変更をするにしても影響範囲の把握や他部署への確認作業、調整が膨大となり、スピード感をもった改修作業はおぼつかなくなってしまいます。そこで、一定の括りでコンポーネント化して依存度を落とすことでコストダウンを図り、ビジネスの変化にクイックに対応したり開発組織の効率化を企図するのが中核の考え方と言えます。ソースコードにおける疎結合化ソースコードにおける疎結合化は、主に以下のようにソースコードを記述することを指します。疎結合化されたソースコードとは大まかにどのようなものか?個別に実行(テスト)が可能であること。各コンポーネントが特定フレームワークのクラスオブジェクトに依存していないこと(プリミティブ型でのデータ受け渡しや、依存性注入(関連記事)を行う等)。レイヤ間(例えばMVCパターンにおけるM-V-C間やアプリケーション層・インフラ(データベース他)層など)の依存関係を簡潔にする。相互依存関係などは極力避ける。変数やプロパティなどを複数の関数やクラスで共有しないこと。いうワードがありますが、疎結合化とは本質的には別概念と考えてよいかと思っています。関心の分離ができているコードでも疎結合化の観点では不十分、あるいはその逆のコードも存在し得るということです。ソースコード同士を疎結合にする必要があるのか?→変更容易性を確保し、ソースコードの転用を容易化→ビジネスに勝てるソフトウェアであり続けるソフトウェアの使途が完成後に微調整されることや、あるいはコアドメインにとって重要なファクタ・不要な機能が完成後に見つかることが非常に多い、という事実はソフトウェアエンジニアリングの世界に広く知られた経験則です。有名なエヴァンス氏の翻訳本でも次のような一説があります。重大な発見はいつでも、設計や実装をするために努力する際に現れるからだ。非常に特殊で予期せぬ問題というものが常に発生する。作られるモデルは、的外れな対象を掘り下げる一方で、重要な問題を見過ごしてしまうのである。第3章また、多くのソフトウェアの目的はビジネス効率化であったりマネタイズであったりします。ビジネスや技術環境は常に変化しており、それに伴いソフトウェア自身もニーズや技術の変化に伴い変化していくことが必要です。時にはプラットフォームごと乗り換え(近年の例でいえば、オンプレからクラウドへの移行など)を求められることもあうでしょう。いった場合において現在使用しているインフラや古いフレームワークに縛られて転用不可能なソースコードだったり、一部を変更すると影響がシステム全体に波及するような状態では、ビジネスの潮流についていくことはできません。DDDの話題に限らず、このようなソフトウェアの目的達成のためにも、各モジュールは責務ごとに疎結合≒個別に実行(テスト)が可能であり変更が容易であり、プラットフォームやフレームワークに縛られず転用可能である必要があると言えます。ソースコード疎結合化の実践では実際どのようにすればよいのか?実装の場面を再現しながら、簡易的なソースコードや架空のフレームワークなどとともに考えていきます。他のレイヤ、例えばアプリケーションからインフラ層などを呼び出さない例えば計算処理の実行中にデータベースから値を取得したり、逆に値を書き込んだりしない努力をすることは重要です。要は、計算は計算、DB更新はDB更新といった具合に実行箇所をきちんと分け、特に計算処理(ビジネスロジックの処理)は極力メモリリソースで完結することが望ましいと言えます。(当然ながら、リソース上限の問題がある場合はこの限りではありません。)具体的にコードで見てみましょう。計算処理を行っているはずが、途中でデータベースを呼び出しているため、この計算処理関数はデータベースがある環境でないと実行不能である点が問題と言えます。result;}「こんなコード、普通書かないでしょ。笑」などと思うでしょうか?後述する通り、筆者は昔、実際にこういったダメコードを実装してしまい苦心した記憶があります。感触としては実に初心者プログラマーあるあるな事例のように感じます。するべきでしょう?ここはやはり上述した通り、計算は計算として分離し、インフラ(DB等)アクセスは別途行う形式としましょう。result;}//実行関数でDB呼び出し、計算、結果をDBに反映、という流れで呼び出す//(依存性注入による更なる疎結合化の余地あり。RunInsertToDatabase(calculationResult);}この修正によりどういった恩恵があるでしょうか?最も大きいのは、計算処理とDBアクセス処理が疎結合化されたこと、すなわち計算(ビジネスロジック)とDBアクセスが個別に実行可能となったことが挙げられます。計算処理(上記例ではCalculation関数)に着目すると、計算処理中にDBにアクセスせず、引数や戻り値も言語仕様の型であるint型となっています。そのためこの関数はこのプロジェクトから引き離しても実行可能であり、データベースなどがない環境でもテストすることができる訳です。各コンポーネントが特定FWのクラスオブジェクトに依存しない(プリミティブ型の使用、依存性注入)個別実行可能性の確保において、各コンポーネント(ビジネスロジック)が特定フレームワークのクラスに依存しないことは重要です。データベースアクセスを提供する架空のフレームワーク(ここでは「DbAccessor」フレームワークとします)があったとして、そのFW内のクラス(ここではDbAccessorResultクラスとします)で結果セットが提供されているとしましょう。このクラスをビジネスロジック関数に与えてしまうとDbAccessorクラスに依存し個別実行性を損なう(DbAccessorを利用している環境でないと実行できない)ため、関数の外でプリミティブ型(int型など)に変換することを検討します。}このようなコードを書く心理として、「事前変換が面倒だからそのまま計算に流してしまおう」「DbAccessorResultのインスタンスがあれば実行中にDBアクセスができて便利」という声が見て取れます。上述の通り、これではCalculation関数がDbAccessorフレームワークがない環境では実行不能であり、個別実行可能性を損なう(個別テストや移植ができない)という問題が生じます。このコードを修正していきましょう。具体的には、Calculation関数とDbAccessorフレームワークを分離するとともに、依存性注入(関連記事)を行います。}//ビジネスロジックの外側からDbAccessorフレームワークを使用する。//Run関数自体もDbAccessorフレームワークから自由になるため、//実態クラスではなくインターフェースで引数を取る。dbAccessorResult.RunInsertToDatabase(resultValue);}この変更により、Calculation関数はDbAccessorフレームワーク(DbAccessorResultクラス)とは無関係に実行でき、フレームワークが存在しない環境でも実行・テストが出来るようになりました。上記修正案のコードでは依存性注入を行っているという特徴が見られます。具体的には、Run関数の引数に実体クラス(DbAccessorResult)ではなくインターフェース(IDbAccessorResult)を指定している点です。これは何故でしょうか?それはRun関数自体もDbAccessorフレームワークから自由になるためです。実態クラスが引数(DbAccessorResult)の場合、Run関数はやはりDbAccessorフレームワークがないと実行できません。他方、フレームワークが提供するインターフェース(IDbAccessorResult)を使用すれば、フレームワークの外でもテストが出来るようになります。イメージをつかむために、コードで考えてみましょう。}}ここで、IDbAccessorResultインターフェースは、フレームワーク本体を使わなくても、他のコードでもコピーして使うことができる点がポイントです。この一工夫によりフレームワークがない環境でもRun関数が実行可能となり、更にテスト用実体クラスの利用により個別にテストが実行できるという依存性注入のメリットが実現したと言えます。関数レベルでの疎結合化:変数やプロパティなどを複数の関数やクラスで共有しない特にオブジェクト指向言語において、クラス内の関数レベルで疎結合化するか否かは状況によります。可能であれば検討するべきでしょう。クラスにはプロパティやフィールド変数を設置できるのが一般的です。これらを複数の関数から変更・参照すると状態の管理が困難になる、というのがその理由です。クラスが巨大になるほど、クラス実装を作業する人数が増えるほど、意識する必要がある論点と言えます。逆に、小規模なクラスや単一責務が明確なクラスであればそこまで厳密に実行する必要もないでしょう。この疎結合化処理は引数の運用が煩雑になるので、状況次第で検討していくべき論点と考えられます。具体的な例で考える疎結合化(+関心の分離)1例えば、「データベースから値を取得して演算した後、結果をエクセルに値を記入して保存」という処理を考えましょう。エクセルファイルに値を記入できるライブラリは世の中にいくつかあります。ライブラリはある日突然ライセンスが有料化されるかもしれないし、あるいは脆弱性の発見等により別のライブラリに乗り換える必要が生じるかもしれません。いった時に、処理とライブラリのAPIが密結合していると、ライブラリ載せ替え作業のためにソースコード全体を解体・再構築する必要が出てしまいます。このような事態に備えてどのようなコードを構築するべきでしょうか?具体的に見ていきます。//エクセル書き込みはライブラリAPIを呼んで行う。library.SaveFile();}上記コードでも一応目的は達せられますが、例えばライブラリを変更したいと思ったり、はたまたエクセルファイルではなくCSVファイルに出力したいといった仕様変更があった時にどうなるでしょうか?データ呼び出しや書き込みといった処理が一連の流れで密結合的に実装されているので、コード全体の修理が必要となってしまいます。すればよいのでしょうか?このコードには、1データベースからのデータ取得、2データの加工(演算)、3ライブラリAPIを使ったエクセルへの書き込みと3つのタスクが詰め込まれています。この処理を一つ一つ分解してみましょう。//エクセル書き込みはライブラリAPIを呼んで行う。}以上のように分解すると、ライブラリ乗り換え等の要請に対して変更が必要なのはWriteToExcelメソッドだけとなり、改修の手間が大幅に省けることになります。具体的に言うと、前処理で生成されている値などを一切気にすることなく、メソッドが受け取った値を出力する処理だけに集中して作業を行うことができるのです。このように、「値をDBからとってファイルに書き込む」といった単純な処理でさえ、疎結合化や関心の分離を意識するか否かで変更容易性が変わってくるということが言えます。コーディングの日常で常に意識するべきである、ということです。Rails等の有名フレームワークにおいてM(モデル層)はニアリイコールでデータベーススキーマ=ORマッパの受け皿という紹介をされます。そのため「モデル層はDBと密結合してよい」という誤解を生みがちですが、今回の事例を踏まえると完全なる誤解であり後々の問題の火種となります。ここでは、筆者が過去その誤解のもとダメコードを生産してしまった失敗経験と、そこから学ぶプラクティス、疎結合化の実践を考えたいと思います。Rails等のFWではモデル層はDBのカラムと一致したプロパティを持たせ、RDBの各行をインスタンス化する役割を担います。他方、MVCアーキテクチャにおいてそのアプリが専門とする計算(コアドメイン)を担うのもモデル層です。これから事例とする財務計算システムでは、入力となる財務情報をつかって独自の計算を行い財務指標を出力するといった役割をいいます。いったものは、通常はモデル層に収めるべし、というのがMVCの基本的考え方です。財務情報を入力し、独自計算した財務指標を出力するモデル(クラス)が今回紹介する事例の中心となる機能です。上述の通り、事例として私が過去に作った、財務データを元に一定のロジックで計算して結果を表示・DB格納するシステムを取り上げます。(今後公表予定のプロダクトなので詳細はその時まで伏せますが、今回の議論には支障ありません。)次の図が、私が当初組んだ構成図です。Coreを使用したMVCアーキテクチャで組まれたWebアプリケーションです。データベースと財務計算モデルが結びついているところが後々の失敗に繋がります。MVC初心者がやりがちな「ファット・コントローラー」(全ロジックをコントローラーに詰め込むアンチパターン)にはなっていない分まだマシです。財務計算モデルはDBと結合し、計算過程でDBからロードしたり、逆に一部の成果物を計算途中でDBに登録し別の箇所で再度ロードするといった動きをしています。この構成が、後々実際に面倒な問題を引き起こしました。この財務計算ロジック、他の環境でも役立ちそうなのにDBから引き剥がせないこのシステムが完成してから数年後、中核となっている財務計算モデルを他の箇所でも使用したいという要望がありました。新たな利用環境は、Webアプリではなくデスクトップ上でスタンドアロンで起動するアプリケーションです。そこで改めてコードを見てみたところ、私はまずい問題に気付きます。コアである財務計算の最中にDBにアクセスする前提のコードが混ざっているため、DBと計算ロジックを引き剥がせないのです。なると、DBがない環境に持ち出すためには、そのロジック自体にも影響が出る可能性もある大手術が必要になってしまいます。このケースでも、かなり処理の流れを変えつつ取り出すことにしたので、相応の工数を必要としました。本来関係ない「計算ロジック」と「DB」をコード上で結合させたのが最大のミス財務計算モデルは現実における課題解決を目指したソリューションであり、本来はDBもブラウザーもない世界に存在するモデルであるはずです。今回の事例では全く関係ない計算ロジックとDBを密結合させて実装した点が失敗でした。ソフトウェアの構成は、コアドメインとなる中核の計算ロジックを動かすために、コアに関係ない多量のコード、インフラ、UIを必要とするのが通常です。いった区分(コアか否か)を明確に行った上、コアとその他の部分が疎結合となるように慎重に分離しておかなければ、何かを変更するたびに大工事が必要となってしまうのです。失敗を踏まえ構成を再検討。DBとの結合箇所を再構成する。冒頭でも書いた通り、モデル層の役割はORマッパとコア計算ロジックの二つです。この内の計算ロジックに関してはDB(インフラ)ともビューとも分離されている必要とがあります。よって計算ロジック内でDBにアクセスする箇所は全て削除し、コントローラーから処理のためのデータを受け取り、コントローラーに値を返すだけのシンプルなクラスに再構築します。再構築前のダメな構成。財務計算モデルがDBと密結合しているので、この環境の外に持っていけません。財務計算モデルは副作用を除去してコントローラーとだけやり取りします。DB操作もコントローラーに委託します。この構成なら、例えば入力をJSONなどデータ交換可能な形式にすれば、あらゆる環境から財務計算モデルを計算可能になります。本記事でも触れたMVCパターンにおいて、Controllerは疎結合化のための重要責務を負っています。いった重要性はあまり正確に理解されず、しばしば「ファットコントローラー」や「痩せすぎコントローラー」が生産されてしまいます。このような背景も踏まえ、下記参考記事では疎結合化においてControllerが行うべき役割について論じています。併せてご参考ください。当社では、IT活用をはじめ、業務効率化やM&A、管理会計など幅広い分野でコンサルティング事業・IT開発事業を行っております。この記事をご覧になり、もし相談してみたい点などがあれば、ぜひ問い合わせフォームまでご連絡ください。皆様のご投稿をお待ちしております。記事筆者へ問い合わせする※ご相談は無料でお受けいたします。感想やご意見等、お気軽にコメントください。公序良俗に反するコメントはお控えください。管理者が承認したコメントはこの箇所に公開させていただく可能性がございます。返信通知先Emailアドレスは、筆者のみに通知され、公開されることはありません。返信通知先Emailを入力された場合は、コメントへの返信をこちらに掲載した際に通知させていただきます。その他の目的には使用いたしません。スパム対策のため、コメントは日本語のみ受け付けております。運営するブログメディアサイトです。業務の一環として、中小企業の経営×IT活用術やプログラミングの話題を中心に発信しています。経営コンサルティング全般からシステム設計、コーディングまで最前線で何でもこなしている、代表者の堺が執筆しています。ご一読のほどよろしくお願いします。プロダクト開発者)現役の財務・金融コンサルタントであり、システム開発者でもあります。自社のアプリ、ホームページ、ブログなど、全て自らコーディングしています。【関心の分離とSQL】複雑なSQLを書くべきではないという問題提起について依存性注入/DIは実はシンプルな概念なので、シンプルに解説する。「ソフトウェア工学」カテゴリをもっと見る自社PRトップページ運営会社お問い合わせ当サイトはリンクフリーです。記事の内容には相応の注意を払い掲載しておりますが、ご利用にあたっては読者の皆様のご判断とご責任の下にお願いいたします。partial",relevance:0},{begin:e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{match:/\(\)/},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[g,i,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}})();hljs.registerLanguage("csharp",e)})();。