依存性のない真にモジュラーコードの作成

ソフトウェアの開発は素晴らしいですが、…私たちは皆、それが感情的なローラーコースターのビットであることに同意することができると思います。 初めに、すべてが素晴らしいです。 数時間ではないにしても、数日のうちに新しい機能を次々に追加します。 あなたはロールにしています!

数ヶ月早送りすると、あなたの開発速度が低下します。 それはあなたが以前ほど懸命に働いていないからですか? そうじゃない さらに数ヶ月早送りすると、開発速度がさらに低下します。 このプロジェクトに取り組むことはもう楽しいことではなく、ドラッグになっています。

悪化します。 アプリケーションで複数のバグの検出を開始します。 多くの場合、1つのバグを解決すると2つの新しいバグが作成されます。 この時点で、あなたは歌い始めることができます:

99 コードの小さなバグ。99匹の小さな虫。

…コード内の127個の小さなバグ。

今このプロジェクトに取り組んでいることについてどのように感じていますか? 私のようなら、おそらくあなたの刺激を失い始める。 既存のコードへのすべての変更は予測不可能な結果をもたらす可能性があるため、このアプリケーションを開発するのは苦痛です。

この経験はソフトウェアの世界では一般的であり、多くのプログラマがソースコードを捨ててすべてを書き直したい理由を説明することができます。

時間の経過とともにソフトウェア開発が遅くなる理由

では、この問題の理由は何ですか?

主な原因は複雑さの増大です。 私の経験から、全体的な複雑さへの最大の貢献は、ソフトウェアプロジェクトの大部分で、すべてが接続されているという事実です。 各クラスには依存関係があるため、電子メールを送信するクラスのコードを変更すると、ユーザーは突然登録できません。 それはなぜですか? あなたの登録コードは、電子メールを送信するコードに依存するためです。 今、あなたはバグを導入せずに何も変更することはできません。 すべての依存関係を追跡することはできません。私たちの問題の本当の原因は、私たちのコードが持っているすべての依存関係から来る複雑さを上げることです。

泥の大きなボールとそれを減らす方法

面白いことに、この問題は何年も前から知られています。 それは”泥の大きい球と呼ばれる共通の反パターンです。”私は、複数の異なる企業で長年にわたって取り組んできたほぼすべてのプロジェクトで、そのタイプのアーキテクチャを見てきました。

では、このアンチパターンは正確には何ですか? 簡単に言えば、各要素が他の要素との依存関係を持っているとき、あなたは泥の大きなボールを得ることができます。 以下では、よく知られているオープンソースプロジェクトApache Hadoopからの依存関係のグラフを見ることができます。 泥の大きなボール(またはむしろ糸の大きなボール)を視覚化するには、円を描き、プロジェクトのクラスをその上に均等に配置します。 お互いに依存するクラスの各ペアの間に線を引くだけです。 今、あなたはあなたの問題の原因を見ることができます。

数十のノードと数百の線がそれらを互いに接続している、Apache Hadoopの大きな泥の球を視覚化します。

Apache Hadoopの「big ball of mud」

モジュラーコードを使用したソリューション

だから私は自分自身に質問をしました:複雑さを減らし、プロジェクトの初めのように楽し 真実は、あなたが複雑さのすべてを排除することはできません、言われます。 新しい機能を追加する場合は、常にコードの複雑さを上げる必要があります。 それにもかかわらず、複雑さは移動して分離することができます。

他の産業がこの問題をどのように解決しているか

機械産業について考えてみてください。 いくつかの小さな機械屋が機械を作っているとき、彼らは標準的な要素のセットを購入し、いくつかのカスタム要素を作成し、それらをまとめます。 それらはそれらの部品を完全に別に作り、ちょうど少数の微調整をする端にすべてを組み立てることができる。 これはどのように可能ですか? 彼らは各要素がボルトサイズのような一定の業界標準によっていかに一緒に合うか、および取り付け穴のサイズおよびそれらの間の間隔のような

物理的なメカニズムとその作品がどのように一緒に収まるかの技術的な図。 ピースは、次に添付する順序で番号が付けられていますが、その順序は左から右に行きます5, 3, 4, 1, 2.

