CodeGym /Java Blog /ランダム /コーディング ルール: システムの作成からオブジェクトの操作まで
John Squirrels
レベル 41
San Francisco

コーディング ルール: システムの作成からオブジェクトの操作まで

ランダム グループに公開済み
皆さん、こんにちは!今日は良いコードの書き方についてお話したいと思います。もちろん、誰もがすぐに Clean Code のような本を読みたがるわけではありません。これらの本には大量の情報が含まれていますが、最初はあまり明らかではないからです。そして読み終わる頃には、コードを書きたいという欲求がすべて失われているかもしれません。これらすべてを考慮して、今日はより良いコードを書くための小さなガイド (推奨事項の小さなセット) を提供したいと思います。この記事では、システムの作成と、インターフェイス、クラス、オブジェクトの操作に関連する基本的なルールと概念について説明します。この記事を読むのにそれほど時間はかかりませんし、退屈しないことを願っています。上から下に向かって、つまりアプリケーションの一般的な構造からより狭い詳細に向かって作業していきます。 コーディングルール: システムの作成からオブジェクトの操作まで - 1

システム

一般に、システムの望ましい特性は次のとおりです。
  • 最小限の複雑さ。過度に複雑なプロジェクトは避けなければなりません。最も重要なことは、シンプルさと明確さです(シンプルであるほど良い)。
  • メンテナンスが容易。アプリケーションを作成するときは、(たとえあなたが個人的にアプリケーションを保守する責任を負わないとしても) アプリケーションを保守する必要があることを覚えておく必要があります。これは、コードが明確かつ明白でなければならないことを意味します。
  • 疎結合。これは、プログラムの異なる部分間の依存関係の数を最小限に抑えることを意味します (OOP 原則への準拠を最大限に高めます)。
  • 再利用性。私たちは、他のアプリケーションでコンポーネントを再利用できるようにシステムを設計しています。
  • 携帯性。システムを別の環境に適応させるのは簡単でなければなりません。
  • ユニフォームスタイル。私たちは、さまざまなコンポーネントで統一されたスタイルを使用してシステムを設計します。
  • 拡張性 (スケーラビリティ)。システムの基本構造を損なうことなくシステムを強化できます (コンポーネントの追加または変更が他のすべてのコンポーネントに影響を与えるべきではありません)。
変更や新しい機能を必要としないアプリケーションを構築することは事実上不可能です。私たちの発案が時代に追いつくためには、常に新しい部品を追加する必要があります。ここでスケーラビリティが重要になります。スケーラビリティとは、基本的にアプリケーションを拡張し、新しい機能を追加し、より多くのリソースを使用して (つまり、より大きな負荷で) 動作することです。言い換えれば、新しいロジックを追加しやすくするために、モジュール性を高めてシステムの結合を減らすなど、いくつかのルールに固執します。コーディングルール: システムの作成からオブジェクトの操作まで - 2

画像ソース

システム設計の段階

  1. ソフトウェアシステム。アプリケーション全体を設計します。
  2. サブシステム/パッケージに分割します。論理的に異なる部分を定義し、それらの間の相互作用のルールを定義します。
  3. サブシステムのクラスへの分割。システムの一部を特定のクラスとインターフェイスに分割し、それらの間の対話を定義します。
  4. クラスをメソッドに分割する。割り当てられた責任に基づいて、クラスに必要なメソッドの完全な定義を作成します。
  5. メソッドの設計。個々のメソッドの機能の詳細な定義を作成します。
通常、この設計は一般の開発者が担当し、アプリケーションのアーキテクトは上記の点を担当します。

システム設計の一般原則と概念

遅延初期化。このプログラミング手法では、アプリケーションは実際に使用されるまでオブジェクトの作成に時間を無駄にしません。これにより、初期化プロセスが高速化され、ガベージ コレクターの負荷が軽減されます。とはいえ、モジュール性の原則に違反する可能性があるため、これをやりすぎないでください。おそらく、構築のすべてのインスタンスを、メイン メソッドやファクトリクラスなどの特定の部分に移動する価値があります。優れたコードの特徴の 1 つは、反復的な定型的なコードがないことです。原則として、そのようなコードは必要なときに呼び出せるように別のクラスに配置されます。

