ソフトウェア設計のO表記

SOLIDを扱うとき、これらの原則に従わないことが問題につながる可能性があるという事実にしばしば遭遇しました。 問題は知られていますが、形式化は不十分です。 この記事は、可能な解決策のコードを書く過程で生じる典型的な状況と、これから生じる結果を形式化することを目的に書かれています。 不正なコードに直面する理由と、プログラムの成長に伴って問題がどのように成長するかについて説明します。 残念ながら、ほとんどの場合、評価は「多く」および「1」オプションに帰着します。これはO表記法の失敗を示唆しますが、そのような分析でさえ、どのコードがシステムのさらなる開発にとって本当に危険であり、どのコードが許容できるかをよりよく理解するのに役立ちます。



定義



プログラマーが一定のファクターに対して正確な変更を実装するためにプログラムでf(n)論理的に孤立した変更を行う必要がある場合、プログラムの変更にはf(n)アクションから「O」が必要であると言います。nはプログラムのサイズを意味します。



指標



Robert Martinの設計機能のいくつかを検討し、O表記の観点から評価してください。

単一の変更が従属モジュールの他の変更のカスケードを引き起こす場合、設計は困難です。 変更するモジュールが多いほど、設計は難しくなります。
大きな違いは、O(1)とO(n)の変化です。 つまり 私たちの設計では、一定の数の変更が許可されています。または、プログラムが成長するにつれて、変更の数が増加します。 次に、変更自体を検討する必要があります-それらもまた、漸近的な振る舞いで硬直することがあります。 したがって、剛性はO(nm)まで複雑になる可能性があります。 パラメータmは剛性の深さと呼ばれます。 剛性O(n)を許容する設計の剛性の深さのおおよその推定でさえ、人間にとっては複雑すぎます。これは、各変化がさらに深い変化をチェックする必要があるためです。

脆弱性は、単一の変更が行われたときに多くの場所で破損するプログラムの特性です。 多くの場合、変更された部分と概念的な関係がない部分で新しい問題が発生します。
変更が発生するモジュール間の論理的な接続は考慮しません。 したがって、表記法の観点からは、脆弱性と剛性の間に違いはなく、剛性に有効な引数は脆弱性にも適用されます。

他のシステムで役立つ可能性のある部品が含まれている設計は不活性ですが、元のシステムからこれらの部品を分離しようとすることに伴う労力とリスクは大きすぎます。
この定義のリスクと努力は、モジュールのサイズが大きくなるにつれて、元のシステムからモジュールを一定でないものとして抽象化しようとするときに、モジュールで発生する変更の数として解釈できます。 ただし、実践が示すように、抽象化する価値はあります。これにより、モジュール自体に秩序がもたらされ、他のプロジェクトに転送できるようになります。 多くの場合、モジュールを別のプロジェクトに最初に転送する必要があると、他の同様のプロジェクトが表示されます。



粘度
変更を加える必要に直面した開発者は通常、これを行うためのいくつかの方法を見つけます。 設計を保持するものも、保持しないものもあります(つまり、本質的に「ハック」です)。 設計を維持するアプローチがハックよりも実装が難しい場合、設計の粘性は高くなります。 問題を解決することは簡単ですが、正しいことは困難です。 設計を維持する変更を簡単に行えるようにプログラムを設計します。
次の粘度は、O表記の観点から近視と呼ばれます。 はい、最初は開発者は実際にはO(n)の代わりにO(1)を変更する機会があります(剛性または脆弱性のため)が、多くの場合、そのような変更はさらに大きな剛性と脆弱性につながります。 剛性の深さを増やします。 このような「ベル」を無視すると、その後の変更は「ハック」で解決できなくなる可能性があり、剛性(おそらく「ベル」よりも前)を変更するか、システムを良好な状態にする必要があります。 つまり、粘度が検出されたら、すぐにシステムを適切に書き換えることをお勧めします。

次のようになります。Ivanは自分の小さな足を丸くするコードを書く必要があります。 プログラムのさまざまな部分に登ると、彼が疑うように、ボクリャドは複数回喫煙されており、彼は適切な断片を見つけます。 コピーしてモジュールに貼り付け、必要な変更を加えます。



しかし、Ivanは、彼がマウスで抽出したコードがPeterをそこに置き、Svetaによって書かれたモジュールから取り出したことを知りません。 スヴェタは少し縁石を吸った最初の人でしたが、彼女はこのプロセスがマーモットの喫煙に非常に似ていることを知っていました。 彼女はどこかでkarmyachit karmaglotというコードを見つけ、それを自分のモジュールにコピーして修正しました。 同じコードがわずかに異なる形式で何度も現れると、開発者は抽象化の概念を失います。
この状況では、発掘された行動の基礎を変更する必要がある場合、この変更はn箇所で行う必要があることが明らかになります。 各コピーペーストで一意の変更が行われる可能性があるため、これは論理的に無関係な変更をもたらす可能性もあります。 この場合、コンパイル段階では保護が行われないため、別の場所での変更を単に忘れる可能性があります。 したがって、これはO(n)テストの反復にもなります。



ソリッド表記に関するアプリケーション



