ソフトウェアの開発は素晴らしいですが…私たちは皆、それが少し感情的なローラーコースターになる可能性があることに同意できると思います。最初は、すべてが素晴らしいです。数時間ではなくても数日で新しい機能を次々に追加します。あなたは順調です!
数か月早送りすると、開発速度が低下します。以前ほど頑張っていないからですか?あんまり。さらに数か月早送りすると、開発速度がさらに低下します。このプロジェクトに取り組むことはもはや面白くなく、ドラッグになっています。
悪くなる。アプリケーションで複数のバグを発見し始めます。多くの場合、1つのバグを解決すると、2つの新しいバグが作成されます。この時点で、あなたは歌い始めることができます:
コード内の99の小さなバグ。 99の小さなバグ。 1つを降ろし、パッチを当てて、
…コードの127の小さなバグ。
今、このプロジェクトに取り組むことについてどう思いますか?あなたが私のようなら、あなたはおそらくあなたのモチベーションを失い始めます。既存のコードを変更するたびに予測できない結果が生じる可能性があるため、このアプリケーションを開発するのは面倒です。
この経験はソフトウェアの世界では一般的であり、多くのプログラマーがソースコードを捨ててすべてを書き直したい理由を説明できます。
では、この問題の理由は何ですか?
主な原因は複雑さの増大です。私の経験から、全体的な複雑さの最大の原因は、ソフトウェアプロジェクトの大多数で、すべてが接続されているという事実です。各クラスには依存関係があるため、メールを送信するクラスのコードを変更すると、ユーザーは突然登録できなくなります。何故ですか?登録コードはメールを送信するコードに依存しているためです。これで、バグを導入せずに何も変更することはできません。すべての依存関係を追跡することは不可能です。
だからあなたはそれを持っています。私たちの問題の本当の原因は、コードが持つすべての依存関係に起因する複雑さを高めることです。
面白いことに、この問題は何年も前から知られています。これは「大きな泥だんご」と呼ばれる一般的なアンチパターンです。私は、複数の異なる会社で何年にもわたって取り組んだほとんどすべてのプロジェクトで、このタイプのアーキテクチャを見てきました。
では、このアンチパターンとは正確には何ですか?簡単に言えば、各要素が他の要素と依存関係にある場合、大きな泥だんごが発生します。以下に、有名なオープンソースプロジェクトApacheHadoopからの依存関係のグラフを示します。大きな泥だんご(というより、大きな毛糸)を視覚化するには、円を描き、プロジェクトのクラスをその上に均等に配置します。相互に依存するクラスの各ペアの間に線を引くだけです。これで、問題の原因を確認できます。
そこで、私は自分自身に質問をしました。プロジェクトの開始時のように、複雑さを軽減し、それでも楽しむことは可能でしょうか?正直なところ、排除することはできません すべて 複雑さの。新しい機能を追加したい場合は、常にコードの複雑さを上げる必要があります。それにもかかわらず、複雑さは移動して分離することができます。
機械産業について考えてみてください。いくつかの小さな機械店が機械を作成しているとき、彼らは標準的な要素のセットを購入し、いくつかのカスタム要素を作成し、それらをまとめます。これらのコンポーネントを完全に個別に作成し、最後にすべてを組み立てて、わずかな調整を加えることができます。これはどのように可能ですか?彼らは、ボルトのサイズなどの設定された業界標準、および取り付け穴のサイズやそれらの間の距離などの事前の決定によって、各要素がどのように組み合わされるかを知っています。
上記のアセンブリの各要素は、最終製品やその他の部品についてまったく知識のない別の会社から提供されます。各モジュラーエレメントが仕様に従って製造されている限り、計画どおりに最終的なデバイスを作成できます。
それをソフトウェア業界で再現できますか?
もちろんできるよ!インターフェースと制御の反転原理を使用する。最良の部分は、このアプローチが任意のオブジェクト指向言語(Java、C#、Swift、TypeScript、JavaScript、PHP)で使用できるという事実です。リストはどんどん増えています。この方法を適用するために、特別なフレームワークは必要ありません。あなたはただいくつかの簡単なルールに固執し、規律を保つ必要があります。
制御の反転について最初に聞いたとき、私はすぐに解決策を見つけたことに気づきました。これは、既存の依存関係を取得し、インターフェースを使用してそれらを反転させるという概念です。インターフェイスは、メソッドの単純な宣言です。具体的な実装は提供されていません。その結果、それらを接続する方法に関する2つの要素間の合意として使用できます。必要に応じて、モジュラーコネクタとして使用できます。 1つの要素がインターフェースを提供し、別の要素がその実装を提供する限り、それらはお互いについて何も知らなくても一緒に作業できます。すばらしい。
簡単な例で、システムを分離してモジュラーコードを作成する方法を見てみましょう。以下の図は、単純なJavaアプリケーションとして実装されています。あなたはこれでそれらを見つけることができます GitHubリポジトリ 。
Main
のみで構成される非常に単純なアプリケーションがあると仮定しましょう。クラス、3つのサービス、および1つのUtil
クラス。これらの要素は、複数の方法で相互に依存しています。以下に、「大きな泥だんご」アプローチを使用した実装を示します。クラスは単にお互いを呼び出します。それらは緊密に結合されており、他の要素に触れずに1つの要素を単純に取り出すことはできません。このスタイルを使用して作成されたアプリケーションを使用すると、最初は急速に成長できます。このスタイルは、物事を簡単に試すことができるため、概念実証プロジェクトに適していると思います。それでも、メンテナンスでさえ危険であり、1回の変更で予測できないバグが発生する可能性があるため、本番環境に対応したソリューションには適していません。下の図は、この大きな泥だんごの構造を示しています。
より良いアプローチを探すために、依存性注入と呼ばれる手法を使用できます。この方法は、すべてのコンポーネントがインターフェースを介して使用されることを前提としています。要素を切り離すという主張を読んだことがありますが、実際にはそうですか?いいえ。下の図をご覧ください。
現在の状況と大きな泥だんごの唯一の違いは、クラスを直接呼び出すのではなく、インターフェイスを介して呼び出すという事実です。これにより、要素の相互分離がわずかに改善されます。たとえば、Service A
を再利用したい場合別のプロジェクトでは、Service A
を取り出すことでそれを行うことができますそれ自体、Interface A
、およびInterface B
とともにおよびInterface Util
。ご覧のとおり、Service A
まだ他の要素に依存しています。その結果、ある場所でコードを変更したり、別の場所で動作を台無しにしたりする際に、依然として問題が発生します。それでも、Service B
を変更すると問題が発生しますおよびInterface B
、それに依存するすべての要素を変更する必要があります。このアプローチは何も解決しません。私の意見では、要素の上にインターフェイスのレイヤーを追加するだけです。依存関係を挿入することは絶対にしないでください。代わりに、依存関係を完全に取り除く必要があります。独立のための万歳!
私が信じるアプローチは、依存関係のすべての主要な頭痛の種を解決し、依存関係をまったく使用しないことによってそれを行います。コンポーネントとそのリスナーを作成します。リスナーはシンプルなインターフェースです。現在の要素の外部からメソッドを呼び出す必要がある場合は常に、リスナーにメソッドを追加して、代わりに呼び出すだけです。この要素は、ファイルの使用、パッケージ内のメソッドの呼び出し、およびメインフレームワークまたはその他の使用済みライブラリによって提供されるクラスの使用のみが許可されています。以下に、要素アーキテクチャを使用するように変更されたアプリケーションの図を示します。
プライベートエクイティ不動産ファンドリスト
このアーキテクチャでは、Main
のみであることに注意してください。クラスには複数の依存関係があります。すべての要素を相互に接続し、アプリケーションのビジネスロジックをカプセル化します。
一方、サービスは完全に独立した要素です。これで、このアプリケーションから各サービスを取り出して、別の場所で再利用できます。彼らは他のものに依存していません。しかし、待ってください。改善されます。動作を変更しない限り、これらのサービスを再度変更する必要はありません。それらのサービスが本来の目的を果たしている限り、時間の終わりまでそのままにしておくことができます。彼らは専門家によって作成することができます ソフトウェアエンジニア 、またはgoto
で調理した史上最悪のスパゲッティコードを初めてコーダーが侵害したステートメントが混在しています。ロジックがカプセル化されているため、問題ではありません。恐ろしいことかもしれませんが、他のクラスに流出することは決してありません。また、プロジェクト内の作業を複数の開発者間で分割することもできます。各開発者は、別の開発者を中断したり、他の開発者の存在を知らなくても、独自のコンポーネントで独立して作業できます。
最後に、最後のプロジェクトの開始時と同じように、独立したコードの記述をもう一度開始できます。
繰り返し可能な方法で作成できるように、構造要素のパターンを定義しましょう。
要素の最も単純なバージョンは、メイン要素クラスとリスナーの2つで構成されています。要素を使用する場合は、リスナーを実装してメインクラスを呼び出す必要があります。最も単純な構成の図を次に示します。
明らかに、最終的には要素をさらに複雑にする必要がありますが、簡単に追加できます。ロジッククラスがプロジェクト内の他のファイルに依存していないことを確認してください。この要素では、メインフレームワーク、インポートされたライブラリ、およびその他のファイルのみを使用できます。画像、ビュー、サウンドなどのアセットファイルに関しては、将来再利用しやすいように、要素内にカプセル化する必要もあります。フォルダ全体を別のプロジェクトにコピーするだけで、そこにあります。
以下に、より高度な要素を示すグラフの例を示します。使用しているビューで構成されており、他のアプリケーションファイルに依存していないことに注意してください。依存関係をチェックする簡単な方法を知りたい場合は、インポートセクションをご覧ください。現在の要素の外部からのファイルはありますか?その場合は、それらを要素に移動するか、リスナーに適切な呼び出しを追加して、これらの依存関係を削除する必要があります。
また、Javaで作成された簡単な「HelloWorld」の例も見てみましょう。
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
ソースコードフォルダで、最初にクライアントファイルとサーバーファイルを分割します。ブラウザとバックエンドサーバーの2つの異なる環境で実行されるため、これを行うのは合理的です。
次に、各レイヤーのコードをアプリと要素と呼ばれるフォルダーに分割します。要素は独立したコンポーネントを持つフォルダーで構成され、アプリフォルダーはすべての要素を相互に接続し、すべてのビジネスロジックを格納します。
このようにして、要素を異なるプロジェクト間で再利用できますが、アプリケーション固有の複雑さはすべて1つのフォルダーにカプセル化され、要素への単純な呼び出しに限定されることがよくあります。
その実践は常に理論に勝ると信じて、Node.jsとTypeScriptで作成された実際の例を見てみましょう。
これは非常にシンプルなWebアプリケーションであり、より高度なソリューションの開始点として使用できます。それは要素アーキテクチャに従うだけでなく、広範囲に構造的な要素パターンを使用します。
ハイライトから、メインページが要素として区別されていることがわかります。このページには独自のビューが含まれています。したがって、たとえば再利用したい場合は、フォルダ全体をコピーして別のプロジェクトにドロップするだけです。すべてを一緒に配線するだけで、準備が整います。
これは、今日から独自のアプリケーションに要素を導入できることを示す基本的な例です。独立したコンポーネントの区別を開始し、それらのロジックを分離することができます。現在作業しているコードがどれほど乱雑であるかは関係ありません。
この新しいツールセットを使用すると、より保守しやすいコードをより簡単に開発できるようになることを願っています。実際に要素パターンを使用する前に、すべての要点を簡単に要約しましょう。
複数のコンポーネント間の依存関係が原因で、ソフトウェアで多くの問題が発生します。
ある場所で変更を加えることにより、別の場所で予測できない動作を導入できます。
3つの一般的なアーキテクチャアプローチは次のとおりです。
大きな泥だんご。迅速な開発には最適ですが、安定した生産目的にはそれほど適していません。
依存性注入。これは、避けるべき中途半端な解決策です。
要素アーキテクチャ。このソリューションを使用すると、独立したコンポーネントを作成して、他のプロジェクトで再利用できます。安定した製品リリースのために保守可能で優れています。
基本的な要素パターンは、すべての主要なメソッドを持つメインクラスと、外界との通信を可能にするシンプルなインターフェイスであるリスナーで構成されます。
フルスタック要素アーキテクチャを実現するには、最初にフロントエンドをバックエンドコードから分離します。次に、アプリと要素のそれぞれにフォルダーを作成します。要素フォルダーはすべての独立した要素で構成され、アプリフォルダーはすべてを相互に接続します。
これで、独自の要素の作成と共有を開始できます。長期的には、メンテナンスが容易な製品の作成に役立ちます。頑張って、あなたが作成したものを教えてください!
また、コードを時期尚早に最適化していることに気付いた場合は、 時期尚早の最適化の呪いを回避する方法 仲間のApeeScapeerKevinBlochによる。
関連: JSのベストプラクティス:TypeScriptと依存性注入を使用してDiscordボットを構築する複数のコンポーネント間の依存関係のため、コードの保守が難しい場合があります。その結果、ある場所で変更を加えると、別の場所で予測できない動作が発生する可能性があります。
モジュラーアーキテクチャとは、アプリケーションを独立した要素に分割することを意味します。プロジェクト間のすべての依存関係は、問題を見つけて修正するのが難しい原因であると認識しています。完全に独立しているため、これらのコンポーネントのテスト、保守、共有、および将来の再利用が非常に簡単になります。
一般的なアーキテクチャのアプローチは次のとおりです。1)大きな泥だんご:迅速な開発には最適ですが、安定した生産目的にはあまり適していません。 2)依存性注入:避けるべき中途半端な解決策。 3)要素アーキテクチャ:安定した製品リリースのために保守可能で優れています。