AOP

アスペクト指向プログラミングにも注目したいと思います。このプログラミング パラダイムは、透過的なロジックを導入することがすべてです。つまり、繰り返しコードをクラス(アスペクト)に入れ、特定の条件が満たされたときに呼び出されます。たとえば、特定の名前のメソッドを呼び出したり、特定の型の変数にアクセスしたりする場合です。コードがどこから呼び出されているかがすぐには分からないため、場合によっては混乱を招く可能性がありますが、これは依然として非常に便利な機能です。特にキャッシュやロギングの場合。通常のクラスにロジックを追加せずに、この機能を追加します。 Kent Beck のシンプルなアーキテクチャに関する 4 つのルール:
  1. 表現力 — クラスの意図を明確に表現する必要があります。これは、適切な名前付け、小さいサイズ、および単一責任の原則 (これについては後で詳しく説明します) の遵守によって実現されます。
  2. クラスとメソッドの最小数 — クラスをできるだけ小さく、焦点を絞ったものにしたいと考えているため、やりすぎると、散弾銃手術のアンチパターンが発生する可能性があります。この原則では、システムをコンパクトに保ち、行き過ぎないようにし、考えられるすべてのアクションに対して別のクラスを作成する必要があります。
  3. 重複なし — 混乱を引き起こし、システム設計が最適ではないことを示す重複コードは抽出され、別の場所に移動されます。
  4. すべてのテストを実行する — すべてのテストに合格するシステムは管理可能です。変更を加えるとテストが失敗する可能性があり、メソッドの内部ロジックを変更すると、システムの動作も予期せぬ形で変化することがわかりました。

個体

システムを設計するときは、よく知られている SOLID 原則を考慮する価値があります。

S (単一責任)、 O (オープン-クローズ)、 L (リスコフ置換)、 I (インターフェース分離)、 D (依存性反転)。

個々の原則については詳しく説明しません。これはこの記事の範囲を少し超えますが、詳細については、ここを参照してください。

インターフェース

おそらく、適切に設計されたクラスを作成するための最も重要な手順の 1 つは、適切な抽象化を表す適切に設計されたインターフェイスを作成し、クラスの実装の詳細を隠し、同時に相互に明確に一貫性のあるメソッドのグループを提示することです。SOLID 原則の 1 つであるインターフェイスの分離を詳しく見てみましょう。クライアント (クラス) は、使用しない不必要なメソッドを実装すべきではありません。言い換えれば、インターフェイスの唯一のジョブを実行することを目的とした最小限のメソッドでインターフェイスを作成することについて話している場合 (これは単一責任の原則に非常に似ていると思います)、代わりにいくつかの小さなメソッドを作成する方が良いということです。 1 つの肥大化したインターフェイス。幸いなことに、クラスは複数のインターフェイスを実装できます。インターフェイスに適切な名前を付けることを忘れないでください。名前は、割り当てられたタスクをできるだけ正確に反映する必要があります。そしてもちろん、短ければ短いほど、混乱は少なくなります。ドキュメントのコメントは通常、インターフェイス レベルで記述されます。これらのコメントは、各メソッドが何をすべきか、どのような引数を受け取り、何を返すかについての詳細を提供します。

クラス

コーディングルール: システムの作成からオブジェクトの操作まで - 3

画像ソース

クラスが内部的にどのように配置されているかを見てみましょう。というか、クラスを書くときに従うべきいくつかの視点とルール。原則として、クラスは特定の順序で変数のリストから始める必要があります。
  1. パブリック静的定数。
  2. プライベート静的定数。
  3. プライベートインスタンス変数。
次に、引数が最も少ないものから引数が最も多いものまで、さまざまなコンストラクターが順番に続きます。これらの後に、最もパブリックなメソッドから最もプライベートなメソッドが続きます。一般に、制限したい機能の実装を隠すプライベート メソッドは最下位にあります。

クラスサイズ

