序文
if ... elseは、すべての高水準プログラミング言語に必須の機能です。しかし、実際のコードは、多くの場合、多すぎます...その他。if ... elseが必要な場合でも、if ... elseの誤用はコードの可読性と保守性に大きな害を及ぼし、ソフトウェアシステム全体を危険にさらします。現在、ソフトウェア開発の分野では多くの新しいテクノロジーと新しいコンセプトが登場していますが、if ... elseの基本的なプログラム形式はそれほど変わっていません。good if ... elseを使用することは、現在だけでなく将来にとっても非常に意味があります。今日は、コード内で...ほかの方法で "kill"する方法を確認し、コードを返して更新します。
質問1:もし多すぎたら...
問題のパフォーマンス
もし...それ以外の場合、コードが多すぎると次のコードのように抽象化できます。論理ブランチは5つしかリストされていませんが、実際の作業では、メソッドに10、20、またはそれ以上の論理ブランチが含まれていることがわかります。また、if ... elseが多すぎると、通常、他の2つの問題が発生します。複雑な論理式とif ... elseのネストが深すぎる場合です。後者の2つの質問については、この記事を次の2つのセクションで紹介します。このセクションでは、最初に、多すぎる場合の状況について説明します。
if (condition1) {
} else if (condition2) {
} else if (condition3) {
} else if (condition4) {
} else {
}
通常、if ... elseメソッドが多すぎて、通常、読みやすさとスケーラビリティは良くありません。ソフトウェア設計の観点から見ると、多すぎます...他のコードでは、多くの場合、このコードは単一責任の原則と開閉の原則に違反しています。実際のプロジェクトでは、需要は絶えず変化しており、新しい需要も生まれています。したがって、ソフトウェアシステムのスケーラビリティは非常に重要です。多くの場合、問題を解決する最大の意味は、コードのスケーラビリティを改善することです。
修正する方法
次に、多すぎる場合の問題を解決する方法について説明します。以下にいくつかの解決策を挙げました。
-
テーブル駆動
-
責任の連鎖
-
アノテーションドリブン
-
イベント駆動型
-
有限状態機械
-
オプション
-
主張する
-
ポリモーフィズム
方法1:テーブルドライブ
はじめに
if ... else固定論理式モードのelseコードの場合、特定のマッピング関係を介してテーブルで論理式を表現できます。次に、テーブル検索メソッドを使用して、入力に対応する処理関数を見つけ、この処理を使用します。操作のための機能。
該当するシーン
論理式モードが固定されている場合...
実装と例
if (param.equals(value1)) {
doAction1(someParams);
} else if (param.equals(value2)) {
doAction2(someParams);
} else if (param.equals(value3)) {
doAction3(someParams);
}
// ...
として再構成可能
Map<?, Function<?> action> actionMappings = new HashMap<>(); // 这里泛型 ? 是为方便演示,实际可替换为你需要的类型
// When init
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});
// 省略 null 判断
actionMappings.get(param).apply(someParams);
上記の例ではJava 8 Lambdaと機能インターフェースを使用していますが、ここでは説明しません。
テーブル間のマッピング関係は一元化または分散化できます。つまり、各処理クラスはそれ自体を登録します。構成ファイルの形式で表現することもできます。要するに、多くの形態があります。
まだ問題はありますが、条件式は上の例ほど単純ではありませんが、少し変形すればテーブルドライブも適用できます。以下は、「プログラミングパール」から税計算例を借用したものです。
if income <= 2200
tax = 0
else if income <= 2700
tax = 0.14 * (income - 2200)
else if income <= 3200
tax = 70 + 0.15 * (income - 2700)
else if income <= 3700
tax = 145 + 0.16 * (income - 3200)
......
else
tax = 53090 + 0.7 * (income - 102200)
上記のコードの場合、実際には、税計算式を抽出し、各ファイルの標準をテーブルに抽出して、サイクルを追加します。特定のリファクタリング後のコードは与えられていない、誰もが自分で考えています。
方法2:責任の連鎖モデル
はじめに
if ... elseの条件式が柔軟で表に抽象化して統一的に判断できない場合、各機能部品に条件判断を引き継ぐ必要があります。チェーンの形で、これらのコンポーネントは直列に接続されて完全な機能を形成します。
該当するシーン
条件式は柔軟であり、統一された形式はありません。
実装と例
責任連鎖のモードは、オープンソースフレームワークのフィルターおよびインターセプター機能の実現に見ることができます。一般的な使用モードを見てみましょう:
リファクタリング前:
public void handle(request) {
if (handlerA.canHandle(request)) {
handlerA.handleRequest(request);
} else if (handlerB.canHandle(request)) {
handlerB.handleRequest(request);
} else if (handlerC.canHandle(request)) {
handlerC.handleRequest(request);
}
}
リファクタリング後:
public void handle(request) {
handlerA.handleRequest(request);
}
public abstract class Handler {
protected Handler next;
public abstract void handleRequest(Request request);
public void setNext(Handler next) { this.next = next; }
}
public class HandlerA extends Handler {
public void handleRequest(Request request) {
if (canHandle(request)) doHandle(request);
else if (next != null) next.handleRequest(request);
}
}
もちろん、例のリファクタリング前のコードは、明確に表現するために、クラスとメソッドの抽出とリファクタリングを行いました。実際には、よりタイル化されたコード実装です。
注:責任の連鎖の制御モード
責任の連鎖モデルは、特定の実装プロセスでいくつかの異なる形式になります。チェーンコール制御の観点から、外部制御と内部制御に分けることができます。
外部制御は柔軟ではありませんが、実装の難しさを軽減します。責任チェーンの1つのリングでの特定の実装では、外部の統合制御のため、次のリングへの呼び出しを考慮する必要はありません。しかし、一般的な外部コントロールはネストされた呼び出しを実現できません。ネストされた呼び出しがあり、責任チェーンの呼び出しを外部から制御したい場合は、実装が少し複雑になります。詳細はSpring Web Interceptorメカニズムの実装方法を参照してください。
内部制御はより柔軟であり、特定の実装はチェーン内の次のリングを呼び出すかどうかを決定できます。しかし、呼制御モードが固定されている場合、そのような実装はユーザーにとって不便です。
特定の用途にはさまざまなデザインパターンのバリエーションがあり、誰もが柔軟である必要があります
方法3:アノテーションドリブン
はじめに
Javaアノテーション(または他の言語の同様のメカニズム)は、メソッドを実行するための条件を定義します。プログラムが実行されると、参加しているコメントで定義された条件が一致するかどうかを比較して、このメソッドを呼び出すかどうかが決定されます。特定の実装では、テーブル駆動型または一連の責任の形で実装できます。
該当するシーン
これは、多くの条件分岐と、プログラムのスケーラビリティと使いやすさに対する高い要件があるシナリオに適しています。これは通常、システムの新しい要件に頻繁に遭遇するコア機能です。
実装と例
このパターンの使用は、一般的なSpring MVCなどの多くのフレームワークで確認できます。これらのフレームワークは非常に一般的に使用されており、どこでもデモを見ることができるため、ここには特定のデモコードはありません。
このモデルの焦点は実装です。既存のフレームワークは、MVCなどの特定の分野で機能を実装するために使用されます。したがって、ビジネスシステムがこのモードを採用する場合、関連するコア機能をそれ自体で実装する必要があります。これには主に、反射や責任の連鎖などのテクノロジーが含まれます。ここでは特定の実装については説明しません。
方法4:イベントドリブン
はじめに
異なるイベントタイプと対応する処理メカニズムを関連付けることにより、デカップリングの目標を達成しながら、複雑なロジックを実現します。
該当するシーン
理論的な観点から見ると、イベント駆動型は一種のテーブル駆動型と見なすことができますが、実際には、上記のイベント駆動型とテーブル駆動型は多くの点で異なります。具体的には:
-
テーブル駆動は通常、1対1の関係であり、イベント駆動は通常、1対多です。
-
テーブル駆動では、トリガーと実行は通常強く依存し、イベント駆動ではトリガーと実行は弱く依存します。
上記の2つのシナリオの違いにつながるのは、上記2つの違いです。具体的には、イベントドリブンを使用して、在庫、ロジスティクス、ポイント、および注文の支払い完了などの他の機能をトリガーできます。
実装と例
実装に関しては、スタンドアロンの練習ドライブは、GuavaやSpringなどのフレームワークを使用して実装できます。分散は通常、さまざまなメッセージキューを通じて実現されます。しかし、ここでの主な議論はif ... elseを排除することであるため、主に単一マシンの問題ドメインに向けられています。特定のテクノロジーが関係しているため、このモードコードは示されていません。
方法5:有限状態機械
はじめに
有限状態機械は、しばしば状態機械と呼ばれます(無限状態機械の概念は無視できます)。最初にウィキペディアで定義を引用します。
有限状態機械(英語:有限状態機械、略称:FSM)は、状態機械と呼ばれ、有限数の状態と、これらの状態間の遷移およびアクションの動作を表す数学モデルです。
実際、ステートマシンは一種のテーブル駆動型と見なすこともできます。これは、実際には現在の状態とイベントの組み合わせと処理機能との間の対応です。もちろん、プロセスが成功した後、状態遷移プロセスがあります。
該当するシーン
現在、インターネットバックエンドサービスはステートレス性を強調していますが、これはステートマシン設計を使用できないことを意味するものではありません。実際、プロトコルスタック、注文処理、その他の機能など、多くのシナリオで、ステートマシンには本来の利点があります。これらの状況は自然に状態と状態循環に存在するためです。
実装と例
ステートマシン設計を実装するには、対応するフレームワークが必要です。このフレームワークには、少なくとも1つのステートマシン定義関数と対応するコールルーティング関数を実装する必要があります。状態マシンの定義には、DSLまたは注釈を使用できます。原則は複雑ではなく、アノテーションやリフレクションなどの機能を習得した学生は実装が簡単でなければなりません。
参照テクノロジー:
-
Apache Mina State Machine
Apache Minaフレームワークは、IOフレームワークの分野ではNettyほど優れていませんが、ステートマシン機能を提供します。https://mina.apache.org/mina-project/userguide/ch14-state-machine/ch14-state-machine.html。独自のステートマシン関数を持っている学生は、ソースコードを参照できます。 -
Spring State Machine
Springのサブプロジェクトは多数ありますが、その中には山や水を表示しないステートマシンフレームワークがあります。SpringState Machine https://projects.spring.io/spring-statemachine/です。DSLとアノテーションの2つの方法で定義できます。
上記のフレームワークは参照としてのみ機能し、特定のプロジェクトが関与する場合、ステートマシンのコア機能をビジネス特性に基づいて実装する必要があります。
方法6:オプション
はじめに
Javaコードのif ... elseの一部は、null以外のチェックが原因です。したがって、この部分によってもたらされるif ... elseを減らすと、if ... elseの総数も減らすことができます。
Javaは、空のオブジェクトを表すために、8からOptionalクラスを導入しました。このクラスは、関連する操作のための多くのメソッドを提供し、if ... elseを削除するために使用できます。オープンソースフレームワークであるGuavaおよびScala言語も同様の機能を提供します。
シーンを使用
空でない判断については、if ... elseがあります。
実装と例
伝統的な文章:
String str = "Hello World!";
if (str != null) {
System.out.println(str);
} else {
System.out.println("Null");
}
オプションを使用した後:
1 Optional<String> strOptional = Optional.of("Hello World!");
2 strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));
Optionalには多くのメソッドがありますが、ここでは1つずつ紹介しません。ただし、get()およびisPresent()メソッドは使用しないでください。それ以外は、従来のif ... elseと同じです。
拡張:Kotlin Null Safety
KotlinにはNull Safetyという機能があります。
bob?.department?.head?.name
連鎖呼び出しの場合、Kotlin言語では、?を渡すことができます。nullポインター例外を回避するため。リングがnullの場合、チェーン式全体の値はnullです。
方法7:アサートモード
はじめに
最後の方法は、空でない検査シナリオによって引き起こされた場合の解決に適しています。同様のシナリオでは、文字列が空でないなど、さまざまなパラメーター検証が行われます。SpringやApache Commonsなどの多くのフレームワークライブラリには、この共通機能を実装するためのツールが用意されています。ですから...自分で書く必要はありません。
-
Apache Commons Langのクラスを検証します:https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/Validate.html
-
SpringのAssertクラス:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/Assert.html
シーンを使用
通常、さまざまなパラメーターの検証に使用されます
拡張:Bean Validation
前の方法と同様に、Assertパターンを導入し、同様の効果を持つテクノロジーを導入します。Bean検証。Bean Validationは、Java EE仕様の1つです。Bean Validationはアノテーションを使用してJava Beansの検証標準を定義し、フレームワークを通じてそれらを均一に検証します。それはまた、もし...他の場合に削減に役割を果たすことができます。
方法8:ポリモーフィズム
はじめに
オブジェクト指向のポリモーフィズムを使用すると、if ... elseを排除することもできます。コードリファクタリングの本では、これも紹介されています:
https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html
シーンを使用
リンクに示されている例は比較的単純であり、多態的除去に適した特定のシナリオを反映できません。一般に、クラス内の複数のメソッドに類似したif ... else判定が例にあり、条件が同じである場合、多態性を使用してif ... elseを排除することを検討できます。
同時に、ポリモーフィズムを使用しても、if ... elseが完全に排除されるわけではありません。代わりに、if ... elseマージがオブジェクト作成ステージに転送されました。if ..の作成段階では、前述の方法を使用できます。
まとめ
上記のセクションでは、if ... elseが多すぎることによって引き起こされる問題と、対応する解決策を紹介しています。このセクションで説明する方法に加えて、他の方法があります。たとえば、「リファクタリングとパターン」という本では、「条件付きロジックを戦略に置き換える」、「状態変更条件付きステートメントを状態に置き換える」、「条件付きスケジューラをコマンドに置き換える」という3つの方法が紹介されています。その中の「コマンドモード」は、この記事の「テーブル駆動」アプローチとほぼ同じです。他の2つの方法は、本「リファクタリングとパターン」で詳細に説明されているため、ここでは繰り返さない。
どの方法をいつ使用するかは、直面している問題のタイプによって異なります。上記の適用可能なシナリオのいくつかは単なる提案であり、開発者自身が検討する必要のあるシナリオがいくつかあります。
質問2:...他に深くネストしている場合
問題のパフォーマンス
もし...他の場合は通常、最も深刻な問題ではありません。一部のコードif ... elseは数が多いだけでなく、if ... elseの間にネストされているため、コードが読みにくくなり、当然維持が困難になります。
if (condition1) {
action1();
if (condition2) {
action2();
if (condition3) {
action3();
if (condition4) {
action4();
}
}
}
}
... elseのネストが深すぎると、コードの可読性に深刻な影響を及ぼします。もちろん、前のセクションで述べた2つの問題があります。
修正する方法
前のセクションで紹介した方法は、このセクションの問題を解決するためにも使用できるため、上記の方法の場合、このセクションは繰り返されません。このセクションでは、if ... elseの数を減らしませんが、コードの読みやすさを向上させるいくつかのメソッドに焦点を当てています。
-
抽出方法
-
ウェイステートメント
方法1:抽出方法
はじめに
抽出方法は、コード再構成の手段です。定義は簡単に理解できます。つまり、コードの一部を抽出して、それを個別に定義された別のメソッドに配置します。借りる
https://refactoring.com/catalog/extractMethod.htmlの定義を使用します。
該当するシーン
if ... elseは非常にネストされたコードであり、多くの場合読みにくくなっています。したがって、大規模なリファクタリングの前に、コードを読みやすくするために少し調整する必要があります。抽出方法は、最も一般的に使用される調整方法です。
実装と例
リファクタリング前:
public void add(Object element) {
if (!readOnly) {
int newSize = size + 1;
if (newSize > elements.length) {
Object[] newElements = new Object[elements.length + 10];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements
}
elements[size++] = element;
}
}
リファクタリング後:
public void add(Object element) {
if (readOnly) {
return;
}
if (overCapacity()) {
grow();
}
addElement(element);
}
方法2:Weiステートメント
はじめに
コードのリファクタリングには、「ネストされた条件ステートメントの代わりにガードステートメントを使用する」https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.htmlというメソッドがあります。コードを直接見てください:
double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else {
if (_isRetired) result = retiredAmount();
else result = normalPayAmount();
};
}
return result;
}
リファクタリング後
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
}
シーンを使用
特定のコードブロックがif ... elseによって完全に制御されているメソッドを見つけた場合、通常はガードステートメントを使用できます。
質問3:if ... else式が複雑すぎる
問題のパフォーマンス
if ... elseによって引き起こされる3番目の問題は、過度に複雑な条件式が原因です。簡単な例ですが、条件1、2、3、4が真と偽の場合は、以下の式の結果を並べて組み合わせてください。
1 if ((condition1 && condition2 ) || ((condition2 || condition3) && condition4)) {
2
3 }
上記は誰もやりたくないと思います。重要なのは、この大きなTuo表現の意味は何ですか?ポイントは、式の意味がわからないとき、だれもその結果を推測したくないということです。
したがって、表現は複雑であり、必ずしも間違っているわけではありません。しかし、表現は理解しにくいです。
修正する方法
if ... else式の複雑な表現の場合、主にコード再構成の抽出と移動によって解決されます。これらのメソッドは「コードリファクタリング」という本で紹介されているため、ここでは繰り返さないことにします。
まとめ
この記事では、if ... elseを削除および簡略化するための10の方法(12の拡張を含む)を紹介します。戦略モード、状態モードなどを介して除去するなど、いくつかの方法もあります。それ以外は、本「リファクタリングとモード」でも紹介されています。
序文で述べたように、if ... elseはコードの重要な部分ですが、if ... elseの過度で不必要な使用は、コードの可読性とスケーラビリティに悪影響を及ぼし、コード全体に影響しますソフトウェアシステム。
他の場合に「殺す」機能は、ソフトウェアのリファクタリング、設計パターン、オブジェクト指向設計、アーキテクチャパターン、データ構造、およびその他のテクノロジーを使用するプログラマの包括的な機能を反映し、プログラマの内部作業を反映します。もし...もし他に合理的に使用したいのなら、あなたはデザインやオーバーデザインなしではそれを行うことができません。これらのテクノロジーを包括的かつ合理的に使用するには、プログラマーが常に自分の作業を調査して要約する必要があります。