26

デバッグ目的で他の人のコードを調べていたところ、次のことがわかりました。

!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);  

これは何を意味するのでしょうか?これをよりわかりやすい if/else ステートメントに変換する自動ツールはありますか? このような複雑な制御構造を扱うためのヒントはありますか?

編集注: これは意見の問題なので、タイトルの「不必要に複雑」から「複雑」に変更しました。これまでのすべての回答に感謝します。

4

7 に答える 7

63

書かれているステートメントは、次のように書き直せば改善される可能性があります....

good = m_seedsfilter==0 ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);

...しかし、一般的には、三項ステートメントに慣れる必要があります。最初に投稿されたコード、xanatos のバージョン、または私のバージョンのいずれについても、本質的に悪いことは何もありません。三項ステートメントは悪いものではなく、言語の基本的な機能であり、一度慣れると、このようなコード (元の投稿に書かれたものではなく、私が投稿したもの) の方が実際には簡単であることに気付くでしょう。 if-else ステートメントのチェーンよりも読み取ります。たとえば、このコードでは、このステートメントを次のように単純に読み取ることができgoodます。m_seedsfilter==0truem_seedsfilter==1newClusters(Sp)newSeed(Sp)

上記の私のバージョンでは、変数 への 3 つの個別の代入が回避されgood、ステートメントの目的が に値を代入することであることが明確になっていることに注意してくださいgood。また、このように書くと、本質的にこれが「スイッチ ケース」構造であり、デフォルト ケースが であることが明確になりますnewSeed(Sp)

operator!()の型がm_seedsfilterオーバーライドされない限り、上記の私の書き直しは有効であることに注意してください。そうであれば、これを使用して元のバージョンの動作を維持する必要があります...

good = !m_seedsfilter   ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);

...そして、以下の xanatos のコメントが証明しているように、メソッドnewClusters()newSeed()メソッドが互いに異なる型を返し、それらの型が慎重に作成された無意味な変換演算子で記述されている場合は、元のコード自体に戻す必要があります (ただし、元の投稿とまったく同じ動作を忠実に再現するために、xanatos 自身の投稿のように、より適切にフォーマットされることを願っています。しかし、現実の世界では誰もそんなことをするつもりはないので、上記の最初のバージョンで問題ないはずです。


更新、元の投稿/回答から2年半後:@TimothyShieldsと私がこれについて時々賛成票を集め続けているのは興味深いことです.Timの答えは、多かれ少なかれ、この答えの賛成票の約50%で一貫して追跡しているようです. (この更新の時点で 43 対 22)。

3 項ステートメントを慎重に使用した場合に追加できる明快さの例をもう 1 つ追加したいと思います。以下の例は、コールスタック使用状況アナライザー (コンパイルされた C コードを分析するツールですが、ツール自体は C# で記述されています) 用に書いていたコードの短いスニペットです。3 つの亜種はすべて、少なくとも外部から見える効果に関する限り、まったく同じ目的を達成します。

1. 三項演算子なし:

Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
if (fcnInfo.callDepth == 0)
{
   Console.Write(" (leaf function");
}
else if (fcnInfo.callDepth == 1)
{
   Console.Write(" (calls 1 level deeper");
}
else
{
   Console.Write(" (calls " + fcnInfo.callDepth + " levels deeper");
}
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

2. 三項演算子を使用して、Console.Write() を個別に呼び出します。

Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
Console.Write((fcnInfo.callDepth == 0) ? (" (leaf function") :
              (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                                         (" (calls " + fcnInfo.callDepth + " levels deeper"));
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

3. 三項演算子を使用して、Console.Write() への単一の呼び出しに折りたたまれます。

Console.WriteLine(
   new string(' ', backtraceIndentLevel) + fcnName +
   ((fcnInfo.callDepth == 0) ? (" (leaf function") :
    (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                               (" (calls " + fcnInfo.callDepth + " levels deeper")) +
   ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

上記の 3 つの例の違いは些細なことだと主張する人もいるかもしれません。簡潔にすることがすべてです。アイデアを「できるだけ少ない言葉」で表現することで、私がアイデアの最後にたどり着くまでに、聞き手/読者がアイデアの始まりを思い出すことができます。小さな子供たちと話すときは、簡単で短い文章を使うので、アイデアを表現するのに多くの文章が必要になります。私の言語に堪能な大人と話すとき、私はアイデアをより簡潔に表現する、より長く、より複雑な文を使用します。

これらの例では、1 行のテキストを標準出力に出力します。それらが実行する操作は単純ですが、より大きなシーケンスのサブセットとしてそれらを想像するのは簡単です。そのシーケンスのサブセットをより簡潔に明確に表現できればできるほど、そのシーケンスのより多くをエディターの画面に収めることができます。もちろん、私は簡単にその努力をやりすぎてしまい、理解するのが難しくなります。目標は、理解可能であることと簡潔であることの間の「スイートスポット」を見つけることです。私は、プログラマーが三項ステートメントに慣れると、それらを使用するコードを理解することは、使用しないコードを理解することよりも容易になると主張します (例えば、上記の23と上記の1 )。

経験豊富なプログラマーが 3 項ステートメントを快適に使用できる最後の理由は、メソッド呼び出しを行うときに不要な一時変数を作成しないようにするためです。その例として、上記の例の 4 番目の変形を提示します。ロジックはConsole.WriteLine();への単一の呼び出しに凝縮されています。結果はわかりにくく、簡潔ではあり ません。

4. 三項演算子なしで、Console.Write() への単一の呼び出しに折りたたまれます。

string tempStr;
if (fcnInfo.callDepth == 0)
{
   tempStr = " (leaf function";
}
else if (fcnInfo.callDepth == 1)
{
   tempStr = " (calls 1 level deeper";
}
else
{
   tempStr = " (calls " + fcnInfo.callDepth + " levels deeper";
}
Console.WriteLine(new string(' ', backtraceIndentLevel) + fcnName + tempStr +
                  ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

「ロジックを単一の呼び出しに凝縮することConsole.WriteLine()は不要である」と主張する前に、これは単なる例であると考えてください。複数のパラメーターを取り、他の変数の状態に基づいた一時的なパラメーターを必要とする他のメソッドの呼び出しを想像してみてください。独自の一時変数を作成してそれらの一時変数でメソッド呼び出しを行うか、または三項演算子を使用してコンパイラに独自の (名前のない) 一時変数を作成させることができます。繰り返しますが、三項演算子を使用すると、使用しない場合よりもはるかに簡潔でわかりやすいコードが可能になると主張します。しかし、理解できるようにするためには、三項演算子が悪であるという先入観を捨てなければなりません。

于 2013-08-14T16:42:00.210 に答える
28

The equivalent non-evil code is this:

if (m_seedsfilter == 0)
{
    good = true;
}
else if (m_seedsfilter == 1)
{
    good = newClusters(Sp);
}
else
{
    good = newSeed(Sp);
}

Chained ternary operators - that is, the following

condition1 ? A : condition2 ? B : condition3 ? C : D

- are a great way to make your code unreadable.

I'll second @phonetagger's suggestion that you become familiar with ternary operators - so that you can eliminate nested ones when you encounter them.

于 2013-08-14T16:44:42.480 に答える
8

これの方が良い?

!m_seedsfilter ? good=true 
               : m_seedsfilter==1 ? good=newClusters(Sp) 
                                  : good=newSeed(Sp);  

付け加えておきますが、この式を単純化することは理論的には可能ですが (なぜですか? とても明確です!)、結果として得られる式は、考えられるすべてのケースでおそらく 100% 同等ではないでしょう...そして、2 つの式がC ++で本当に同等なのは、非常に非常に非常に複雑な問題です...

私が考案した退化した例 ( http://ideone.com/uLpe0L ) (あまり退化していないことに注意してください...小さなプログラミング エラーに基づいているだけです) は、 を考慮goodしてbool、2 つのクラスUnixDateTimeを作成SmallUnixDateTimeし、newClusters()a を返し、 aSmallUnixDateTimenewSeed()返しますUnixDateTime。どちらも、1970 年 1 月 1 日午前 0 時からの秒数の形式で Unix 日時を含めるために使用する必要があります。SmallUnixDateTimeを使用しint、 をUnixDateTime使用しlong longます。どちらも暗黙的に変換可能boolです (それらの内部値が!= 0、何か「古典的な」ものである場合に返されます) が、UnixDateTime暗黙的に変換可能ですSmallUnixDateTime(精度が失われる可能性があるため、これは間違っています...これは小さなプログラミング エラーです)。変換に失敗すると、SmallUnixDateTimesetted to0が返されます。この例のコードでは、常に単一の変換があります: between SmallUnixDateTimetoboolまたは between UnixDateTimeto bool...

この似ているが異なる例では、次のようになります。

good = !m_seedsfilter ? true 
                      : m_seedsfilter==1 ? newClusters(Sp) 
                                         : newSeed(Sp);

可能なパスは 2 つあります。SmallUnixDateTime( newClusters(Sp)) が に変換されるboolか、UnixDateTime( newSeed(Sp)) が最初に に変換されSmallUnixDateTime、次に に変換されboolます。明らかに、2 つの式は同等ではありません。

動作させる (または「動作させない」) には、 ( )newSeed(Sp)に含めることができない値を返します。SmallUnixTimestd::numeric_limits<int>::max() + 1LL

于 2013-08-14T16:41:18.980 に答える
4

主な質問に答えるために、これは条件式の例です:

条件式:
    論理和式 論理
    和式 ?   : 条件式

論理 OR 式が に評価される場合true、式の結果は の後の式になり?、それ以外の場合は の後の式になり:ます。例えば、

x = y > 0 ? 1 : 0;

が0 より大きい x場合は1 を割り当て、それ以外の場合は '0' を割り当てます。y

この例は下手に書かれているので、あなたがその例にうんざりするのは当然です。?:作成者は、オペレーターを制御構造として 使用しようとしていますが、これは意図されていません。

これを書くより良い方法は


    good = !m_seedsfilter ? true : 
                            ( m_seedsfilter == 1 ? newClusters(SP) : 
                                                   newSeed(SP) );

m_seedsfilterが 0 の場合、goodに設定されtrueます。m_seedsfilterが 1 の場合good、 の結果が設定されnewClusters(SP)ます。それ以外の場合はgood、 の結果が設定されnewSeed(SP)ます。

于 2013-08-14T17:06:54.080 に答える
3

ネストされた三項ステートメントは、コードを読みにくくします。残りのコードを大幅に簡素化する場合にのみ使用します。引用されたコードは、次のように書き直すことができます。

good = !m_seedsfilter ? true : m_seedsfilter==1 ? newClusters(Sp) : newSeed(Sp); 

またはこのように:

if(!m_seedsfilter)
    good = true;
else if(m_seedsfilter==1)
    good = newClusters(Sp);
else
    good = newSeed(Sp);

最初の選択肢はより簡潔ですが、初心者にとって読みにくく、デバッグしにくいものです。

于 2013-08-14T16:57:06.100 に答える
1
if ( !m_seedsfilter )
  good = true;
else if ( m_seedsfilter == 1 )
  good = newClusters(Sp);
else
  good = newSeed(Sp);

後に続く式は、句に似たものを導入するwhile に?ほぼ対応します。これはステートメントではなく式であることに注意してください。if ( expression ):else

<condition> ? <expression-1> : <expression-2>

は値がtrue の場合、それ以外のexpression-1場合は である式です。conditionexpression-2

于 2013-08-14T16:46:56.450 に答える
1
!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);

に翻訳されます

if (!m_seedsfilter)
{
     good = true;
}
else
{
     if (m_seedsfilter == 1)
     {
          good = newClusters(Sp);
     }
     else
     {
          good = new Seed(Sp);
     }
}
于 2013-08-15T13:12:45.633 に答える