上記のアセンブリ内の各要素は、最終製品またはその他の部分について何の知識も持たない別の会社によって提供されることができます。 各モジュール要素が仕様に従って製造されている限り、計画どおりに最終的なデバイスを作成することができます。

ソフトウェア業界でそれを複製することはできますか?

確かに私たちはできます! 最良の部分は、このアプローチは、任意のオブジェクト指向言語で使用することができるという事実である:Java、C#、スウィフト、TypeScript、JavaScript、PHP-リストは上に行く。 このメソッドを適用するために派手なフレームワークは必要ありません。 あなただけのいくつかの簡単なルールに固執し、規律を維持する必要があります。

制御の反転はあなたの友人です

私が最初に制御の反転について聞いたとき、私はすぐに私が解決策を見つけたことに気づきました。 これは、既存の依存関係を取得し、インターフェイスを使用してそれらを反転する概念です。 インターフェイスは、メソッドの単純な宣言です。 具体的な実装は提供していません。 その結果、それらを接続する方法に関する2つの要素間の合意として使用することができます。 あなたがする場合、彼らは、モジュラーコネクタとして使用することができます。 ある要素がインターフェイスを提供し、別の要素がその実装を提供する限り、お互いについて何も知らずに一緒に作業することができます。 それは素晴らしいです。

モジュラーコードを作成するためにシステムをどのように分離できるかを簡単な例で見てみましょう。 以下の図は、単純なJavaアプリケーションとして実装されています。 あなたはこのGitHubリポジトリでそれらを見つけることができます。

問題

Mainクラス、三つのサービス、単一のUtilクラスのみで構成される非常に単純なアプリケーションがあると仮定しましょう。 これらの要素は、複数の方法で互いに依存しています。 以下では、”big ball of mud”アプローチを使用した実装を見ることができます。 クラスは単にお互いを呼び出します。 それらは密接に結合されており、他の要素に触れることなく1つの要素を取り出すことはできません。 このスタイルを使用して作成されたアプリケーションを使用すると、最初は急速に成長することができます。 私はこのスタイルは、あなたが簡単に物事を遊ぶことができるので、概念実証プロジェクトに適していると信じています。 それにもかかわらず、メンテナンスであっても危険であり、単一の変更が予測不可能なバグを作成する可能性があるため、本番環境対応のソリ 下の図は、この泥の建築の大きなボールを示しています。

Mainは、それぞれUtilを使用するサービスA、B、およびCを使用します。 サービスCもサービスAを使用します。

なぜ依存性注入がすべて間違っているのか

より良いアプローチを探すために、依存性注入と呼ばれる技術を使用することができます。 このメソッドは、すべてのコンポーネントがインターフェイスを介して使用されることを前提としています。 私はそれが要素を切り離すという主張を読んだことがありますが、実際にはそうですか? いいえ。. 下の図を見てください。

以前のアーキテクチャですが、依存性注入を使用しています。 現在、Mainは、対応するサービスによって実装されるインタフェースサービスA、B、およびCを使用しています。 サービスAとCの両方で、インターフェイスサービスBとインターフェイスUtilが使用されます。 各サービスは、そのインターフェイスとともに要素と見なされます。

現在の状況と大きな泥のボールの唯一の違いは、クラスを直接呼び出すのではなく、インターフェイスを介してクラスを呼び出すという事実です。 これは、要素を互いに分離することをわずかに改善します。 たとえば、別のプロジェクトでService Aを再利用したい場合は、Service A自体とInterface A、およびInterface BInterface Utilを取り出すことでそれを行うことができます。 ご覧のとおり、Service Aはまだ他の要素に依存しています。 その結果、ある場所でコードを変更したり、別の場所で動作を乱したりすることに問題があります。 それでも、Service BInterface Bを変更すると、それに依存するすべての要素を変更する必要があるという問題が発生します。 私の意見では、要素の上にインターフェイスの層を追加するだけです。 依存関係を注入するべきではありませんが、代わりにそれらを一度だけ取り除く必要があります。 独立のために万歳!

モジュラーコードの解決策

私が信じているアプローチは、依存関係をまったく使用しないことによって、依存関係の主な頭痛をすべて解決します。 コンポーネントとそのリスナーを作成します。 リスナーはシンプルなインターフェイスです。 現在の要素の外側からメソッドを呼び出す必要があるときはいつでも、リスナーにメソッドを追加して代わりに呼び出すだけです。 要素は、ファイルの使用、パッケージ内のメソッドの呼び出し、メインフレームワークまたは他の使用されるライブラリによって提供されるクラスの使用のみが許可されています。 以下では、要素アーキテクチャを使用するように変更されたアプリケーションの図を見ることができます。

要素アーキテクチャを使用するように変更されたアプリケーションの図。 主な用途Utilとすべての三つのサービス。 Mainは、そのサービスによって使用される各サービスのリスナーも実装します。 リスナーとサービスは一緒に要素と見なされます。

このアーキテクチャでは、Mainクラスのみが複数の依存関係を持っていることに注意してください。 すべての要素を配線し、アプリケーションのビジネスロジックをカプセル化します。一方、

サービスは完全に独立した要素です。 今、あなたはこのアプリケーションから各サービスを取り出し、どこか別の場所に再利用することができます。 彼らは何か他のものに依存しません。 しかし、待って、それが良くなる:あなたは限り、あなたは彼らの動作を変更しないように、再びこれらのサービスを変更する必要はありません。 限り、これらのサービスは、彼らが何をすべきかを行うように、彼らは時間の終わりまで手つかずのままにすることができます。 それらは、プロのソフトウェアエンジニア、またはgoto文が混在して調理された最悪のスパゲッティコードの誰もが妥協した初めてのコーダによって作成 彼らの論理はカプセル化されているので、それは問題ではありません。 それは恐ろしいかもしれないとして、それは他のクラスに流出することはありません。 これにより、プロジェクト内の作業を複数の開発者間で分割することができ、各開発者は別の開発者を中断したり、他の開発者の存在を知らなくても、

最後に、最後のプロジェクトの開始時と同じように、独立したコードをもう一度書き始めることができます。

要素パターン

構造要素パターンを定義して、反復可能な方法で作成できるようにしましょう。

要素の最も単純なバージョンは二つのもので構成されています: メイン要素クラスとリスナー。 要素を使用する場合は、リスナーを実装してメインクラスを呼び出す必要があります。 ここでは、最も単純な構成の図です:

アプリ内の単一の要素とそのリスナーの図。 前と同様に、アプリはアプリによって実装されたリスナーを使用する要素を使用します。

明らかに、最終的には要素に複雑さを追加する必要がありますが、簡単に行うことができます。 ロジッククラスがプロジェクト内の他のファイルに依存していないことを確認してください。 この要素で使用できるのは、メインフレームワーク、インポートされたライブラリ、およびその他のファイルのみです。 それは、画像、ビュー、サウンドなどのようなアセットファイルに来るとき。 また、将来的には再利用が容易になるように、要素内にカプセル化する必要があります。 フォルダ全体を別のプロジェクトにコピーするだけで、そこにあります!

以下に、より高度な要素を示す例のグラフを見ることができます。 これは、使用しているビューで構成され、他のアプリケーションファイルには依存しないことに注意してください。 依存関係をチェックする簡単な方法を知りたい場合は、importセクションを見てください。 現在の要素の外部からのファイルはありますか? その場合、それらの依存関係を要素に移動するか、リスナーに適切な呼び出しを追加することによって、それらの依存関係を削除する必要があります。

より複雑な要素の単純な図。 ここでは、"要素"という言葉の大きな意味は、ビュー、論理A、B、C、要素、および要素リスナーの六つの部分で構成されています。 後者の2つとアプリの関係は以前と同じですが、内側の要素も論理AとCを使用します。Logic Cは論理AとBを使用します。Logic Aは論理BとViewを使用し

うにして単純に”Hello World”の例で作成されたJava.

public class Main { interface ElementListener { void printOutput(String message); } static class Element { private ElementListener listener; public Element(ElementListener listener) { this.listener = listener; } public void sayHello() { String message = "Hello World of Elements!"; this.listener.printOutput(message); } } static class App { public App() { } public void start() { // Build listener ElementListener elementListener = message -> System.out.println(message); // Assemble element Element element = new Element(elementListener); element.sayHello(); } } public static void main(String args) { App app = new App(); app.start(); }}

最初に、出力を出力するメソッドを指定するためにElementListenerを定義します。 要素自体は以下で定義されています。 要素でsayHelloを呼び出すと、ElementListenerを使用してメッセージを出力するだけです。 要素はprintOutputメソッドの実装から完全に独立していることに注意してください。 これは、コンソール、物理プリンタ、または派手なUIに印刷することができます。 要素はその実装に依存しません。 この抽象化のため、この要素はさまざまなアプリケーションで簡単に再利用できます。

今、メインのAppクラスを見てみましょう。 リスナーを実装し、具体的な実装とともに要素をアセンブルします。 今、私たちはそれを使用して開始することができます。

また、ここでJavaScriptでこの例を実行することができます

要素アーキテクチャ

のは、大規模なアプリケーションで要素パターンを使用して見てみましょう。 それは小さなプロジェクトでそれを示すための一つのことです—それは現実の世界にそれを適用するために別のものです。

私が使用したいフルスタックwebアプリケーションの構造は次のようになります:

src├── client│ ├── app│ └── elements│ └── server ├── app └── elements

ソースコードフォルダでは、最初にクライアントファイルとサーバーファイルを分割します。 ブラウザとバックエンドサーバー:彼らは二つの異なる環境で実行されるので、それは、行うには合理的なことです。

次に、各レイヤーのコードをappとelementsというフォルダに分割します。 要素は独立したコンポーネントを持つフォルダで構成され、アプリフォルダはすべての要素を配線し、すべてのビジネスロジックを格納します。

そうすれば、要素は異なるプロジェクト間で再利用できますが、アプリケーション固有の複雑さはすべて単一のフォルダにカプセル化され、要素への単純な呼び出しに縮小されることがよくあります。

実践例

実践は常に理論を切り札と信じて、Nodeで作成された実際の例を見てみましょう。jsとTypeScript。

実際の例

これは、より高度なソリューションの出発点として使用できる非常に単純なwebアプリケーションです。 それは要素の建築に続きます、また広範囲に構造要素パターンを使用します。

ハイライトから、メインページが要素として区別されていることがわかります。 このページには独自のビューが含まれています。 したがって、たとえば、再利用したい場合は、フォルダ全体をコピーして別のプロジェクトにドロップするだけです。 ちょうど一緒にすべてを配線し、あなたが設定されています。

これは、今日、独自のアプリケーションで要素の導入を開始できることを示す基本的な例です。 独立したコンポーネントの区別を開始し、ロジックを分離することができます。 あなたが現在作業しているコードがどれほど乱雑であってもかまいません。

より速く開発し、より頻繁に再利用!

私は、この新しいツールセットを使用すると、より保守しやすいコードをより簡単に開発できることを願っています。 実際に要素パターンを使用する前に、すべての主なポイントをすばやく要約してみましょう:

  • ソフトウェアの多くの問題は、複数のコンポーネント間の依存関係のために発生します。

  • ある場所に変更を加えることで、予測不可能な動作を他の場所に導入することができます。

三つの一般的な建築アプローチは次のとおりです:

  • 泥の大きなボール。 それは急速な開発のために大きいですが、安定した生産の為にそう大きくないです。

  • 依存性注入。 それはあなたが避けるべき半分焼きの解決策です。

  • 要素アーキテクチャ。 このソリューションを使用すると、独立したコンポーネントを作成し、他のプロジェクトで再利用することができます。 それは安定した生産解放のために維持可能、華麗です。

基本的な要素パターンは、すべての主要なメソッドを持つメインクラスと、外部の世界との通信を可能にするシンプルなインターフェイスであるリスナで構成されています。

フルスタックエレメントアーキテクチャを実現するには、まずフロントエンドとバックエンドコードを分離します。 次に、アプリと要素のそれぞれにフォルダを作成します。 Elementsフォルダはすべての独立した要素で構成され、appフォルダはすべてをまとめて配線します。

今、あなたは行くと、独自の要素を作成し、共有を開始することができます。 長期的には、それはあなたが簡単に保守可能な製品を作成するのに役立ちます。 幸運と私はあなたが作成したものを知ってみましょう!

また、コードを途中で最適化している場合は、同僚のToptaler Kevin Blochによる早期最適化の呪いを避ける方法を読んでください。

コメントを残す

メールアドレスが公開されることはありません。