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


63

理解しやすいもの、大きなブール文(非常に複雑なもの)、または述語メソッドに分解された同じ文(多くの余分なコードを読む)

オプション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番目のアプローチを好みますが、コードが何をするのかを理解するためにすべてのメソッドを読む必要があるため、問題があることを理解し、コードの意図を抽象化します。


13
オプション2は、Martin Fowlerがリファクタリング本で推奨しているものに似ています。さらに、メソッド名はすべてのランダム式の目的として機能し、メソッドの内容は実装の詳細に過ぎず、時間の経過とともに変化する可能性があります。
プログラマー

2
本当に同じ表現ですか?「または」は「および」よりも優先順位が低く、とにかく2番目は意図を示し、もう1つ(最初)は技術的です。
thepacker

3
@thepackerの言うこと。最初の方法で間違いを犯したという事実は、ターゲットオーディエンスの非常に重要なセクターにとって最初の方法が簡単に理解できないというかなり良い手がかりです。あなた自身!
スティーブジェソップ

3
オプション3:どちらも好きではありません。2番目はとんでもなく冗長であり、1番目は2番目と同等ではありません。括弧が役立ちます。
デビッドハンメン

3
これは見苦しいかもしれませんが、どちらのコードブロックに ifステートメントはありません。あなたの質問はブール式に関するものです。
カイルストランド

回答:


88

理解しやすいもの

後者のアプローチ。理解しやすいだけでなく、記述、テスト、リファクタリング、拡張も簡単です。必要な各条件は、独自の方法で安全に分離および処理できます。

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

メソッドの名前が適切であれば問題ありません。実際、メソッド名は条件の意図を説明するため、理解しやすいでしょう。
見物人にとってはif MatchesDefinitionId()if (propVal.PropertyId == context.Definition.Id)

[個人的には、最初のアプローチで目が痛む。]


12
メソッド名が適切であれば、理解しやすくなります。
BЈовић

そして、それらを(メソッド名)意味のある短いものにしてください。20以上のcharsメソッド名が目を痛めます。MatchesDefinitionId()ボーダーラインです。
マインドウィン

2
@Mindwinメソッド名を「短く」するか、意味のあるものにするかを選択することになった場合は、毎回後者を使用すると言います。Shortは良いですが、読みやすさを犠牲にしません。
Ajedi32

@ Ajedi32は、メソッドがメソッド名で何をするかについてのエッセイを書いたり、文法的に正しいメソッド名を持っている必要はありません。(ワークグループまたは組織全体で)略語の標準を明確に保てば、短い名前と読みやすさの問題にはなりません。
マインドウィン

Zipfの法則を使用する:物事をより冗長にして、使用をやめさせます。
hoosierEE

44

これがこれらの述語関数が使用される唯一の場所である場合、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論理AND を使用するHasValueことです。したがって、正反対ではありません。


3
このソリューションはかなり優れており、私は確かに多くの同様のコードを書いています。とても読みやすいです。欠点は、私が投稿したソリューションと比較して、速度です。この方法を使用すると、何であれ条件付きテストの山を実行します。私のソリューションでは、処理される値に基づいて操作を劇的に削減できます。
-BuvinJ

5
ここに示されているような@BuvinJテストはかなり安価であるはずです。そのため、条件の一部が高価であることを知らない限り、またはパフォーマンスに非常に敏感なコードでない限り、読みやすいバージョンを選びます。
-svick

1
@svickこれはほとんどの場合、パフォーマンスの問題を引き起こす可能性が低いことは間違いありません。それでも、読みやすさを損なうことなく操作を削減できるのであれば、なぜそうしないのですか?これは私のソリューションよりもはるかに読みやすいとは思いません。それはテストに自己文書化の「名前」を与えます-これは素晴らしいことです...それは特定のユースケースとテストがそれ自体でいかに理解しやすいかに帰着すると思います。
-BuvinJ

コメントを追加すると読みやすくなります
...-BuvinJ

@BuvinJこのソリューションで私が本当に気に入っているのは、最後の行以外をすべて無視することで、それが何をしているのかをすぐに理解できることです。これはもっと読みやすいと思います。
svick

42

一般に、後者が好まれます。

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

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


1
はい、あなたの答えはrepo、静的フィールド/プロパティ、つまりグローバル変数のように見えるの使用の修正にも対処する必要があります。静的メソッドは確定的であり、グローバル変数を使用しないでください。
デビッドアルノ

3
@DavidArno-それは素晴らしいことではありませんが、手元の質問に正接しているようです。そして、より多くのコードなしにそれはだもっともらしいデザインがそのように動作させるためにセミ正当な理由があること。
Telastyn

1
はい、決してレポを気にしません。私はコードを少し難読化しなければなりませんでした。クライアントコードをそのまま
インターウェブ

23

これらの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;   
}

3
これを投稿してから数秒以内にどうしてダウン票を得たのですか?投票するときにコメントを追加してください!この答えは同じくらい速く動作し、読みやすくなっています。だから問題は何ですか?
-BuvinJ

2
@BuvinJ:絶対に問題はありません。元のコードと同じですが、1ダースの括弧と画面の端を越えて伸びる1行で戦う必要はありません。そのコードを上から下まで読んで、すぐに理解できます。WTFカウント=0。
gnasher72916年

1
関数の最後以外で返すと、コードが読みにくくなり、読みにくくなります。私は単一の出口を好みます。このリンクでのいくつかの良い議論。stackoverflow.com/questions/36707/...
ブラッド・トーマス

5
@Brad thomas単一の出口点には同意できません。通常、深くネストされた括弧になります。戻りはパスを終了するので、私にとっては読みやすくなっています。
ボルジャブ

1
@BradThomas私はボルジャブに完全に同意します。深いネストを避けることが、実際に長い条件文を分割するよりもこのスタイルを頻繁に使用する理由です。私は、たくさんのネストを使ってコードを書いていることに気づきます。それから、ネストを1つまたは2つ以上深くする方法を模索し始めました。その結果、コードの読み取りと保守が非常に簡単になりました。関数を終了する方法を見つけられる場合は、できるだけ早く終了してください!深いネストと長い条件を回避する方法を見つけることができる場合は、そうしてください!
-BuvinJ

10

私はオプション2の方が好きですが、構造的な変更を提案します。条件の最後の行の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()組み合わせに最適な名前であるかどうかはわかりません。しかし、すぐに思い浮かぶものはありません。


メソッド内で何が行われているのかを説明しようとするのは、呼び出しを再構築するよりも実際に優れています。
クラール

2

C#では、コードはあまりオブジェクト指向ではありません。静的メソッドと静的フィールドのように見えるものを使用しています(例repo)。一般的に、静的はコードのリファクタリングを困難にし、テストを困難にしますが、再利用性を妨げます。そして、あなたの質問に答えてください。

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

C#には、オブジェクトとそのフィールドで比較を行うための豊富なインターフェイスとシステムユーティリティのセットがあります。明らかに超えて.Equalsの方法、スターターのために、に見てIEqualityComparerIEquatable、およびなどのユーティリティSystem.Collections.Generic.EqualityComparer.Default


0

後者の方が間違いなく好まれます。私は最初の方法で事例を見てきましたが、ほとんどの場合、読むことは不可能です。私はそれを最初の方法で行うのを間違えたので、それを述語メソッドに変更するように頼まれました。


0

読みやすさのために空白を追加し、より曖昧な部分を読者が理解しやすいようにコメントを追加する場合、この2つはほぼ同じだと思います。

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

私が提案したような変更では、混乱が少なく拡散しているため、おそらく前者のアプローチを採用するでしょう。サブルーチン呼び出しは脚注のようなものです。有用な情報を提供しますが、読書の流れを中断します。述語がより複雑な場合は、それらを具体化する概念を理解可能なチャンクで構築できるように、それらを別々のメソッドに分割します。


+1に値します。他の回答に基づいた一般的な意見ではないが、思考のための良い食べ物。ありがとう:)
ウィレム

1
@willemいいえ、それは+1に値しません。2つのアプローチは同じではありません。追加のコメントは愚かで不必要です。
BЈовић

2
良いコードは決して理解しやすいように、コメントに依存しません。実際、コメントはコードが持つ可能性のある最悪の混乱です。コードはそれ自体を語るべきです。また、OPが評価する2つのアプローチは、追加する空白の数に関係なく、「ほぼ同じ」になることはありません。
ワンダーベル

コメントを読むよりも、意味のある関数名を使用する方が適切です。「クリーンコード」の本で述べられているように、コメントはスローコードを表現できないことです。関数がそれをより明確に述べたはずなのに、何をしているのかを説明してください。
ボルジャブ

0

まあ、再利用したい部分がある場合、それらを別々の適切な名前の関数に分けることは明らかに最善のアイデアです。
再利用しなくても、条件をより適切に構成し、その意味を説明するラベルを付けることができます。

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

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

最初のものは絶対に恐ろしいです。||を使用しています 同じ行の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;
}

-4

私はこれらのソリューションのどちらも好きではありません、それらは推論するのが難しく、読むのが難しいです。小規模なメソッドのためだけに小規模なメソッドに抽象化しても、必ずしも問題が解決するとは限りません。

理想的には、プロパティをメタプログラムで比較すると思うので、新しいメソッドを定義したり、新しいプロパティのセットを比較するたびに分岐したりする必要はありません。

私はc#についてはわかりませんが、javascriptではこのようなものははるかに優れており、少なくともMatchesDefinitionIdとMatchesParentIdを置き換えることができます

function compareContextProp(obj, property, value){
  if(obj[property])
    return obj[property] == value
  return false
}

1
C#でこのようなものを実装することは問題になりません。
スヌープ

〜5回の呼び出しcompareContextProp(propVal, "PropertyId", context.Definition.Id)のブール値の組み合わせが、フォームの〜5回の比較のOPのブール値の組み合わせよりも読みやすいかどうかはわかりませんpropVal.PropertyId == context.Definition.Id。それはかなり長く、通話サイトから複雑さを実際に隠すことなく、余分なレイヤーを追加します。(それが重要な場合、私は投票しませんでした)
Ixrec
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.