巨大な if 条件をどのように処理しますか?
-
08-06-2019 - |
質問
これは、私が使用したすべての言語で私を悩ませたものです。if ステートメントがありますが、条件部分に非常に多くのチェックがあるため、複数の行に分割するか、ネストされた if ステートメントを使用するか、醜いことを受け入れて先に進む必要があります。私の人生とともに。
私や同じ問題に直面している他の人にとって役立つ可能性のある他の方法はありますか?
例: すべてを 1 行に記述:
if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true)
{
複数行の例:
if (var1 = true && var2 = true && var2 = true
&& var3 = true && var4 = true && var5 = true
&& var6 = true)
{
ネストされた例:
if (var1 = true && var2 = true && var2 = true && var3 = true)
{
if (var4 = true && var5 = true && var6 = true)
{
解決
条件を複数のブール値に分割し、マスター ブール値を条件として使用します。
bool isOpaque = object.Alpha == 1.0f;
bool isDrawable = object.CanDraw && object.Layer == currentLayer;
bool isHidden = hideList.Find(object);
bool isVisible = isOpaque && isDrawable && ! isHidden;
if(isVisible)
{
// ...
}
さらに良いのは:
public bool IsVisible {
get
{
bool isOpaque = object.Alpha == 1.0f;
bool isDrawable = object.CanDraw && object.Layer == currentLayer;
bool isHidden = hideList.Find(object);
return isOpaque && isDrawable && ! isHidden;
}
}
void Draw()
{
if(IsVisible)
{
// ...
}
}
変数には、機能ではなく意図を実際に示す名前を付けるようにしてください。これは、開発者がコードを保守するのに非常に役立ちます...それはあなたかもしれません!
他のヒント
まだ誰もこれを手に入れていないことに驚いています。このタイプの問題に特化したリファクタリングがあります。
http://www.refactoring.com/catalog/decomposeConditional.html
ここで対処すべき問題が 2 つあります。読みやすさとわかりやすさ
「読みやすさ」の解決策はスタイルの問題であるため、解釈の余地があります。私の好みはこれです:
if (var1 == true && // Explanation of the check
var2 == true && // Explanation of the check
var3 == true && // Explanation of the check
var4 == true && // Explanation of the check
var5 == true && // Explanation of the check
var6 == true) // Explanation of the check
{ }
またはこれ:
if (var1 && // Explanation of the check
var2 && // Explanation of the check
var3 && // Explanation of the check
var4 && // Explanation of the check
var5 && // Explanation of the check
var6) // Explanation of the check
{ }
とはいえ、この種の複雑なチェックは、コードをスキャンしながら頭の中で解析するのが非常に難しい場合があります (特にオリジナルの作成者ではない場合)。複雑さの一部を抽象化するヘルパー メソッドを作成することを検討してください。
/// <Summary>
/// Tests whether all the conditions are appropriately met
/// </Summary>
private bool AreAllConditionsMet (
bool var1,
bool var2,
bool var3,
bool var4,
bool var5,
bool var6)
{
return (
var1 && // Explanation of the check
var2 && // Explanation of the check
var3 && // Explanation of the check
var4 && // Explanation of the check
var5 && // Explanation of the check
var6); // Explanation of the check
}
private void SomeMethod()
{
// Do some stuff (including declare the required variables)
if (AreAllConditionsMet (var1, var2, var3, var4, var5, var6))
{
// Do something
}
}
「SomeMethod」メソッドを視覚的にスキャンすると、テスト ロジックの実際の複雑さは隠されますが、意味論的な意味は人間が高レベルで理解できるように保持されます。開発者が本当に詳細を理解する必要がある場合は、AreAllConditionsMet メソッドを調べることができます。
これは正式には「条件付き分解」リファクタリング パターンとして知られていると思います。Resharper や Refactor Pro などのツール!この種のリファクタリングを簡単に行うことができます。
いずれの場合も、コードを読みやすく理解できるようにするための鍵は、現実的な変数名を使用することです。これが不自然な例であることは理解していますが、「var1」、「var2」などは ない 受け入れ可能な変数名。それらが表すデータの根本的な性質を反映した名前を付ける必要があります。
多くの場合、これらをコンポーネントのブール変数に分割します。
bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...
まず、すべてを削除します == true
部分的には、50% 短くなります ;)
大きな症状が出たときは、その原因を探します。ポリモーフィズムを使用する必要がある場合もあれば、状態オブジェクトを追加する必要がある場合もあります。基本的に、これはリファクタリングが必要であることを意味します (コードの匂い)。
時々私は使います ド・モルガンの法則 ブール式を少し簡略化します。
チェックアウト 実装パターン ケント・ベック著。この状況で役立つかもしれないと私が考えている特定のパターンがあります...それは「警備員」と呼ばれます。大量の条件を用意するのではなく、それらをガードに分割することで、メソッド内でどのような不利な条件が明確になるかがわかります。
たとえば、何かを実行するメソッドがあるが、何かを実行すべきではない特定の条件がある場合、次のようになります。
public void doSomething() {
if (condition1 && condition2 && condition3 && condition4) {
// do something
}
}
これを次のように変更できます。
public void doSomething() {
if (!condition1) {
return;
}
if (!condition2) {
return;
}
if (!condition3) {
return;
}
if (!condition4) {
return;
}
// do something
}
これは少し冗長ですが、はるかに読みやすくなっており、特に奇妙なネストが発生し始めた場合には、ガードが役立ちます (抽出メソッドと組み合わせると)。
ちなみに、私はその本を強くお勧めします。
私は、if ステートメントの各条件を 1 つのタブでインデントしたり、開き括弧と一致させたりしている人や編集者をたくさん見てきました。
if (var1 == true
&& var2 == true
&& var3 == true
) {
/* do something.. */
}
私は通常、最後の条件と同じ行に閉じ括弧を置きます。
if (var1 == true
&& var2 == true
&& var3 == true) {
/* do something.. */
}
しかし、これはそれほどきれいではないと思います。
スティーブ・マコーネル氏のアドバイス コードの完成:多次元テーブルを使用します。各変数はテーブルのインデックスとして機能し、IFステートメントはテーブルルックアップに変わります。たとえば、(size == 3 && weight> 70)がテーブルエントリの決定[size] [weight_group]に変換される場合
ファンクターと述語を見てみましょう。Apache Commons プロジェクトには、条件付きロジックをオブジェクトにカプセル化できるようにする優れたオブジェクト セットが含まれています。その使用例は O'reilly で入手できます ここ. 。コード例の抜粋:
import org.apache.commons.collections.ClosureUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.NOPClosure;
Map predicateMap = new HashMap();
predicateMap.put( isHonorRoll, addToHonorRoll );
predicateMap.put( isProblem, flagForAttention );
predicateMap.put( null, ClosureUtils.nopClosure() );
Closure processStudents =
ClosureUtils.switchClosure( predicateMap );
CollectionUtils.forAllDo( allStudents, processStudents );
これらすべての isHonorRoll 述語と、それらを評価するために使用されるクロージャの詳細は次のとおりです。
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
// Anonymous Predicate that decides if a student
// has made the honor roll.
Predicate isHonorRoll = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return( ( s.getGrade().equals( "A" ) ) ||
( s.getGrade().equals( "B" ) &&
s.getAttendance() == PERFECT ) );
}
};
// Anonymous Predicate that decides if a student
// has a problem.
Predicate isProblem = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return ( ( s.getGrade().equals( "D" ) ||
s.getGrade().equals( "F" ) ) ||
s.getStatus() == SUSPENDED );
}
};
// Anonymous Closure that adds a student to the
// honor roll
Closure addToHonorRoll = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// Add an award to student record
s.addAward( "honor roll", 2005 );
Database.saveStudent( s );
}
};
// Anonymous Closure flags a student for attention
Closure flagForAttention = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// Flag student for special attention
s.addNote( "talk to student", 2005 );
s.addNote( "meeting with parents", 2005 );
Database.saveStudent( s );
}
};
まず最初に、次のことを考えてみましょう。
if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...
また、抽象的なコード例をリファクタリングするのは非常に困難です。具体的な例を示すと、問題に適したより適切なパターンを特定しやすくなります。
それは良くありませんが、私が過去にやったことは次のとおりです。(次のメソッドはブール値テストの短絡を防ぎ、最初のテストが false であってもすべてのテストが実行されます。戻る前に常にすべてのコードを実行する必要があることがわかっている場合を除き、推奨されるパターンではありません -- 私の間違いを見つけてくれた pTomato に感謝します!)
ブール値 ok = cond1;
オーケー &= cond2;
オーケー &= cond3;
オーケー &= cond4;
オーケー &= cond5;
オーケー &= cond6;
これは次と同じです: (同じではありません。上記の注意を参照してください!)
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
私はブール値を分離することに頼っています。
Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);
if ( cond1 && cond2 ) {}
他の人が述べたように、条件文を分析して、可読性を高めるために他のメソッドにアウトソーシングできる方法があるかどうかを確認します。
PHP のようなリフレクティブ言語では、変数-変数を使用できます。
$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
if ($$v == true) {
// do something
break;
}
私はレベルごとに分類したいので、例を次のようにフォーマットします。
if (var1 = true
&& var2 = true
&& var2 = true
&& var3 = true
&& var4 = true
&& var5 = true
&& var6 = true){
このように、より多くのネストがある場合に便利です (明らかに、すべてに対して "= true" よりも実際の条件の方が興味深いでしょう)。
if ((var1 = true && var2 = true)
&& ((var2 = true && var3 = true)
&& (var4 = true && var5 = true))
&& (var6 = true)){
Python でプログラミングしている場合は、組み込みの関数を使用すると簡単です。 all()
変数のリストに適用される関数 (ここではブール リテラルのみを使用します):
>>> L = [True, True, True, False, True]
>>> all(L) # True, only if all elements of L are True.
False
>>> any(L) # True, if any elements of L are True.
True
あなたの言語 (C#) に対応する関数はありますか?ジャワ?)。もしそうなら、それがおそらく最もクリーンなアプローチです。
マクダウェル
単一の「&」演算子を使用すると、式の両側が評価されるということは正しいです。ただし、(少なくとも C# では) '&&' 演算子を使用する場合、false を返す最初の式が最後に評価される式になります。これにより、評価を FOR ステートメントの前に置くことは、他の方法と同様に適切になります。
@tweakt
それは良くありませんが、私が過去にやったことは次のとおりです。
ブール値 ok = cond1;オーケー &= cond2;オーケー &= cond3;オーケー &= cond4;オーケー &= cond5;オーケー &= cond6;
これは次と同じです:
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
実際、これら 2 つはほとんどの言語で同じではありません。2 番目の式は通常、条件の 1 つが false になるとすぐに評価を停止します。これにより、条件の評価にコストがかかる場合、パフォーマンスが大幅に向上する可能性があります。
読みやすさを考えると、個人的には上記の Mike Stone の提案の方が好みです。冗長なコメントを付けるのは簡単で、早期に実行できることによる計算上の利点をすべて維持できます。条件付き評価を他の関数から遠くに移動してコードの構成が混乱する場合は、同じ手法を関数内でインラインで実行することもできます。少し安っぽいですが、いつでも次のようなことができます。
do {
if (!cond1)
break;
if (!cond2)
break;
if (!cond3)
break;
...
DoSomething();
} while (false);
while (false) はちょっと安っぽいです。言語に「once」または簡単に抜け出すことができるスコープ演算子があればいいのにと思います。
Perl で実行している場合は、次のようにしてチェックを実行します。
{
last unless $var1;
last unless $var2;
last unless $var3;
last unless $var4;
last unless $var5;
last unless $var6;
... # Place Code Here
}
これをサブルーチン上で使用する予定がある場合は、次のすべてのインスタンスを置き換えます。 last
と return
;
私は各条件を説明的な変数に分割するのが好きです。
bool isVar1Valid, isVar2Valid, isVar3Valid, isVar4Valid;
isVar1Valid = ( var1 == 1 )
isVar2Valid = ( var2.Count >= 2 )
isVar3Valid = ( var3 != null )
isVar4Valid = ( var4 != null && var4.IsEmpty() == false )
if ( isVar1Valid && isVar2Valid && isVar3Valid && isVar4Valid ) {
//do code
}
if ( (condition_A)
&& (condition_B)
&& (condition_C)
&& (condition_D)
&& (condition_E)
&& (condition_F)
)
{
...
}
とは対照的に
if (condition_A) {
if (condition_B) {
if (condition_C) {
if (condition_D) {
if (condition_E) {
if (condition_F) {
...
}
}
}
}
}
}
そして
if ( ( (condition_A)
&& (condition_B)
)
|| ( (condition_C)
&& (condition_D)
)
|| ( (condition_E)
&& (condition_F)
)
)
{
do_this_same_thing();
}
とは対照的に
if (condition_A && condition_B) {
do_this_same_thing();
}
if (condition_C && (condition_D) {
do_this_same_thing();
}
if (condition_E && condition_F) {
do_this_same_thing();
}
コードを検査するための静的分析ツールのほとんどは、複数の条件式が演算子の優先順位ルールや括弧の数の減少に依存するのではなく、式分析を指示する明示的な括弧を使用しない場合にエラーを発生します。
同じインデント レベルの開き/閉じ中括弧 {}、開き閉じ括弧 ()、左に括弧と演算子を含む条件式を垂直方向に配置することは、非常に便利な習慣であり、すべてを詰め込むのではなく、コードの読みやすさと明瞭さを大幅に向上させます。垂直方向の配置、スペース、括弧がなければ、単一行に詰め込まれる可能性があります
演算子の優先順位ルールは扱いが難しいです。&& ||よりも優先されますが、| &&よりも優先される
それで、 ...
if (expr_A & expr_B || expr_C | expr_D & expr_E || expr_E && expr_F & expr_G || expr_H {
}
これは、単なる人間が読み取って不適切に評価してしまう非常に簡単な複数の条件式です。
if ( ( (expr_A)
& (expr_B)
)
|| ( (expr_C)
| ( (expr_D)
& (expr_E)
)
)
|| ( (expr_E)
&& ( (expr_F)
& (expr_G)
)
)
|| (expr_H)
)
{
}
水平方向のスペース (改行)、垂直方向の配置、または式の評価をガイドする明示的な括弧には何も問題はなく、これらすべてが読みやすさと明瞭さを向上させます。
これを行う場合:
if (var1 == true) {
if (var2 == true) {
if (var3 == true) {
...
}
}
}
そうすれば、何かが真実ではない場合にも対応できます。たとえば、入力を検証している場合、入力を適切にフォーマットする方法などについてのヒントをユーザーに提供できます。