次にクラスの規模についてお話したいと思います。SOLID 原則の 1 つである単一責任原則を思い出してみましょう。各オブジェクトの目的 (責任) は 1 つだけであり、そのすべてのメソッドのロジックはそれを達成することを目的としていると述べています。これは、大規模で肥大化したクラス (実際には God オブジェクトのアンチパターンです) を避けるように指示します。また、あらゆる種類の異なるロジックを含む多数のメソッドがクラスに詰め込まれている場合は、それをクラスに分割することを考える必要があります。いくつかの論理部分 (クラス)。これにより、特定のクラスのおおよその目的がわかっていれば、各メソッドの目的を理解するのにそれほど時間はかからなくなるため、コードの可読性が向上します。また、クラス名に注目してください。クラス名には、それに含まれるロジックが反映されている必要があります。たとえば、名前に 20 以上の単語が含まれるクラスがある場合、リファクタリングについて考える必要があります。自尊心のあるクラスは、それほど多くの内部変数を持たないでください。実際、各メソッドは 1 つまたはいくつかのメソッドで動作し、クラス内に多くの凝集を引き起こします (クラスは統一された全体である必要があるため、これは当然のことです)。その結果、クラスの結束力が高まるとクラスの規模は縮小し、当然クラス数は増加します。特定の大きなタスクがどのように機能するかを確認するには、クラス ファイルをさらに詳しく調べる必要があるため、これは一部の人にとって煩わしいものです。さらに、各クラスは小さなモジュールであり、他のクラスとの関連性は最小限に抑える必要があります。この分離により、クラスにロジックを追加するときに必要な変更の数が減ります。各メソッドは 1 つまたはいくつかのメソッドと連携し、クラス内に多くの凝集を引き起こします (クラスは統一された全体である必要があるため、これは当然のことです)。その結果、クラスの結束力が高まるとクラスの規模は縮小し、当然クラス数は増加します。特定の大きなタスクがどのように機能するかを確認するには、クラス ファイルをさらに詳しく調べる必要があるため、これは一部の人にとって煩わしいものです。さらに、各クラスは小さなモジュールであり、他のクラスとの関連性は最小限に抑える必要があります。この分離により、クラスにロジックを追加するときに必要な変更の数が減ります。各メソッドは 1 つまたはいくつかのメソッドと連携し、クラス内に多くの凝集を引き起こします (クラスは統一された全体である必要があるため、これは当然のことです)。その結果、クラスの結束力が高まるとクラスの規模は縮小し、当然クラス数は増加します。特定の大きなタスクがどのように機能するかを確認するには、クラス ファイルをさらに詳しく調べる必要があるため、これは一部の人にとって煩わしいものです。さらに、各クラスは小さなモジュールであり、他のクラスとの関連性は最小限に抑える必要があります。この分離により、クラスにロジックを追加するときに必要な変更の数が減ります。結束力が高まるとクラスの規模が縮小し、当然クラスの数は増加します。特定の大きなタスクがどのように機能するかを確認するには、クラス ファイルをさらに詳しく調べる必要があるため、これは一部の人にとって煩わしいものです。さらに、各クラスは小さなモジュールであり、他のクラスとの関連性は最小限に抑える必要があります。この分離により、クラスにロジックを追加するときに必要な変更の数が減ります。結束力が高まるとクラスの規模が縮小し、当然クラスの数は増加します。特定の大きなタスクがどのように機能するかを確認するには、クラス ファイルをさらに詳しく調べる必要があるため、これは一部の人にとって煩わしいものです。さらに、各クラスは小さなモジュールであり、他のクラスとの関連性は最小限に抑える必要があります。この分離により、クラスにロジックを追加するときに必要な変更の数が減ります。

オブジェクト

カプセル化

ここではまず、OOP の原則であるカプセル化について説明します。実装を隠すことは、変数を隔離するメソッドを作成することにはなりません (個々のメソッド、ゲッター、セッターによるアクセスを軽率に制限することは、カプセル化のポイント全体が失われるため、良くありません)。アクセスを隠すことは、抽象化を形成することを目的としています。つまり、クラスは、データを操作するために使用する共有の具象メソッドを提供します。そしてユーザーは、このデータをどのように扱っているかを正確に知る必要はありません。それが機能するだけで十分です。

デメテルの法則

また、デメテルの法則を考慮することもできます。これは、クラスおよびメソッド レベルでの複雑さの管理に役立つ小さなルールのセットです。Carオブジェクトがあり、それにmove(Object arg1, Object arg2)メソッドがあるとします。デメテルの法則によれば、このメソッドは次の呼び出しに限定されます。
  • Carオブジェクト自体 (つまり、「this」オブジェクト)のメソッド。
  • moveメソッド内で作成されたオブジェクトのメソッド。
  • 引数として渡されるオブジェクトのメソッド ( arg1arg2 )。
  • 内部Carオブジェクトのメソッド (これも「this」)。
言い換えれば、デメテルの法則は、親が子供に言うようなものです。「友達とは話していいけど、知らない人とは話せないよ」。

データ構造

データ構造は、関連する要素のコレクションです。オブジェクトをデータ構造として考えると、メソッドが操作する一連のデータ要素が存在します。これらのメソッドの存在は暗黙的に想定されています。つまり、データ構造は、保存されたデータを保存し、そのデータを操作 (処理) することを目的としたオブジェクトです。通常のオブジェクトとの主な違いは、通常のオブジェクトが暗黙的に存在すると想定されるデータ要素を操作するメソッドのコレクションであることです。わかりますか?通常のオブジェクトの主な側面はメソッドです。内部変数は、それらの正しい動作を容易にします。しかし、データ構造では、保存されたデータ要素の操作をサポートするためのメソッドが存在します。これはここで最も重要です。データ構造の 1 つのタイプは、データ転送オブジェクト (DTO) です。これはパブリック変数を持ち、メソッドを持たない (または読み取り/書き込みのメソッドのみ) ク​​ラスで、データベースの操作時やソケットからのメッセージの解析時などにデータを転送するために使用されます。通常、データはこのようなオブジェクトに長期間保存されません。これは、アプリケーションが動作するエンティティのタイプにほぼ即座に変換されます。エンティティもデータ構造ですが、その目的はアプリケーションのさまざまなレベルでビジネス ロジックに参加することです。DTO の目的は、アプリケーションとの間でデータを転送することです。DTO の例: もデータ構造ですが、その目的はアプリケーションのさまざまなレベルでビジネス ロジックに参加することです。DTO の目的は、アプリケーションとの間でデータを転送することです。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) メソッドも含むメソッドを備えたオブジェクトです。このようなオブジェクトは乱雑であり、新しいメソッドを追加することが困難になります。これらは、要素を保存するのか、ロジックを実行するのか、その目的が明確ではないため、避ける必要があります。

変数作成の原則

変数について少し考えてみましょう。より具体的には、作成時にどのような原則が適用されるかを考えてみましょう。
  1. 理想的には、変数を使用する直前に宣言して初期化する必要があります (作成したまま忘れないでください)。
  2. 可能な限り、初期化後に値が変更されないように、変数を Final として宣言してください。
  3. カウンタ変数について忘れないでください。通常、これは何らかのforループで使用されます。つまり、ゼロにすることを忘れないでください。そうしないと、すべてのロジックが壊れる可能性があります。
  4. コンストラクターで変数を初期化してみる必要があります。
  5. オブジェクトを参照付きで使用するか参照なし ( new SomeObject() )で使用するかの選択肢がある場合は、参照なしを選択してください。オブジェクトが使用された後、そのオブジェクトは次のガベージ コレクション サイクル中に削除され、そのリソースが無駄にならないからです。
  6. 変数の存続期間 (変数の作成から最後に参照されるまでの期間) をできるだけ短くしてください。
  7. ループ内で使用される変数は、ループを含むメソッドの先頭ではなく、ループの直前に初期化します。
  8. 常に最も制限されたスコープから開始し、必要な場合にのみ拡張します (変数を可能な限りローカルにするように努める必要があります)。
  9. 各変数は 1 つの目的にのみ使用してください。
  10. 隠れた目的を持つ変数、たとえば 2 つのタスクに分割された変数は避けてください。これは、その型がタスクの 1 つを解決するのに適していないことを意味します。

メソッド

コーディング ルール: システムの作成からオブジェクトの操作まで - 4

映画『スター・ウォーズ エピソード3/シスの復讐』より(2005年)

ロジックの実装、つまりメソッドの実装に直接進みましょう。
  1. ルール #1 — コンパクトさ。理想的には、メソッドは 20 行を超えてはなりません。これは、パブリック メソッドが大幅に「増大」した場合、ロジックを分割して別のプライベート メソッドに移動することを検討する必要があることを意味します。

  2. ルール 2 — ifelsewhileおよびその他のステートメントには、ブロックを大量にネストしてはなりません。ネストが多くなると、コードの可読性が大幅に低下します。理想的には、ネストされた{}ブロックは2 つ以下にする必要があります。

    また、これらのブロック内のコードをコンパクトかつシンプルに保つことも望ましいです。

  3. ルール #3 — メソッドは 1 つの操作のみを実行する必要があります。つまり、メソッドがあらゆる種類の複雑なロジックを実行する場合、それをサブメソッドに分割します。その結果、メソッド自体は、他のすべての操作を正しい順序で呼び出すことを目的としたファサードになります。

    しかし、操作が単純すぎて別のメソッドに入れることができない場合はどうすればよいでしょうか? 確かに、スズメに向かって大砲を発砲するような気分になることもありますが、小さな方法には多くの利点があります。

    • コードの理解が向上します。
    • 開発が進むにつれて、メソッドはより複雑になる傾向があります。メソッドが最初から単純であれば、その機能を複雑にするのは少し簡単になります。
    • 実装の詳細は隠されています。
    • コードの再利用が容易になります。
    • より信頼性の高いコード。

  4. ステップダウン ルール — コードは上から下に読む必要があります。下から読むほど、ロジックを深く掘り下げることができます。逆も同様で、上位に行くほどメソッドは抽象化されます。たとえば、switch ステートメントはコンパクトではなく、望ましくありませんが、switch の使用を避けられない場合は、switch をできるだけ下位のメソッドに移動するようにしてください。

  5. メソッドの引数 — 理想的な数はどれくらいですか? 理想的には、何もありません:) しかし、それは本当に起こりますか?ただし、引数が少ないほどメソッドの使用とテストが容易になるため、引数はできる限り少なくするように努める必要があります。疑問がある場合は、多数の入力パラメーターを使用してメソッドを使用するすべてのシナリオを予想してみてください。

  6. さらに、入力パラメーターとしてブール値フラグを持つメソッドを分離することをお勧めします。これは、それだけでメソッドが複数の操作を実行することを意味するためです (true の場合は 1 つの操作を実行し、false の場合は別の操作を実行します)。上でも書きましたが、これは良くないので、できれば避けるべきです。

  7. メソッドに多数の入力パラメーターがある場合 (極端な値は 7 ですが、実際には 2 ~ 3 個から考え始める必要があります)、引数の一部を別のオブジェクトにグループ化する必要があります。

  8. 類似した (オーバーロードされた) メソッドが複数ある場合は、類似したパラメーターを同じ順序で渡す必要があります。これにより、読みやすさと使いやすさが向上します。

  9. パラメータをメソッドに渡すときは、それらがすべて使用されていることを確認する必要があります。そうでない場合、なぜそれらが必要なのでしょうか。未使用のパラメータをインターフェイスから削除して、作業は完了です。

  10. try/catch は本質的にあまり良くないので、別の中間メソッド (例外を処理するメソッド) に移動することをお勧めします。

    
    public void exceptionHandling(SomeObject obj) {
        try {  
            someMethod(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

上で重複コードについて説明しましたが、もう一度繰り返します。コードが繰り返されるメソッドがいくつかある場合は、それを別のメソッドに移動する必要があります。これにより、メソッドとクラスの両方がよりコンパクトになります。名前を管理するルールを忘れないでください。クラス、インターフェイス、メソッド、変数に適切な名前を付ける方法の詳細については、この記事の次の部分で説明します。しかし、今日私があなたにできるのはそれだけです。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION