システム
システムの一般的な望ましい特性は次のとおりです。- 最小限の複雑さ- 過度に複雑なプロジェクトは避けるべきです。重要なのはシンプルさと明確さです (最高 = シンプル)。
- メンテナンスの容易さ- アプリケーションを作成するときは、(自分がサポートしていない場合でも) アプリケーションをサポートする必要があることを覚えておく必要があります。そのため、コードは明確かつ明白でなければなりません。
- 弱い結合は、プログラムの異なる部分間の接続の最小数です (OOP 原則を最大限に使用します)。
- 再利用性- 他のアプリケーションでそのフラグメントを再利用できるシステムを設計する。
- 移植性- システムは別の環境に簡単に適応できる必要があります。
- 単一スタイル - 異なるフラグメントの単一スタイルでシステムを設計します。
- 拡張性 (スケーラビリティ) - 基本構造を損なうことなくシステムを改善します (フラグメントを追加または変更しても、残りの部分には影響しません)。
システム設計段階
- ソフトウェア システム- 一般的な形式のアプリケーションを設計します。
- サブシステム/パッケージへの分離- 論理的に分離可能な部分を定義し、それらの間の相互作用のルールを定義します。
- サブシステムをクラスに分割する- システムの一部を特定のクラスとインターフェイスに分割し、それらの間の相互作用を定義します。
- クラスをメソッドに分割すると、このクラスのタスクに基づいて、クラスに必要なメソッドが完全に定義されます。メソッド設計 - 個々のメソッドの機能の詳細な定義。
システム設計の主な原則と概念
遅延初期化イディオム アプリケーションは、使用されるまでオブジェクトの作成に時間を費やさないため、初期化プロセスが高速化され、ガベージ コレクターの負荷が軽減されます。ただし、モジュール性の違反につながる可能性があるため、これをやりすぎるべきではありません。すべての設計ステップを、 main などの特定の部分、またはファクトリーのように動作するクラスに移動する価値があるかもしれません。優れたコードの側面の 1 つは、頻繁に繰り返される定型コードがないことです。原則として、そのようなコードは別のクラスに配置され、適切なタイミングで呼び出すことができます。 AOPこれとは別に、アスペクト指向プログラミングについて 触れたいと思います。これは、エンドツーエンドのロジックを導入したプログラミングです。つまり、繰り返しコードがクラス (アスペクト) に入れられ、特定の条件に達したときに呼び出されます。たとえば、特定の名前のメソッドにアクセスする場合や、特定の型の変数にアクセスする場合です。コードがどこから呼び出されるのかがすぐには分からないため、混乱を招くことがありますが、それでも、これは非常に便利な機能です。特に、キャッシュまたはロギングの場合、通常のクラスに追加のロジックを追加せずに、この機能を追加します。OAP について詳しくは、こちらをご覧ください。 Kent Beck によるシンプルなアーキテクチャを設計するための 4 つのルール- 表現力- クラスの目的を明確に表現する必要性は、正しい名前付け、小さなサイズ、および単一責任の原則の遵守によって達成されます (これについては後で詳しく説明します)。
- 最小限のクラスとメソッド- クラスをできるだけ小さく一方向に分割したいと考えているため、行き過ぎてしまう可能性があります (アンチパターン - ショットガンニング)。この原則では、システムをコンパクトに保ち、行き過ぎないようにし、くしゃみごとにクラスを作成する必要があります。
- 重複の欠如- 混乱を招く余分なコードはシステム設計が不十分であることを示しており、別の場所に移動されます。
- すべてのテストの実行- すべてのテストに合格したシステムは制御されます。変更はテストの失敗につながる可能性があるためです。これにより、メソッドの内部ロジックの変更が予期される動作の変更にもつながったことがわかります。 。
インターフェース
おそらく、適切なクラスを作成する最も重要な段階の 1 つは、クラスの実装の詳細を隠す適切な抽象化を表現し、同時に相互に明確に一貫性のあるメソッドのグループを表現する適切なインターフェイスを作成することです。 。SOLID 原則の 1 つであるインターフェイス分離を詳しく見てみましょう。クライアント (クラス) は、使用しない不必要なメソッドを実装すべきではありません。つまり、このインターフェイスの唯一のタスクを実行することを目的とした最小限のメソッドでインターフェイスを構築することについて話している場合 (私にとって、これは単一の責任に非常に似ています)、より小さいメソッドをいくつか作成する方が良いでしょう。 1 つの肥大化したインターフェイスの代わりに、1 つのインターフェイスを使用します。幸いなことに、継承の場合と同様に、クラスは複数のインターフェイスを実装できます。また、インターフェイスの正しい名前についても覚えておく必要があります。名前は、そのタスクをできるだけ正確に反映する必要があります。そしてもちろん、短ければ短いほど、混乱は少なくなります。通常、ドキュメントのコメントはインターフェイス レベルで記述されます。これは、メソッドが何をすべきか、どのような引数を受け取り、何を返すかを詳細に説明するのに役立ちます。クラス
クラスの内部構成を見てみましょう。というか、クラスを構築するときに従うべきいくつかのビューとルール。通常、クラスは、特定の順序で配置された変数のリストで始まる必要があります。- パブリック静的定数。
- プライベート静的定数。
- プライベートインスタンス変数。
クラスサイズ
次にクラスサイズについてお話したいと思います。 SOLID の原則の 1 つである単一責任を思い出してください。 単一責任- 単一責任の原則。それは、各オブジェクトには 1 つの目標 (責任) だけがあり、そのすべてのメソッドのロジックはそれを保証することを目的としていると述べています。つまり、これに基づいて、大規模で肥大化したクラス (その性質上、アンチパターンである「神聖なオブジェクト」) を避ける必要があり、クラス内に多様で異質なロジックのメソッドが多数ある場合は、次のことを考える必要があります。それをいくつかの論理部分 (クラス) に分割することについて。これにより、特定のクラスのおおよその目的がわかっていれば、メソッドの目的を理解するのに多くの時間が必要なくなるため、コードの読みやすさが向上します。クラス名にも注意する必要があります。クラス名には、含まれるロジックが反映されている必要があります。たとえば、名前に 20 語以上の単語が含まれるクラスがある場合、リファクタリングについて考える必要があります。すべての自尊心のあるクラスは、これほど多数の内部変数を持つべきではありません。実際、各メソッドはそれらの 1 つまたは複数と連携して動作するため、クラス内でより大きな結合が生じます (クラスは単一の全体として存在する必要があるため、まさにそうあるべきです)。その結果、クラスの一貫性が高まると、クラス自体の一貫性が低下し、当然、クラスの数は増加します。一部の人にとって、これは面倒で、特定の大きなタスクがどのように機能するかを確認するには、もっと授業に行く必要があります。とりわけ、各クラスは小さなモジュールであり、他のクラスとの接続は最小限にする必要があります。この分離により、クラスにロジックを追加するときに必要な変更の数が減ります。オブジェクト
カプセル化
ここではまず、OOPカプセル化の原則の 1 つについて説明します。したがって、実装を隠すことは、変数間にメソッド層を作成することにはなりません (単一のメソッド、ゲッターとセッターによるアクセスを軽率に制限することは、カプセル化のポイント全体が失われるため、良くありません)。アクセスを隠すことは抽象化を形成することを目的としています。つまり、クラスはデータを操作するための共通の具象メソッドを提供します。しかし、ユーザーはこのデータをどのように扱うかを正確に知る必要はありません。それは機能しますし、それで問題ありません。デメテルの法則
デメテルの法則を考慮することもできます。これは、クラスおよびメソッド レベルでの複雑さの管理に役立つ小さなルールのセットです。そこで、オブジェクトがありCar
、それにメソッド - があると仮定しましょうmove(Object arg1, Object arg2)
。デメテルの法則によれば、このメソッドは次の呼び出しに限定されます。
- オブジェクト自体
Car
(つまり this) のメソッド。 - で作成されたオブジェクトのメソッド
move
。 - 引数として渡されたオブジェクトのメソッド -
arg1
、arg2
; - 内部オブジェクトのメソッド
Car
(これと同じ)。
データ構造
データ構造は、関連する要素のコレクションです。オブジェクトをデータ構造として考えると、それはメソッドによって処理されるデータ要素のセットであり、その存在は暗黙的に暗黙的に示されます。つまり、データを保存したり、保存されたデータを操作(処理)することを目的としたオブジェクトです。通常のオブジェクトとの主な違いは、オブジェクトが、その存在が暗黙的に示されているデータ要素を操作するメソッドのセットであることです。わかりますか?通常のオブジェクトでは、主な側面はメソッドであり、内部変数はその正しい動作を目的としていますが、データ構造ではその逆です。メソッドは、ここでの主要な要素である格納された要素をサポートし、その要素の操作を支援します。データ構造の 1 つのタイプは、データ転送オブジェクト (DTO)です。これは、パブリック変数を持ち、データベースを操作するときにデータを渡したり、ソケットからのメッセージを解析したりするメソッド (または読み取り/書き込みメソッドのみ) を持たないクラスです。通常、このようなオブジェクト内のデータは長期間保存されず、保存されます。アプリケーションが動作するエンティティにほぼ即座に変換されます。エンティティもデータ構造ですが、その目的はアプリケーションのさまざまなレベルでビジネス ロジックに参加することであり、DTO はアプリケーションとの間でデータを転送することです。DTO の例:@Setter
@Getter
@NoArgsConstructor
public class UserDto {
private long id;
private String firstName;
private String lastName;
private String email;
private String password;
}
すべてが明らかであるように見えますが、ここでハイブリッドの存在について学びます。 ハイブリッドは、重要なロジックを処理し、内部要素を保存し、それらへのアクセス メソッド (get/set) を保存するメソッドを含むオブジェクトです。このようなオブジェクトは乱雑であり、新しいメソッドを追加することが困難になります。これらは、要素の保存やある種のロジックの実行など、その目的が明確ではないため、使用しないでください。考えられるオブジェクトのタイプについては、ここで読むことができます。
変数作成の原則
変数について少し考えてみましょう。変数を作成するための原則が何であるかを考えてみましょう。- 理想的には、変数を使用する直前に変数を宣言して初期化する必要があります (変数を作成して忘れるのではなく)。
- 可能な限り、初期化後に値が変更されないように、変数を Final として宣言してください。
- カウンタ変数を忘れないでください (通常、カウンタ変数は何らかのループで使用します
for
。つまり、カウンタ変数を忘れずにリセットしてください。そうしないと、ロジック全体が壊れる可能性があります)。 - コンストラクターで変数を初期化してみる必要があります。
new SomeObject()
参照 ( )ありまたはなしでオブジェクトを使用する選択肢がある場合は、参照なし( ) を選択してください。これは、このオブジェクトが一度使用されると、次回のガベージ コレクション中に削除され、リソースが無駄にならないためです。- 変数の有効期間 (変数の作成から最後のアクセスまでの距離) をできるだけ短くします。
- ループ内で使用される変数は、ループを含むメソッドの先頭ではなく、ループの直前に初期化します。
- 常に最も制限されたスコープから開始し、必要な場合にのみ拡張します (変数を可能な限りローカルにするように努める必要があります)。
- 各変数は 1 つの目的にのみ使用してください。
- 隠れた意味を持つ変数は避けてください (変数は 2 つのタスクの間で引き裂かれています。つまり、その型がどちらかのタスクを解決するのに適していないことを意味します)。
メソッド
ロジックの実装、つまりメソッドに直接移りましょう。-
最初のルールはコンパクトさです。理想的には、1 つのメソッドが 20 行を超えないようにする必要があるため、たとえばパブリック メソッドが大幅に「増大」した場合は、分離されたロジックをプライベート メソッドに移動することを検討する必要があります。
-
2 番目のルールは、コマンド
if
、などのブロックを高度にネストしないことです。else
while
これにより、コードの可読性が大幅に低下します。理想的には、ネストは 2 ブロック以下にする必要があります{}
。これらのブロックのコードをコンパクトかつシンプルにすることもお勧めします。
-
3 番目のルールは、メソッドは 1 つの操作のみを実行する必要があるということです。つまり、メソッドが複雑で多様なロジックを実行する場合、それをサブメソッドに分割します。その結果、メソッド自体はファサードとなり、その目的は他のすべての操作を正しい順序で呼び出すことです。
しかし、操作が単純すぎて別のメソッドを作成できない場合はどうすればよいでしょうか? 確かに、大砲からスズメを撃ち出すようなことがあるかもしれませんが、小さな方法には多くの利点があります。
- コードの読み取りが容易になります。
- メソッドは開発の過程でより複雑になる傾向があり、メソッドが最初から単純であれば、その機能を複雑にするのは少し簡単です。
- 実装の詳細を非表示にする。
- コードの再利用を容易にする。
- コードの信頼性が高くなります。
-
下向きのルールは、コードは上から下に読む必要があるということです。下位になるほどロジックが深くなり、逆に上位になるほどメソッドが抽象化されます。たとえば、switch コマンドは非常にコンパクトではなく、望ましくないものですが、スイッチを使用せずにいられない場合は、スイッチをできるだけ下位のメソッドに移動するようにしてください。
-
メソッドの引数- 理想的な引数はいくつですか? 理想的には、何もないのです))しかし、本当にそうなるのでしょうか?ただし、その数が少ないほど、このメソッドの使用とテストが容易になるため、できるだけ少なくするようにしてください。疑問がある場合は、多数の入力引数を持つメソッドを使用するすべてのシナリオを推測してみてください。
-
これとは別に、入力引数としてブール値フラグを持つメソッドを強調表示したいと思います。これは、当然のことながら、このメソッドが複数の操作 (true の場合は 1 つ、false - 別の操作) を実装していることを意味するためです。上でも書きましたが、これは良くないので、できれば避けるべきです。
-
メソッドに大量の引数が含まれる場合(極値は 7 ですが、2 ~ 3 個以降は考慮する必要があります)、いくつかの引数を別のオブジェクトにグループ化する必要があります。
-
複数の同様のメソッド (オーバーロード) がある場合は、同様のパラメーターを同じ順序で渡す必要があります。これにより、読みやすさと使いやすさが向上します。
-
メソッドにパラメータを渡すときは、それらがすべて使用されることを確認する必要があります。そうでない場合、引数は何のためにあるのでしょうか? それをインターフェースから切り取るだけで終わりです。
-
try/catch
これはその性質上あまり見栄えがよくないので、中間の別のメソッド (例外を処理するメソッド) に移動するのが良いでしょう。public void exceptionHandling(SomeObject obj) { try { someMethod(obj); } catch (IOException e) { e.printStackTrace(); } }
GO TO FULL VERSION