web-dev-qa-db-ja.com

大きなブール式は、述語メソッドに分解された同じ式よりも読みやすいですか?

理解しやすいものは何ですか、大きなブール文(かなり複雑)、または同じ文が述語メソッド(たくさんの追加コードを読む)に分割されていますか?

オプション1、大きなブール式:

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {

        return propVal.PropertyId == context.Definition.Id
            && !repo.ParentId.HasValue || repo.ParentId == propVal.ParentId
            && ((propVal.SecondaryFilter.HasValue && context.SecondaryFilter.HasValue && propVal.SecondaryFilter.Value == context.SecondaryFilter) || (!context.SecondaryFilter.HasValue && !propVal.SecondaryFilter.HasValue));
    }

オプション2、条件を述語メソッドに分解:

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {
        return MatchesDefinitionId(context, propVal)
            && MatchesParentId(propVal)
            && (MatchedSecondaryFilter(context, propVal) || HasNoSecondaryFilter(context, propVal));
    }

    private static bool HasNoSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);
    }

    private static bool MatchedSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    }

    private bool MatchesParentId(TValToMatch propVal)
    {
        return (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    }

    private static bool MatchesDefinitionId(CurrentSearchContext context, TValToMatch propVal)
    {
        return propVal.PropertyId == context.Definition.Id;
    }

私はメソッド名をコメントとして見るので2番目のアプローチを好みますが、コードが何をするかを理解するためにすべてのメソッドを読む必要があるので問題があることを理解し、コードの意図を抽象化します。

63
willem

理解しやすいこと

後者のアプローチ。理解が容易になるだけでなく、記述、テスト、リファクタリング、拡張も容易になります。必要な各条件を安全に分離し、独自の方法で処理できます。

コードを理解するにはすべてのメソッドを読む必要があるため、問題があります。

メソッドに適切な名前を付けても問題ありません。実際、メソッド名は条件の意図を表すため、理解しやすくなります。
onlookerの場合、if MatchesDefinitionId()if (propVal.PropertyId == context.Definition.Id)よりも説明的です

[個人的に、最初のアプローチは私の目を痛める。]

88
wonderbell

これがこれらの述語関数が使用される唯一の場所である場合は、代わりにローカルbool変数を使用することもできます。

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    bool matchesDefinitionId = (propVal.PropertyId == context.Definition.Id);
    bool matchesParentId = (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    bool matchesSecondaryFilter = (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    bool hasNoSecondaryFilter = (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);

    return matchesDefinitionId
        && matchesParentId
        && matchesSecondaryFilter || hasNoSecondaryFilter;
}

これらは、さらに分解して、読みやすくするために並べ替えることもできます。と

bool hasSecondaryFilter = propVal.SecondaryFilter.HasValue;

次に、propVal.SecondaryFilter.HasValueのすべてのインスタンスを置き換えます。すぐに突き出る1つのことは、hasNoSecondaryFilterは否定されたHasValueプロパティに対して論理ANDを使用し、matchesSecondaryFilterは否定されていないHasValueに対して論理ANDを使用することです。 -それは正反対ではありません。

44
Simon Richter

一般に、後者が好ましい。

これにより、呼び出しサイトがより再利用可能になります。これは、DRYをサポートします(基準が変更されたときに変更する場所が少なく、より確実に変更できることを意味します)。そして、多くの場合、これらのサブ基準は、他の場所で独立して再利用されるものであり、あなたはそれをする。

ああ、それはこれを単体テストするのをはるかに簡単にし、あなたがあなたがそれを正しく行ったという自信を与えます。

41
Telastyn

これらの2つの選択肢の間にある場合は、後者の方が適しています。ただし、これらだけではありません。単一の関数を複数のifに分割するのはどうですか?関数を終了して追加のテストを回避する方法をテストします。1行のテストで「短絡」を大まかにエミュレートします。

これは読みやすいです(例のロジックを再確認する必要があるかもしれませんが、コンセプトは当てはまります):

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    if( propVal.PropertyId != context.Definition.Id ) return false;

    if( repo.ParentId.HasValue || repo.ParentId != propVal.ParentId ) return false;

    if( propVal.SecondaryFilter.HasValue && 
        context.SecondaryFilter.HasValue && 
        propVal.SecondaryFilter.Value == context.SecondaryFilter ) return true;

    if( !context.SecondaryFilter.HasValue && 
        !propVal.SecondaryFilter.HasValue) return true;

    return false;   
}
23
BuvinJ

私はオプション2の方が好きですが、1つの構造変更を提案します。条件の最後の行の2つのチェックを1つの呼び出しに結合します。

_private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    return MatchesDefinitionId(context, propVal)
        && MatchesParentId(propVal)
        && MatchesSecondaryFilterIfPresent(context, propVal);
}

private static bool MatchesSecondaryFilterIfPresent(CurrentSearchContext context, 
                                                    TValToMatch propVal)
{
    return MatchedSecondaryFilter(context, propVal) 
               || HasNoSecondaryFilter(context, propVal);
}
_

私がこれを提案する理由は、2つのチェックが単一の機能単位であり、条件付きの括弧をネストするとエラーが発生しやすくなるためです。どちらも、最初にコードを書くという観点と、コードを読む人の観点の両方からです。これは、式のサブ要素が同じパターンに従っていない場合に特に当てはまります。

MatchesSecondaryFilterIfPresent()が組み合わせに最適な名前かどうかはわかりません。しかし、すぐに頭に浮かぶのはもっと良いことです。

C#では、コードはあまりオブジェクト指向ではありません。静的メソッドを使用しており、静的フィールドのように見えます(repoなど)。静的はコードがリファクタリングを困難にし、テストを困難にし、再利用性を妨げると一般に考えられています。そして、あなたの質問に:このような静的な使用法は、オブジェクト指向の構築よりも可読性と保守性が低くなります。

このコードをよりオブジェクト指向の形式に変換する必要があります。そうすると、オブジェクトやフィールドの比較などを行うコードを配置するための賢明な場所があることに気づくでしょう。それから、オブジェクトにそれら自身を比較するように依頼することができるでしょう。比較する簡単なリクエスト(例:if ( a.compareTo (b) ) { }、すべてのフィールド比較を含めることができます。)

C#には、オブジェクトとそのフィールドを比較するための豊富なインターフェイスとシステムユーティリティのセットがあります。明白な.Equalsメソッドのほかに、まず、IEqualityComparerIEquatable、およびSystem.Collections.Generic.EqualityComparer.Defaultなどのユーティリティを調べます。

2
Erik Eidt

最初のものは絶対に恐ろしいです。 ||を使用しています同じ行にある2つのもの。これは、コードのバグか、コードを難読化する意図のいずれかです。

    return (   (   propVal.PropertyId == context.Definition.Id
                && !repo.ParentId.HasValue)
            || (   repo.ParentId == propVal.ParentId
                && (   (   propVal.SecondaryFilter.HasValue
                        && context.SecondaryFilter.HasValue 
                        && propVal.SecondaryFilter.Value == context.SecondaryFilter)
                    || (   !context.SecondaryFilter.HasValue
                        && !propVal.SecondaryFilter.HasValue))));

これは、少なくとも中途半端に適切にフォーマットされており(フォーマットが複雑な場合は、if条件が複雑であるためです)、そこに意味がないかどうかを理解する機会が少なくともあります。もしフォーマットされたごみと比較すると、何か他のものはより良いです。しかし、あなたは極端なことしかできないようです:ifステートメントの完全な混乱、または4つの完全に無意味なメソッド。

(cond1 && cond2)||に注意してください。 (!cond1 && cond3)は次のように書くことができます

cond1 ? cond2 : cond3

混乱を減らすでしょう。私は書きます

if (propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue) {
    return true;
} else if (repo.ParentId != propVal.ParentId) {
    return false;
} else if (propVal.SecondaryFilter.HasValue) {
    return (   context.SecondaryFilter.HasValue
            && propVal.SecondaryFilter.Value == context.SecondaryFilter); 
} else {
    return !context.SecondaryFilter.HasValue;
}
0
gnasher729

後者が間違いなく優先され、私は最初の方法のケースを見てきましたが、ほとんど常に読むことは不可能です。私はそれを最初の方法で行うことを間違え、述語メソッドに変更するように求められました。

0
Snoop

読みやすさのために空白とコメントを追加して読者がわかりにくい部分を補う場合、2つはほぼ同じだと思います。

覚えておいてください:良い解説は読者にコードを書いたときに何を考えていたかと伝えます。

私が提案したような変更があれば、前者のアプローチを使用することになるでしょう。サブルーチン呼び出しは脚注のようなものです。それらは有用な情報を提供しますが、読み取りの流れを妨害します。述語がより複雑な場合は、それらを別々のメソッドに分割して、それらが具現化する概念を理解可能なチャンクで構築できるようにします。

0
Mark Wood

まあ、再利用する可能性のあるパーツがある場合は、それらを適切な名前の個別の関数に分離することは、明らかに最良のアイデアです。
それらを再利用しない場合でも、そうすることで、条件をより適切に構成し、それらに何を説明するラベルを付けることができるかもしれませんmean

ここで、最初のオプションを見てみましょう。インデントや改行はそれほど役に立ちませんでしたし、条件付きもそれほどうまく構造化されていません。

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal) {
    return propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue
        || repo.ParentId == propVal.ParentId
        && propVal.SecondaryFilter.HasValue == context.SecondaryFilter.HasValue
        && (!propVal.SecondaryFilter.HasValue || propVal.SecondaryFilter.Value == context.SecondaryFilter.Value);
}
0
Deduplicator