JavaRush /Java Blog /Random-JA /コーヒーブレイク #85。私が苦労して学んだ Java の 3 つのレッスン。コードで SOLID 原則を使用する...

コーヒーブレイク #85。私が苦労して学んだ Java の 3 つのレッスン。コードで SOLID 原則を使用する方法

Random-JA グループに公開済み

私が苦労して学んだ Java の 3 つのレッスン

出典: Medium Java の学習は難しいです。私は自分の間違いから学びました。これで、あなたも私の失敗や苦い経験から学ぶことができますが、必ずしも経験する必要はありません。 コーヒーブレイク #85。 私が苦労して学んだ Java の 3 つのレッスン。 コードで SOLID 原則を使用する方法 - 1

1. ラムダは問題を引き起こす可能性があります。

ラムダは多くの場合、コードが 4 行を超え、予想よりも大きくなります。これは作業記憶に負担をかけます。ラムダから変数を変更する必要がありますか? そんなことはできません。なぜ?ラムダが呼び出しサイト変数にアクセスできる場合、スレッドの問題が発生する可能性があります。したがって、ラムダから変数を変更することはできません。しかし、ラムダのハッピーパスは正常に機能します。実行時にエラーが発生すると、次の応答が返されます。
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
ラムダのスタック トレースを追跡するのは困難です。名前は紛らわしく、追跡やデバッグが困難です。ラムダが増える = スタック トレースも増える。ラムダをデバッグする最良の方法は何ですか? 中間結果を使用します。
map(elem -> {
 int result = elem.getResult();
 return result;
});
もう 1 つの良い方法は、高度な IntelliJ デバッグ手法を使用することです。TAB を使用してデバッグするコードを選択し、それを中間結果と結合します。 「ラムダを含む行で停止したときに F7 キーを押すと (ステップイン)、IntelliJ はデバッグが必要なフラグメントを強調表示します。Tab を使用してデバッグするブロックを切り替えることができ、それを決定したら、もう一度 F7 を押します。 ラムダから呼び出しサイト変数にアクセスするにはどうすればよいですか? Final 変数または実際に Final 変数にのみアクセスできます。呼び出し場所変数の周囲にラッパーを作成する必要があります。AtomicType を使用するか、独自のタイプを使用します。作成した変数ラッパーはlambdaで変更することができます。スタックトレースの問題を解決するにはどうすればよいですか? 名前付き関数を使用します。こうすることで、責任のあるコードをすばやく見つけてロジックをチェックし、問題を解決できます。名前付き関数を使用して、不可解なスタック トレースを削除します。同じラムダが繰り返されていますか? それを名前付き関数に配置します。単一の参照点が得られます。各ラムダは生成された関数を取得するため、追跡が困難になります。
lambda$yourNamedFunction
lambda$0
名前付き関数は別の問題を解決します。大きなラムダ。名前付き関数は大きなラムダを分割し、より小さなコード部分を作成し、プラグイン可能な関数を作成します。
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. リストの問題

リスト ( Lists ) を操作する必要があります。データにはHashMap が必要です。ロールの場合はTreeMap が必要です。リストは続きます。そして、コレクションの操作を避ける方法はありません。リストの作り方は?どのようなリストが必要ですか? それは不変であるべきですか、それとも可変であるべきですか? これらの答えはすべて、コードの将来に影響を与えます。後で後悔しないように、事前に適切なリストを選択してください。 Arrays::asList は「エンドツーエンド」リストを作成します。このリストでできないことは何ですか? サイズ変更はできません。彼は不変です。ここで何ができるでしょうか?要素、並べ替え、またはサイズに影響を与えないその他の操作を指定します。Arrays::asListのサイズは不変ですが、内容は不変ではないため、慎重に 使用してください。new ArrayList() は、新しい「可変」リストを作成します。作成されたリストはどのような操作をサポートしますか? これが注意すべき理由です。new ArrayList()を使用して、不変リストから可変リストを作成します。 List::of は「不変」コレクションを作成します。特定の条件下では、そのサイズと内容は変化しません。コンテンツがintなどのプリミティブ データである場合、リストは不変です。次の例を見てください。
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### 変更可能なコンテンツを含む listOF のテスト ### a123
不変オブジェクトを作成し、それをList::of に挿入する必要があります。ただし、List::of は不変性を保証しません。 List::of は、不変性、信頼性、可読性を提供します。いつ可変構造を使用するか、いつ不変構造を使用するかを理解してください。変更すべきではない引数のリストは、不変リストに含める必要があります。可変リストは可変リストである可能性があります。信頼性の高いコードを作成するにはどのようなコレクションが必要かを理解します。

