web-dev-qa-db-ja.com

このifステートメントのセットを単純化するにはどうすればよいですか? (または、何がそんなに気まずいのですか?)

同僚がこのコードを見せてくれたので、重複したコードを削除できなかったのはなぜかと考えました。

private List<Foo> parseResponse(Response<ByteString> response) {
    if (response.status().code() != Status.OK.code() || !response.payload().isPresent()) {
      if (response.status().code() != Status.NOT_FOUND.code() || !response.payload().isPresent()) {
        LOG.error("Cannot fetch recently played, got status code {}", response.status());
      }
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    doSomeLogic();
    // ...
    // ...
    // ...
    return someOtherList;
}

これは、もう少し冗長にするための代替表現です。

private void f() {
    if (S != 200 || !P) {
        if (S != 404 || !P) {
            Log();
        }
        return;
    }
    // ...
    // ...
    // ...
    doSomeLogic();
    // ...
    // ...
    // ...
    return;
}

!Pを複製せずに、これを書く簡単な方法はありますか?そうでない場合、!Pの因数分解を不可能にする状況または条件に関する固有のプロパティがありますか?

31
Andrew Cheong

多くのコードのように見える理由の1つは、繰り返しが多いことです。変数を使用して、繰り返される部分を保存します。これにより、読みやすくなります。

_private List<Foo> parseResponse(Response<ByteString> response) {
    Status status = response.status();
    int code = status.code();
    boolean payloadAbsent = !response.payload().isPresent();

    if (code != Status.OK.code() || payloadAbsent) {
      if (code != Status.NOT_FOUND.code() || payloadAbsent) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
      }
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
_

編集:以下のコメントでスチュワートが指摘しているように、response.status()と_Status.OK_を直接比較できる場合は、.code()および_import static_を使用して、ステータスに直接アクセスします。

_import static Namespace.Namespace.Status;

// ...

private List<Foo> parseResponse(Response<ByteString> response) {
    Status status = response.status();
    boolean payloadAbsent = !response.payload().isPresent();

    if (status != OK || payloadAbsent) {
      if (status!= NOT_FOUND || payloadAbsent) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
      }
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
_

payloadAbsentロジックの複製をどうするかという質問に関して、Zacharyは私が提案したものと互換性のあるいくつかの良いアイデアを提供しました。もう1つのオプションは、基本構造を維持することですが、ペイロードをチェックする理由をより明確にします。これにより、ロジックを追跡しやすくなり、内部ifで_||_を使用する必要がなくなります。大藤、私はこのアプローチにあまり熱心ではありません。

_import static Namespace.Namespace.Status;

// ...

private List<Foo> parseResponse(Response<ByteString> response) {
    Status status = response.status();
    boolean failedRequest = status != OK;
    boolean loggableError = failedRequest && status!= NOT_FOUND ||
        !response.payload().isPresent();

    if (loggableError) {
      LOG.error("Cannot fetch recently played, got status code {}", status);
    }
    if (failedRequest || loggableError) {
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
_
29
JLRishe

条件チェックを明確にし、同じ論理的結果を維持したい場合は、次が適切な場合があります。

_if (!P) {
    Log();
    return A;
}
if (S != 200) {
    if (S != 404) {
        Log();
    }
    return A;
}
return B;
_

または(これはOP優先でした)

_if (S == 404 && P) {
    return A;
}
if (S != 200 || !P) {
    Log();
    return A;
}
return B;
_

または(スイッチを気にしないなら、私は個人的にこれを好む)

_if (P) {
    switch (S) {
        case 200: return B;
        case 404: return A;
    }
}
Log ();
return A;
_

あなたはcould中括弧を削除し、単一行の本体をif-statementと同じ行に移動することにより、if-statementを圧縮します。ただし、単一行のifステートメントは混乱を招き、完全に悪い習慣になる可能性があります。私がコメントから集めたものから、あなたの好みはこの使用に反対するでしょう。単一行のifステートメントはロジックを凝縮してよりクリーンなコードの外観を与えることができますが、明快さとコードの意図は「経済的な」コードを重視する必要があります。明確にするために、私は個人的に単一行のif文が適切な状況があると感じていますが、元の条件は非常に長いので、この場合はこれに強く反対します。

_if (S != 200 || !P) {
    if (S != 404 || !P) Log();
    return A;
}
return B;
_

サイドノードとして:Log();ステートメントがネストされたifステートメントの唯一の到達可能なブランチである場合、次の論理IDを使用してロジックを凝縮できます( Distributive )。

_(S != 200 || !P) && (S! = 404 || !P) <=> (S != 200 && S != 404) || !P_

[〜#〜] edit [〜#〜]コンテンツを再配置し、コメントに記載されている問題を解決するための重要な編集。

21
Zachary

簡潔さが常に最良の解決策とは限らず、ほとんどの場合、ローカルの名前付き変数はコンパイラーによって最適化されることを覚えておいてください。

私はおそらく使用します:

    boolean goodPayload = response.status().code() == Status.OK.code() && response.payload().isPresent();
    if (!goodPayload) {
        // Log it if payload not found error or no payload at all.
        boolean logIt = response.status().code() == Status.NOT_FOUND.code()
                || !response.payload().isPresent();
        if (logIt) {
            LOG.error("Cannot fetch recently played, got status code {}", response.status());
        }
        // Use empty.
        return Lists.newArrayList();
    }
13
OldCurmudgeon

コードの匂いは、telling itではなく、要求 Responseオブジェクトであるということです。 ParseメソッドがResponseオブジェクトのメソッド(または、より可能性の高い、そのスーパークラス)ではなく、Responseオブジェクトの外部にある理由を自問してください。 Log()メソッドは、おそらくParseメソッドの代わりにResponseオブジェクトコンストラクターで呼び出すことができますか?プロパティstatus().code()およびpayload().isPresent()がコンストラクターで計算される瞬間に、default parsed objectを単純な(そして単一の)if ... else ... Parse()に残りますか?

実装の継承を使用してオブジェクト指向言語で記述できることに恵まれている場合は、すべての条件ステートメント(および式!)を照会して、コンストラクターまたはメソッドのいずれに持ち上げられる候補かを確認する必要があります。 )コンストラクターを呼び出します。すべてのクラス設計で従うことができる単純化は、本当に計り知れません。

13
Pieter Geerkens

実際に冗長な主なものは!P(!payloadが存在する)です。ブール式として記述した場合、次のようになります。

(A || !P) && (B || !P)

あなたが観察したように、!Pは繰り返されるようであり、それは不必要です。ブール代数では、ANDを乗算のようなもの、およびOR加算のようなものを扱うことができます。したがって、単純な代数のようにこれらの括弧を展開できます。

A && B || A && !P || !P && B || !P && !P

!Pを持つすべてのANDされた式をグループ化できます。

A && B || (A && !P || !P && B || !P && !P)

これらの用語にはすべて!Pが含まれているため、乗算のように取り出すことができます。行うときは、それをtrueに置き換えます(1と同じように、1回はすべてがそれ自体であり、trueおよび何かはそれ自体であるため):

A && B || !P && (A && true || B && true || true && true)

「true && true」はORされた式の1つであるため、グループ化全体が常にtrueであり、次のように単純化できることに注意してください。

A && B || !P && true
-> A && B || !P

ここでの適切な表記法と用語については、さびている。しかし、それが要点です。

Ifステートメントに複雑な式がある場合は、コードに戻ります。他の人が指摘しているように、一度使用して破棄する場合でも、それらを意味のある変数に固定する必要があります。

これらをまとめると、次のようになります。

boolean statusIsGood = response.status().code() != Status.OK.code() 
  && response.status().code() != Status.NOT_FOUND.code();

if (!statusIsGood || !response.payload().isPresent()) {
  log();
}

この変数は、否定しても「statusIsGood」という名前になっていることに注意してください。名前が否定された変数は本当に紛らわしいからです。

本当に複雑なロジックに対して上記の種類の単純化を行うことができることに留意してくださいしかし、常にそうするべきではありません。技術的には正しい表現になりますが、それを見てもだれもわかりません。

この場合、単純化によって意図が明確になると思います。

10
Vectorjohn

私見の問題は、主に繰り返しとネストする場合です。他の人は、私もお勧めする明確な変数とユーティリティ関数を使用することを提案していますが、検証の懸念を分離することもできます。

私が間違っている場合は修正してくださいが、実際に応答を処理する前にコードが検証しようとしているようですので、ここに検証を記述する別の方法があります:

private List<Foo> parseResponse(Response<ByteString> response)
{
    if (!response.payload.isPresent()) {
        LOG.error("Response payload not present");
        return Lists.newArrayList();
    }
    Status status = response.status();
    if (status != Status.OK || status != Status.NOT_FOUND) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
        return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
5
emont01

Ifステートメントを逆にして、次のように明確にすることができます。

private void f() {
    if (S == 200 && P) {
        return;
    }

    if (S != 404 || !P) {
        Log();
    }

    return;
}

次に、「responseIsValid()」や「responseIsInvalid()」などの意味のあるメソッド名を使用して、条件をリファクタリングできます。

4
Kerri Brown

ヘルパー関数は、ネストされた条件を簡素化できます。

private List<Foo> parseResponse(Response<ByteString> response) {
    if (!isGoodResponse(response)) {
        return handleBadResponse(response);
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
3
jxh

最も厄介な部分は、ロジックが単一の一貫した値を必要とするように見えるときに、response.status()のようなゲッターが何度も呼び出されることです。ゲッターは常に同じ値を返すことが保証されているため、おそらく動作しますが、コードの意図を誤って表現し、現在の仮定に対してより脆弱になります。

これを修正するには、コードがresponse.status()を1回取得する必要があります。

var responseStatus = response.status();

、その後はresponseStatusを使用します。これは、取得のたびに同じ値であると想定される他のゲッター値に対して繰り返す必要があります。

さらに、このコードが後でより動的なコンテキストでスレッドセーフな実装にリファクタリングされる可能性がある場合、これらの取得はすべて同じシーケンシャルポイントで行われることが理想的です。要点は、特定の時点でresponseの値を取得するということなので、コードのクリティカルセクションは1つの同期プロセスでそれらの値を取得する必要があります。

一般に、目的のデータフローを正しく指定すると、コードの復元力と保守性が向上します。誰かがゲッターに副作用を追加したり、responseを抽象データ型にする必要がある場合、意図したとおりに動作し続ける可能性がはるかに高くなります。

3
Nat

以下は同等だと思います。しかし、他の人が指摘しているように、コードの透明性は「単純な」コードよりも重要です。

if (not ({200,404}.contains(S) && P)){
    log();
    return;
}
if (S !=200){
    return;
}
// other stuff
2
Acccumulation

免責事項:提示された機能の署名や機能については質問しません。

私は、関数が委任するのではなく、それ自体で多くの作業を行っているため、気まずい感じがします。

この場合、検証部分を引き上げることをお勧めします。

// Returns empty if valid, or a List if invalid.
private Optional<List<Foo>> validateResponse(Response<ByteString> response) {
    var status = response.status();
    if (status.code() != Status.NOT_FOUND.code() || !response.payload().isPresent()) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
        return Optional.of(Lists.newArrayList());
    }

    if (status.code() != Status.OK.code()) {
        return Optional.of(Lists.newArrayList());
    }

    return Optional.empty();
}

ネスト条件よりもreturnステートメントを繰り返すほうが好ましいことに注意してください。これにより、コードflatが維持され、循環的な複雑さが軽減されます。さらに、すべてのエラーコードに対してsameの結果を常に返すことを保証するものではありません。

その後、parseResponseが簡単になります。

private List<Foo> parseResponse(Response<ByteString> response) {
    var error = validateResponse(response);
    if (error.isPresent()) {
        return error.get();
    }

    // ...
    // ...
    // ...
    return someOtherList;
}

代わりに、機能的なスタイルを使用できます。

/// Returns an instance of ... if valid.
private Optional<...> isValid(Response<ByteString> response) {
    var status = response.status();
    if (status.code() != Status.NOT_FOUND.code() || !response.payload().isPresent()) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
        return Optional.empty();
    }

    if (status.code() != Status.OK.code()) {
        return Optional.empty();
    }

    return Optional.of(...);
}

private List<Foo> parseResponse(Response<ByteString> response) {
    return isValid(response)
        .map((...) -> {
            // ...
            // ...
            // ...
            return someOtherList;
        })
        .orElse(Lists.newArrayList());
}

個人的に私は余分なネストが少し面倒だと思います。

2
Matthieu M.

JLRisheの答えのような変数を使用するだけです。しかし、単純なブールチェックを複製しないよりも、コードの明快さがはるかに重要であると主張します。アーリーリターンステートメントを使用して、これをより明確にすることができます。

private List<Foo> parseResponse(Response<ByteString> response) {

    if (response.status().code() == Status.NOT_FOUND.code() && !response.payload().isPresent()) // valid state, return empty list
        return Lists.newArrayList();

    if (response.status().code() != Status.OK.code()) // status code says something went wrong
    {
        LOG.error("Cannot fetch recently played, got status code {}", response.status());
        return Lists.newArrayList();
    }

    if (!response.payload().isPresent()) // we have an OK status code, but no payload! should this even be possible?
    {
        LOG.error("Cannot fetch recently played, got status code {}, but payload is not present!", response.status());
        return Lists.newArrayList();
    }

    // ... got ok and payload! do stuff!

    return someOtherList;
}
1
user673679

整数を明示的な値の有限セットと比較することによる分岐は、switchで処理するのが最適です。

if (P) {
    switch (S) {
        case 200: return B;
        case 404: return A;
    }
}

Log();
return A;
1
Alexander

ログに記録するコードのセットと、ペイロードが存在しない一般的な状態を設定できます

Set<Code> codes = {200, 404};
if(!codes.contains(S) && !P){
    log();
}
return new ArrayList<>();

状態の修正。

1
Java Samurai

この一連の条件では、重複を回避する方法はないと思います。ただし、必要に応じて条件をできるだけ合理的に分離し、他の領域を複製することを好みます。

私がこれを書いていて、現在のスタイルを守っていたら、次のようなものになるでしょう。

    private void f() {
        if(!P) {
            Log();          // duplicating Log() & return but keeping conditions separate
            return;
        } else if (S != 200) {
            if (S != 404) {
                Log();
            }
            return;
        }

        // ...
        // ...
        // ...
        return;
    }

コードの単純さには主観的な要素があり、読みやすさは非常に主観的です。それを考えると、このメソッドをゼロから作成する場合、これが私のバイアスを与えたものです。

private static final String ERR_TAG = "Cannot fetch recently played, got status code {}";

private List<Foo> parseResponse(Response<ByteString> response) {
    List<Foo> list = Lists.newArrayList();

    // similar to @JLRishe keep local variables rather than fetch a duplicate value multiple times
    Status status = response.status();
    int statusCode = status.code();
    boolean hasPayload = response.payload().isPresent();

    if(!hasPayload) {
        // If we have a major error that stomps on the rest of the party no matter
        //      anything else, take care of it 1st.
        LOG.error(ERR_TAG, status);
    } else if (statusCode == Status.OK.code()){
        // Now, let's celebrate our successes early.
        // Especially in this case where success is narrowly defined (1 status code)
        // ...
        // ...
        // ...
        list = someOtherList;
    } else {
        // Now we're just left with the normal, everyday failures.
        // Log them if we can
        if(statusCode != Status.NOT_FOUND.code()) {
            LOG.error(ERR_TAG, status);
        }
    }
    return list;        // One of my biases is trying to keep 1 return statement
                        // It was fairly easy here.
                        // I won't jump through too many hoops to do it though.
}

コメントを削除しても、コード行はほぼ2倍になります。一部の人は、これではコードを単純化できないと主張します。私にとってはそうです。

1
Gary99

繰り返されるPテストを除外することができます。次の(擬似コード)は、質問のコードと論理的に同等です。

private List<Foo> f() {
    List<Foo> list(); /*whatever construction*/
    if (P) {
        if (S==200) {
            // ...
            // ...
            // ...
            list.update(/*whatever*/);
        }
        else if (S!=404) {
           Log();
        }
    }
    else {
       Log();
    }
    return list;
}

読みやすさの観点から、次のようにします(これも擬似コードです)。

private bool write_log() {
    return (S!=200 && S!=404) || !P
}
private bool is_good_response() {
    return S==200 && P
}
private List<Foo> f() {
    List<Foo> list(); /*whatever construction*/
    if (write_log()) {
       Log();
    }
    if (is_good_response()) {
        // ...
        // ...
        // ...
        list.update(/*whatever*/);
    }
    return list;
}

おそらくより適切な名前の関数を使用します。

1
Peter Bingham

コードが何をしようとしているのかわかりません:正直なところ、404ステータスのみを記録し、コードが200ではないときに空のリストを返すことは、NPEを避けようとしているように感じます...

私はそれが次のように良い方法だと思います:

private boolean isResponseValid(Response<ByteString> response){
    if(response == null){
        LOG.error("Invalid reponse!");
        return false;
    }

    if(response.status().code() != Status.OK.code()){
        LOG.error("Invalid status: {}", response.status());
        return false;
    }

    if(!response.payload().isPresent()){
        LOG.error("No payload found for response!");
        return false;
    }
    return true;
}

private List<Foo> parseResponse(Response<ByteString> response) throws InvalidResponseException{
    if(!isResponseValid(response)){
        throw InvalidResponseException("Response is not OK!");
    }

    // logic
}

何らかの理由でifロジックを変更できない場合、とにかく検証を別の関数に移動します。

また、Java命名規則を使用してみてください。

LOG.error("")    // should be log.error("")
Status.OK.code() // why couldn't it be a constant like Status.OK, or Status.getOkCode()?
0
Matteo A