SRP(単一責任の原則)。 ソフトウェアエンティティには、変更の理由(責任)が1つだけある必要があります。 つまり、たとえば、クラスはビジネスロジックとマッピングに従うべきではありません。 1つの責任を変更する場合、別の責任を破損していないことを確認する必要があります。 つまり、SRPの原則との矛盾により、剛性と脆弱性が生じます。 この原則に従うことは、慣性を取り除き、潜在的に少数の依存関係でモジュールをあるプログラムから別のプログラムに転送するのにも役立ちます。



変更の漸近的動作は、原則に従わない場合と基本的に同じままですが、定数係数は大幅に減少します。 どちらの場合も、クラスのコンテンツ全体を確認する必要があり、エンティティのインターフェイスを変更する場合は、このエンティティとやり取りする場所を確認する必要があります。 次のSRPのみが、インターフェイスとその変更の可能性、および変更後に障害が発生する可能性のある内部実装の量を減らすのに役立ちます。 インターフェイスの説明に切り捨てられた同様の推論は、ISP(インターフェイス分離の原則)に有効です。



OCP(オープンクローズ原則)。 ソフトウェアエンティティ(クラス、モジュール、関数など)は、展開のために開かれ、変更のために閉じられている必要があります。 これは、ソースコードに影響を与えずにモジュールの動作を変更できるように理解する必要があります。 通常、これは継承とポリモーフィズムによって実現されます。 LSPの原則に違反することはOCPの違反であり、DIPはOCPを維持する手段であるため、LSPとDIPの両方に以下を適用できます。 開閉の原則に違反すると、この変更に関して閉じられていないすべてのエンティティに変更を加える必要があります。



かなり些細な状況は、例えば、子クラスのリストの中で変数のタイプを決定するifのチェーンの存在です。 このような構造は、プログラムに複数回現れる場合があります。 そして、新しい子クラスを追加するたびに、そのような各チェーンで適切な変更を行う必要があります。 同様の状況は、子クラスだけでなく、考えられるすべての特定のケースを考慮しない場合にも発生する可能性があります[これは、執筆時点ではなく一般的なケースを指します。 ケースは後で表示される場合があります]。



ここで、同じタイプのm個の変更を行うときの状況を考えてみましょう。これは、開閉の原則との矛盾により、n回の操作が必要です。 その後、すべてをそのままにして、特殊なケースを考慮するためのアーキテクチャをサポートし、一般化しない場合、すべての変更O(mn)の全体的な複雑さが得られます。 この変更に関してm個すべての場所を閉じると、後続の変更はO(m)ではなくO(1)を占有します。 したがって、全体的な複雑さはO(m + n)に削減されます。 つまり、OCPの開始が遅すぎることはありません。



マーティンはこの状況について、最初の変更からどのように閉じるかを推測するべきではないことは言うまでもありません(もちろん確かではありません)。 これは論理的です。なぜなら、非近接のためにO(1 * n)アクションを実行し、その後O(m)アクションを実行して後続の変更から身を閉じるからです。 全体として、全体的な複雑さO(n + m)を取得しますが、同時に、必要になったときにすべてのアクションを正確に実行し、必要かどうかを知らずに事前に何もしません。



パターンとO表記



計算理論のO表記と設計のO表記の間には、もう1つの類似点があります。 「正面」ソリューションよりも典型的な問題を迅速に解決する検索ツリーやヒープなどのアルゴリズムとデータ構造を使用して計算の数を減らすという事実と、良い典型的なソリューションを使用できる優れた設計のプログラマーの操作の数設計パターンと呼ばれます。 パターンの効果は、SOLID原則のコンテキストで評価でき、その結果、O表記のコンテキストで評価できます。



たとえば、Mediatorテンプレートは、2つのオブジェクト間の相互作用のロジックを変更するときにプログラム内の何かを壊す可能性を排除します。これは、テンプレートを完全にカプセル化し、そのような変更の絶え間ない複雑さを保証するためです。



アダプターテンプレートを使用すると、一般的な目的で使用するさまざまなインターフェイスでエンティティを使用(読み取り追加)できます。 このテンプレートを使用すると、システムのサイズに依存しない操作の数に対して、互換性のないインターフェイスを持つ新しいオブジェクトをシステムに埋め込むことができます。



データ構造は、優れた漸近性を持つ操作と悪い操作を持つ操作をサポートできるため、パターンは、一部の変更に対して柔軟に動作し、他の変更に対して厳密に動作します。



合理的な制限



最適化問題に取り組んでいるO表記法を扱うときは、常に最適な漸近性を持つアルゴリズムが問題の解決に最適であるとは限らないことを覚えておく必要があります。 ピラミッド型の並べ替えの方が漸近性が優れているという事実にもかかわらず、3つの要素の配列のバブルによる並べ替えはピラミッド型よりも高速に動作することを理解する必要があります。 nの値が小さい場合、定数が重要な役割を果たしますが、O表記はそれを隠します。 デザインのO表記も同じように機能します。 小規模なプロジェクトの場合、多くのテンプレートをフェンスすることは意味がありません。それらの実装のコストは、「不十分な設計」から行われるべき変更の数を超えるからです。



All Articles