3. 注釈があると速度が遅くなる

注釈を使用しますか? それらを理解していますか?彼らが何をしているか知っていますか?Loggedアノテーションがあらゆるメソッドに適していると考えているなら、それは間違いです。Logged を使用してメソッドの引数をログに記録しました。驚いたことに、それはうまくいきませんでした。
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
このコードの何が問題なのでしょうか? ここには多くの構成ダイジェストがあります。これには何度も遭遇するでしょう。設定は通常のコードと混合されています。それ自体は悪くないのですが、どうしても目を引いてしまいます。注釈は定型コードを減らすために必要です。エンドポイントごとにログ ロジックを作成する必要はありません。トランザクションを設定する必要はありません。@Transactionalを使用してください。アノテーションはコードを抽出することでパターンを削減します。両方ともゲームに参加しているため、ここでは明確な勝者はいません。私は今でも XML と注釈を使用しています。繰り返しのパターンを見つけた場合は、ロジックを注釈に移動するのが最善です。たとえば、ログ記録は優れた注釈オプションです。教訓: 注釈を使いすぎないようにし、XML を忘れないでください。

おまけ: オプションで問題が発生する可能性があります

OptionalorElse を使用します。orElse定数を渡さないと、望ましくない動作が発生します。将来の問題を防ぐために、これを認識しておく必要があります。いくつかの例を見てみましょう。getValue(x) が値を返すと、 getValue(y)が実行されます。orElseのメソッドは、getValue(x) が空ではないOptional value を返した場合に実行されます。
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
orElseGet を使用します。空ではないOptionalのコードは実行されません。
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

結論

Javaを学ぶのは難しいです。Java を 24 時間で学ぶことはできません。スキルを磨きましょう。時間をかけて学び、仕事で優れた能力を発揮しましょう。

コードで SOLID 原則を使用する方法

出典: Cleanthecode 信頼性の高いコードを作成するには、SOLID 原則が必要です。ある時点で、私たちは皆、プログラミングの方法を学ばなければならなくなりました。正直に言いましょう。私たちは愚かでした。そして私たちのコードも同じでした。ありがたいことに、私たちはソリッドを持っています。 コーヒーブレイク #85。 私が苦労して学んだ Java の 3 つのレッスン。 コードで SOLID 原則を使用する方法 - 2

堅固な原則

では、SOLID コードはどうやって書くのでしょうか? 実は簡単なんです。次の 5 つのルールに従うだけです。
  • 単一責任の原則
  • オープンクローズの原理
  • リスコフ置換原理
  • 界面分離原理
  • 依存関係逆転の原則
心配しないで!これらの原則は、見た目よりもはるかに単純です。

単一責任の原則

ロバート C. マーティンは著書の中で、この原則を次のように説明しています。「クラスには、変化する理由が 1 つだけあるべきです。」2 つの例を一緒に見てみましょう。

1.してはいけないこと

ユーザーが次のことをできるようにするUserというクラスがあります。
  • アカウントを登録
  • ログイン
  • 初めてログインしたときに通知を受け取る
このクラスにはいくつかの責任があります。登録プロセスが変更されると、Userクラスも変更されます。ログイン処理や通知処理が変更された場合も同様です。これは、クラスがオーバーロードされていることを意味します。彼には責任が多すぎる。これを修正する最も簡単な方法は、責任をクラスに移して、Userクラスがクラスの結合のみを担当するようにすることです。その後プロセスが変更された場合、変更が必要な 1 つの明確な別個のクラスが存在します。

2. 何をすべきか

新しいユーザーに通知を表示するクラスFirstUseNotificationを想像してください。これは 3 つの機能で構成されます。
  • すでに通知が表示されているかどうかを確認する
  • 通知を表示する
  • 通知をすでに表示済みとしてマークする
このクラスには変更する理由が複数ありますか? いいえ。このクラスには、新しいユーザーへの通知を表示するという明確な機能が 1 つあります。これは、クラスを変更する理由が 1 つあることを意味します。つまり、この目標が変更された場合。したがって、このクラスは単一責任の原則に違反しません。もちろん、変更される可能性のある点はいくつかあります。通知が既読としてマークされる方法や、通知の表示方法が変更される可能性があります。しかし、授業の目的は明確で基本的なものなので、これで問題ありません。

オープンクローズの原理

オープンクローズ原則は、Bertrand Meyer によって考案されました。「ソフトウェア オブジェクト (クラス、モジュール、関数など) は、拡張に対してはオープンであるべきですが、変更に対してはクローズされるべきです。」この原則は実際には非常に単純です。ソース コードを変更せずに新しい機能を追加できるようにコードを作成する必要があります。これは、変更したクラスに依存するクラスを変更する必要がある状況を防ぐのに役立ちます。ただし、この原則を実装するのははるかに困難です。マイヤー氏は継承の使用を提案しました。しかし、それは強いつながりにつながります。これについては、「インターフェイス分離の原則」と「依存関係逆転の原則」で説明します。そこで Martin は、ポリモーフィズムを使用するという、より良いアプローチを思いつきました。従来の継承の代わりに、このアプローチでは抽象基本クラスを使用します。このように、実装を必要とせずに、継承仕様を再利用できます。インターフェイスは一度書き込んだ後、閉じて変更を加えることができます。新しい関数はこのインターフェイスを実装し、拡張する必要があります。

リスコフ置換原理

この原理は、プログラミング言語とソフトウェア方法論への貢献でチューリング賞を受賞したバーバラ・リスコフによって発明されました。彼女は記事の中で、自分の原則を次のように定義しました。「プログラム内のオブジェクトは、プログラムの正しい実行に影響を与えることなく、そのサブタイプのインスタンスと置き換え可能である必要があります。」プログラマとしてこの原則を見てみましょう。正方形があると想像してください。それは長方形である可能性がありますが、正方形は長方形の特殊な形状であるため、論理的に聞こえます。ここでリスコフ置換原則が役に立ちます。コード内で長方形が表示されると予想される場所には、正方形が表示される可能性もあります。ここで、長方形にSetWidth メソッドSetHeightメソッドがあると想像してください。これは、正方形にもこれらのメソッドが必要であることを意味します。残念ながら、これには意味がありません。これは、ここではリスコフ置換原則に違反していることを意味します。

界面分離原理

すべての原則と同様、インターフェイス分離の原則は、思っているよりもはるかに単純です。「多くのクライアント固有のインターフェイスは、1 つの汎用インターフェイスよりも優れています。」単一責任の原則と同様に、目標は副作用と必要な変更の数を減らすことです。もちろん、そのようなコードを意図的に書く人はいません。でも遭遇しやすいんです。前の原則の 2 乗を覚えていますか? ここで、計画を実行することを決定したと想像してください。長方形から正方形を作成します。ここで、正方形にsetWidthsetHeight を強制的に実装していますが、おそらく何もしません。もしそんなことをしたら、幅も高さも期待していたものと違って、おそらく何かが壊れてしまうでしょう。私たちにとって幸運なことに、これは、長方形を使用する場合はどこでも正方形の使用を許可するようになったため、リスコフ置換原則に違反しなくなったことを意味します。ただし、これにより新たな問題が発生します。つまり、インターフェイスを分離するという原則に違反していることになります。派生クラスに、使用しないことを選択した機能を強制的に実装させます。

依存関係逆転の原則

最後の原則は単純です。高レベルのモジュールは再利用可能である必要があり、低レベルのモジュールへの変更の影響を受けるべきではありません。
  • A. 高レベルのモジュールは低レベルのモジュールに依存すべきではありません。どちらも抽象化 (インターフェイスなど) に依存する必要があります。
  • B. 抽象化は詳細に依存すべきではありません。詳細 (具体的な実装) は抽象化に依存する必要があります。
これは、高レベルのモジュールと低レベルのモジュールを分離する抽象化を実装することで実現できます。原則の名前は、依存関係の方向が変化することを示唆していますが、実際はそうではありません。依存関係の間に抽象化を導入することによって依存関係を分離するだけです。結果として、次の 2 つの依存関係が得られます。
  • 高レベルのモジュール (抽象化に応じて)
  • 同じ抽象化に依存する低レベルのモジュール
これは難しいように思えるかもしれませんが、実際には、オープン/クローズ原則とリスコフ置換原則を正しく適用すると、自動的に行われます。それだけです!これで、SOLID を支える 5 つの基本原則を学習しました。これら 5 つの原則を使えば、コードを素晴らしいものにすることができます。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION