web-dev-qa-db-ja.com

三項演算子を最適化する

私は他の誰かによって書かれたこのコードに出くわしました。この条件演算子の使用は推奨されていますか、それとも一般的に使用されていますか?メンテナンスが難しいと感じていますが、それとも私だけですか?これを書く別の方法はありますか?

  exp_rsp_status =  req.security_violation ? (dis_prot_viol_rsp && is_mstr) ? 
                    uvc_pkg::MRSP_OKAY : uvc_pkg::MRSP_PROTVIOL : req.slv_req.size()  ?
                    ((is_mst_abort_rsp && dis_mst_abort_rsp) ||
                    ((req.slv_req[0].get_rsp_status()==uvc_pkg::MRSP_PROTVIOL) && dis_prot_viol_rsp) ||
                    (is_mst_abort_rsp && req.is_pci_config_req() && dis_pcicfg_mst_abort_rsp)) ?
                    uvc_pkg::MRSP_OKAY : req.slv_req[0].get_rsp_status() : uvc_pkg::MRSP_OKAY;
39
Jean

それはただひどいコードです。

  • 書式が正しくありません。式の階層がわかりません。
  • 書式設定が適切であっても、式は複雑すぎて人間の目ですばやく解析できません。
  • 意図は不明です。それらの条件の目的は何ですか?

それで、あなたは何ができますか?

  • 条件文(if)を使用します。
  • 部分式を抽出し、変数に格納します。チェック this リファクタリングカタログからの良い例。
  • ヘルパー関数を使用します。ロジックが複雑な場合は、初期のreturnsを使用してください。深いインデントが好きな人はいません。
  • 最も重要なことは、すべてに意味のある名前を付けることです。何かを計算する必要がある理由を明確にする必要があります。

そして明確にするために:三項演算子には何も問題はありません。慎重に使用すると、消化しやすいコードが生成されることがよくあります。ただし、ネストは避けてください。コードが非常に明確な場合は、時々2番目のレベルを使用しますが、それでも括弧を使用するので、貧弱な脳は演算子の優先順位を解読するために余分なサイクルを実行する必要がありません。

コードの読者に注意してください。

66
Karoly Horvath

おそらくこれはデバイスドライバーのメッセージループにあり、おそらく10年前の元のコーダーは、コードのジャンプを望んでいませんでした。彼のコンパイラがジャンプ付きの三項演算子を実装していないことを彼が確認したことを願っています!

コードを調べると、私の最初の意見は、三項演算子のシーケンスは、すべてのコードと同様に、適切にフォーマットされていると読みやすくなるということです。

そうは言っても、OPの例を正しく解析したかどうかはわかりません。従来のネストされたif-else構造でさえ、検証するのは難しいでしょう。この式は、基本的なプログラミングパラダイムである分割統治法に違反しています。

req.security_violation
?   dis_prot_viol_rsp && is_mstr
    ?   uvc_pkg::MRSP_OKAY
    :   uvc_pkg::MRSP_PROTVIOL
:   req.slv_req.size()
    ?       is_mst_abort_rsp && dis_mst_abort_rsp
        ||      req.slv_req[0].get_rsp_status()==uvc_pkg::MRSP_PROTVIOL
            &&  dis_prot_viol_rsp
        ||  is_mst_abort_rsp && req.is_pci_config_req() && dis_pcicfg_mst_abort_rsp
        ?   uvc_pkg::MRSP_OKAY
        : req.slv_req[0].get_rsp_status()
    : uvc_pkg::MRSP_OKAY;

リファクタリングしたときにコードがどのように見えるかを確認したかったのです。確かに短くはありませんが、話す関数名が意図を明確にする方法が好きです(もちろんここで推測しました)。変数名はおそらくグローバルではないため、関数にパラメーターが必要になるため、これはある程度疑似コードであり、コードが再び不明確になります。ただし、パラメータは、ステータスやリクエスト構造などへの単一のポインタである可能性があります(dis_prot_viol_rspなどの値が抽出されています)。異なる条件を組み合わせるときに三項を使用するかどうかは議論の余地があります。私はそれがしばしばエレガントだと思います。

bool ismStrProtoViol()
{
    return dis_prot_viol_rsp && is_mstr;
}

bool isIgnorableAbort()
{
    return is_mst_abort_rsp && dis_mst_abort_rsp;
}

bool isIgnorablePciAbort()
{
    return is_mst_abort_rsp && req.is_pci_config_req() && dis_pcicfg_mst_abort_rsp;
}

bool isIgnorableProtoViol()
{
    return req.slv_req[0].get_rsp_status()==uvc_pkg::MRSP_PROTVIOL &&  dis_prot_viol_rsp;
}


eStatus getRspStatus()
{
    eStatus ret;

    if( req.security_violation )
    {
        ret = ismStrProtoViol() ?  uvc_pkg::MRSP_OKAY : uvc_pkg::MRSP_PROTVIOL;
    }
    else if(  req.slv_req.size() )
    {
        ret =       isIgnorableAbort()
                ||  isIgnorableProtoViol()
                ||  isIgnorablePciAbort()
            ? uvc_pkg::MRSP_OKAY
            : req.slv_req[0].get_rsp_status();
    else
    {
        ret = uvc_pkg::MRSP_OKAY;
    }

    return ret;
}

最後に、uvc_pkg::MRSP_OKAYが一種のデフォルトであり、特定の状況でのみ上書きされるという事実を利用できます。これにより、ブランチが削除されます。コードの理由を少し彫った後、どのように見えるかを見てください。セキュリティ違反でない場合は、実際のリクエストステータスから、空のリクエストと無視できるアボートを除いたものを確認してください。

eStatus getRspStatus()
{
    eStatus ret = uvc_pkg::MRSP_OKAY;

    if( req.security_violation )
    {
        ret = ismStrProtoViol() ? uvc_pkg::MRSP_OKAY : uvc_pkg::MRSP_PROTVIOL;
    }
    else if(        req.slv_req.size()
                &&  !isIgnorableAbort()
                &&  !isIgnorablePorotoViol()
                &&  !isIgnorablePciAbort()
            )
    {
        ret = req.slv_req[0].get_rsp_status();
    }

    return ret;
}

なんて醜い混乱。私はそれをifに分解し、それ以外の場合はそれが何をしているかを確認するだけです。あまり読みにくいですが、とにかく投稿したいと思いました。うまくいけば、他の誰かがあなたのためにもっとエレガントな解決策を持っています。しかし、あなたの質問に答えるために、それほど複雑な三元を使用しないでください。それが何をしているのかを理解するために私がしたことを誰もやりたくない。

if ( req.security_violation )
{
    if ( dis_prot_viol_rsp && is_mstr )
    {
        exp_rsp_status = uvc_pkg::MRSP_OKAY;
    }
    else
    {
        exp_rsp_status = uvc_pkg::MRSP_PROTVIOL;
    }
}
else if ( req.slv_req.size() )
{
    if ( ( is_mst_abort_rsp && dis_mst_abort_rsp ||
         ( req.slv_req[0].get_rsp_status() == uvc_pkg::MRSP_PROTVIOL && dis_prot_viol_rsp ) ||
         ( is_mst_abort_rsp && req.is_pci_config_req() && dis_pcicfg_mst_abort_rsp ) )
    {
        exp_rsp_status = uvc_pkg::MRSP_OKAY;
    }
    else
    {
        exp_rsp_status = req.slv_req[0].get_rsp_status();
    }

}
else
{
    exp_rsp_status = uvc_pkg::MRSP_OKAY
} 
14
Chase Henslee

これはひどいコードです。

変数を単一の式で初期化することが望ましい場合がよくありますが(たとえば、constにすることができます)、これはこのようなコードを書く言い訳にはなりません。複雑なロジックを関数に移動し、それを呼び出して変数を初期化できます。

void
example(const int a, const int b)
{
  const auto mything = make_my_thing(a, b);
}

C++ 11以降では、ラムダを使用して変数を初期化することもできます。

void
example(const int a, const int b)
{
  const auto mything = [a, b](){
      if (a == b)
        return MyThing {"equal"};
      else if (a < b)
        return MyThing {"less"};
      else if (a > b)
        return MyThing {"greater"};
      else
        throw MyException {"How is this even possible?"};
  }();
}
10
5gon12eder

他の人は、そのコードの抜粋がどれほどひどいのか、素晴らしい説明ですでに言っています。そのコードが悪い理由をもう少し説明します。

  1. 1つの「if-else」を1つの機能だけを実装すると考えると、そのコードがどれほど複雑かは明らかです。あなたの場合、私はifの数を数えることさえできません。

  2. あなたのコードが 単一責任の原則 を破って壊れていることは明らかです。

    ...クラスまたはモジュールには、変更する理由が1つだけある必要があります。

  3. ユニットテストは悪夢であり、これは別の危険信号です。そして、あなたの同僚はそのコードの単体テストを書き込もうとさえしなかったに違いありません。

4
BЈовић

一般的または推奨?番号。

私は似たようなことをしましたが、理由がありました:

  1. それはサードパーティのC関数への議論でした。
  2. 当時、私は現代のC++に精通していませんでした。
  3. 私以外の誰かがそれを読むことを知っていたので、私はそれからf ***をコメントしてフォーマットしました...または、それが何年も後に何をしているのかを知る必要がありました。
  4. リリースされることのなかったのはDEBUGCODEでした。

    textprintf_ex(gw->GetBackBuffer(), font, 0, 16, WHITE, -1, "BUTTON: %s",
                           //If...                        Then Display...
                      (ButtonClicked(Buttons[STOP])    ?  "STOP"
                    : (ButtonClicked(Buttons[AUTO])    ?  "AUTO" 
                    : (ButtonClicked(Buttons[TICK])    ?  "TICK"
                    : (ButtonClicked(Buttons[BLOCK])   ?  "BLOCK"
                    : (ButtonClicked(Buttons[BOAT])    ?  "BOAT"
                    : (ButtonClicked(Buttons[BLINKER]) ?  "BLINKER"
                    : (ButtonClicked(Buttons[GLIDER])  ?  "GLIDER"
                    : (ButtonClicked(Buttons[SHIP])    ?  "SHIP"
                    : (ButtonClicked(Buttons[GUN])     ?  "GUN"
                    : (ButtonClicked(Buttons[PULSAR])  ?  "PULSAR"
                    : (ButtonClicked(Buttons[RESET])   ?  "RESET"
                    :  /*Nothing was clicked*/            "NONE"
                    )))))))))))
                 );
    

If-elseチェーンを使用しなかった唯一の理由は、Wordを画面に印刷するだけだったため、コードが膨大になり、追跡が困難になったためです。

0
Casey