これはvery簡略化した例です。これは必ずしも言語固有の質問ではありません。関数を記述できる他の多くの方法、および関数に加えることができる変更を無視してください。。色はユニークなタイプです
string CanLeaveWithoutUmbrella()
{
if(sky.Color.Equals(Color.Blue))
{
return "Yes you can";
}
else
{
return "No you can't";
}
}
私が会った多くの人々、ReSharper、そして this guy (私がしばらくこれを尋ねようとしていたことを思い出させたコメント)は、コードをリファクタリングしてelse
ブロックを削除し、これを残しておくことをお勧めします:
(私は大多数が言ったことを思い出すことができません、私は他にこれを尋ねなかったかもしれません)
string CanLeaveWithoutUmbrella()
{
if(sky.Color.Equals(Color.Blue))
{
return "Yes you can";
}
return "No you can't";
}
質問:else
ブロックを含めないことにより、複雑さが増していますか?
両方のブロックのコードが直接関連しているという事実を述べることにより、else
が意図をより直接的に述べるという印象を受けています。
さらに、特に後日コードを変更した後で、ロジックの微妙なミスを防ぐことができることもわかりました。
この単純化された例のバリエーションを見てみましょう(これは意図的に単純化された例であるため、or
演算子を無視しています)。
bool CanLeaveWithoutUmbrella()
{
if(sky.Color != Color.Blue)
{
return false;
}
return true;
}
最初の条件が自分の条件に制約を課していることをすぐに正しく認識せずに、最初の例の後の条件に基づいて、誰かが新しいif
ブロックを追加できるようになりました。
else
ブロックが存在する場合、新しい条件を追加したユーザーは、else
ブロックのコンテンツを強制的に移動する必要があります(そして、何らかの方法でブロックを発見すると、ヒューリスティックがコードに到達できないことを示しますが、if
が1つの場合は、別のものを制約します)。
もちろん、とにかく特定の例を定義する方法は他にもありますが、そのすべてがその状況を防止しますが、これは単なる例です。
私が与えた例の長さは、これの視覚的な側面を歪める可能性があるので、括弧までのスペースがメソッドの残りの部分に対して比較的重要ではないと仮定します。
Elseブロックの省略に同意するケースについて言及するのを忘れており、if
ブロックを使用してでなければならない制約を適用する場合ヌルチェック(または他のガード)など、all以降のコードに対して論理的に満足します。
私の見解では、明示的なelseブロックが望ましいです。これを見ると:
if (sky.Color != Blue) {
...
} else {
...
}
私は相互に排他的なオプションを扱っていることを知っています。 ifブロック内でwhatsを読む必要はありません。
これを見ると:
if (sky.Color != Blue) {
...
}
return false;
一見すると、それは関係なくfalseを返し、空が青ではないオプションの副作用があるように見えます。
私が見ているように、2番目のコードの構造は、コードが実際に行うことを反映していません。それは良くないね。代わりに、論理構造を反映する最初のオプションを選択してください。論理フローを知るために、各ブロックのリターン/ブレーク/コンティニューロジックをチェックする必要はありません。
なぜ誰かが他の人を好むのかは私にとって謎です。うまくいけば、誰かが反対の立場を取り、私に彼らの答えを教えてくれるでしょう。
Elseブロックの省略に同意するケースについて言及するのを忘れており、ifブロックを使用して、次のすべてのコードに対して論理的に満たす必要がある制約を適用している場合(null-checkなど) )。
あなたが幸福な道を去った場合、ガード条件は大丈夫だと思います。エラーまたは障害が発生したため、通常の実行を続行できません。これが発生した場合、例外をスローするか、エラーコードを返すか、または言語に適したものを返す必要があります。
この回答の元のバージョンの直後に、このようなコードが私のプロジェクトで発見されました:
Foobar merge(Foobar left, Foobar right) {
if(!left.isEmpty()) {
if(!right.isEmpty()) {
Foobar merged = // construct merged Foobar
// missing return merged statement
}
return left;
}
return right;
}
Else内にreturnを入れないことで、returnステートメントが欠落しているという事実を見落としました。他の人が雇われていたら、コードはコンパイルさえしなかっただろう。 (もちろん、私がずっと心配しているのは、このコードで書かれたテストがこの問題を検出しないのはかなり悪かったことです。)
私が見つけたelse
ブロックを削除する主な理由は、過度のインデントです。 else
ブロックの短絡により、よりフラットなコード構造が可能になります。
多くのif
ステートメントは基本的にガードです。これらは、nullポインターの逆参照やその他の「これを行わないでください!」エラー。そして、それらは現在の機能の迅速な終了につながります。例えば:
if (obj === NULL) {
return NULL;
}
// further processing that now can assume obj attributes are set
ここで、else
句は、ここでは非常に合理的であると思われるかもしれません。
if (obj === NULL) {
return NULL;
}
else if (obj.color != Blue) {
// handle case that object is not blue
}
そして実際、それがあなたのしていることがすべてであるならば、それは上の例よりもはるかにインデントされていません。しかし、これが実際のマルチレベルのデータ構造(JSON、HTML、XMLなどの一般的な形式を処理するときに常に発生する状態)で行うことはほとんどありません。したがって、特定のHTMLノードのすべての子のテキストを変更する場合は、次のように言います。
elements = tree.xpath(".//p");
if (elements === NULL) {
return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
return NULL;
}
for (c in p.children) {
c.text = c.text.toUpperCase();
}
return p;
ガードはコードのインデントを増やしません。 else
句を使用すると、すべての実際の作業が右側に移動し始めます。
elements = tree.xpath(".//p");
if (elements === NULL) {
return NULL;
}
else {
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
return NULL;
}
else {
for (c in p.children) {
c.text = c.text.toUpperCase();
}
return p;
}
}
これは重要な右方向の動きを持ち始めており、これは2つのガード条件のみを備えた、かなり自明な例です。ガードを追加するたびに、インデントレベルが1つ追加されます。私がXMLを処理したり、詳細なJSONフィードを使用したりした実際のコードは、3、4、またはそれ以上のガード条件を簡単にスタックできます。常にelse
を使用すると、4、5、または6レベルがインデントされます。そしてそれは間違いなくコードの複雑さの感覚に貢献し、何が何に一致するかを理解するのに費やす時間が増えました。迅速でフラットな、早期終了スタイルは、その「過剰な構造」の一部を排除し、よりシンプルに見えます。
補遺この回答へのコメントは、NULL /空の処理がではない唯一の理由であることが明確ではない可能性があることに気づきましたガード条件文用。ほとんどない!この単純な例はNULL /空の処理に重点を置いており、その他の理由は含まれていませんが、たとえば、「関連」コンテンツのXMLやASTなどの深い構造を検索すると、多くの場合、関連のないノードを除外して興味のない特定のテストが長く続きます。ケース。一連の「このノードが関連しない場合、リターン」テストは、NULL /空のガードが行うのと同じ種類の右方向のドリフトを引き起こします。そして実際には、後続の関連性テストは、検索された対応するより深いデータ構造のNULL /空のガードと頻繁に結合されます。
これを見ると:
_if(something){
return lamp;
}
return table;
_
一般的なイディオムが表示されます。一般的なpattern、私の頭では次のように変換されます。
_if(something){
return lamp;
}
else return table;
_
これはプログラミングで非常に一般的なイディオムであるため、この種のコードを見るのに慣れているプログラマーは、頭に暗黙の「翻訳」があり、それを適切な意味に「変換」します。
明示的なelse
は必要ありません。パターンを全体として認識したため、頭は「そこに配置します」。共通パターン全体の意味を理解しているので、それを明確にするために小さな詳細は必要ありません。
たとえば、次のテキストを読んでください。
これ読んだ?
春のパリが大好き
もしそうなら、あなたは間違っています。もう一度文章を読む。実際に書かれているのは
the春のパリが大好き
テキストには2つの "the"単語があります。では、なぜ2つのうち1つを見逃したのですか?
それはあなたの脳が共通のパターンを見て、すぐにそれをその既知の意味に翻訳したからです。 patternを見たときにすでに認識しているため、意味を理解するために詳細全体を詳細に読む必要はありませんでした。これが、余分な "the"を無視した理由です。
上記のイディオムも同じです。たくさんのコードを読んだプログラマーは、if(something) {return x;} return y;
のこのpatternパターンを見るのに慣れています。彼らはその意味を理解するためにelse
を使う必要はありません。なぜなら彼らの脳は「彼らのためにそこに置いている」からです。彼らはそれを既知の意味を持つパターンとして認識します:「右中括弧の後は、前のelse
の暗黙的なif
としてのみ実行されます」。
したがって、私の意見では、else
は不要です。しかし、もっと読みやすいと思われるものを使用してください。
彼らはコードに視覚的な混乱を追加するので、そうです、それらは複雑さを追加するかもしれません。
ただし、その逆も当てはまり、コード長を減らすための過度のリファクタリングによって複雑さが増す可能性もあります(もちろん、この単純なケースではありません)。
else
ブロックが意図を述べているという声明に同意します。しかし、これを実現するためにコードに複雑さを追加しているため、私の結論は異なります。
私はそれが論理の微妙な間違いを防ぐことを可能にするというあなたの陳述に同意しません。
例を見てみましょう。空が青で湿度が高い場合、湿度が高くない場合、または空が赤の場合にのみtrueが返されます。しかし、その後、中かっこを1つ間違えて、間違ったelse
に配置します。
bool CanLeaveWithoutUmbrella() {
if(sky.Color == Color.Blue)
{
if (sky.Humidity == Humidity.High)
{
return true;
}
else if (sky.Humidity != Humidity.High)
{
return true;
} else if (sky.Color == Color.Red)
{
return true;
}
} else
{
return false;
}
}
私たちは本番コードでこの種のばかげた間違いをすべて見てきましたが、検出するのが非常に難しい場合があります。
私は以下を提案します:
デフォルトがfalse
であることが一目でわかるので、これは読みやすいと思います。
また、ブロックの内容を強制的に移動して新しい句を挿入するという考えは、エラーが発生しやすくなります。これはどういうわけか オープン/クローズの原則 に関連しています(コードは拡張のためにオープンで、変更のためにクローズでなければなりません)。既存のコードを強制的に変更している(たとえば、コーダーがブレースのバランス調整でエラーを引き起こす可能性がある)。
最後に、あなたはあなたが正しいと思うあらかじめ決められた方法で答えるように人々を導いています。インスタンスによって、あなたは非常に単純な例を提示しますが、その後、人々はそれを考慮に入れるべきではないと述べます。ただし、例の単純さは質問全体に影響します。
ケースが提示されたケースのように単純である場合、私の答えは、if else
句をまったく省略することです。
return(sky.Color == Color.Blue);
ケースが少し複雑で、さまざまな条項がある場合、私は答えます。
bool CanLeaveWithoutUmbrella(){
if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
return true;
} else if (sky.Humidity != Humidity.High) {
return true;
} else if (sky.Color == Color.Red) {
return true;
}
return false;
}
ケースが複雑で、すべての句がtrueを返さない場合(おそらく、それらはdoubleを返す)、ブレークポイントまたはlogginコマンドを導入したい場合は、次のようなものを検討します。
double CanLeaveWithoutUmbrella() {
double risk = 0.0;
if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
risk = 1.0;
} else if (sky.Humidity != Humidity.High) {
risk = 0.5;
} else if (sky.Color == Color.Red) {
risk = 0.8;
}
Log("value of risk:" + risk);
return risk;
}
何かを返すメソッドではなく、変数を設定したりメソッドを呼び出したりするif-elseブロックについて話しているのであれば、はい、最後のelse
句を含めます。
したがって、例の単純さも考慮すべき要素です。
説明されているif-elseのケースはより複雑ですが、ほとんどの(すべてではない)実用的な状況では、それほど重要ではありません。
数年前、私が航空業界で使用されるソフトウェアに取り組んでいたとき(認定プロセスを経る必要がありました)、あなたの質問が浮上しました。それがコードの複雑さを増加させたので、その「他の」声明に経済的コストがあったことが判明しました。
これが理由です。
「else」の存在により、評価する必要のある暗黙の3番目のケースが作成されました。「if」も「else」も取得されていません。あなたは(当時のように)WTFを考えているのでは?
説明はこんな感じで......
論理的な観点から見ると、「if」と「else」の2つのオプションしかありません。しかし、私たちが証明していたのは、コンパイラが生成したオブジェクトファイルでした。したがって、「else」が存在する場合、生成された分岐コードが正しいこと、および不思議な3番目のパスが表示されなかったことを確認するために分析を行う必要がありました。
これは、「if」のみがあり、「else」がない場合と対照的です。その場合、「if」パスと「non-if」パスの2つのオプションしかありません。評価する2つのケースは、3つよりも複雑ではありません。
お役に立てれば。
コードの最も重要な目標は、コミット済みとして理解可能にすることです(これは、簡単にリファクタリングされるのとは対照的です。これは、有用ですがそれほど重要ではありません)。少し複雑なPythonの例を使用してこれを説明することができます:
def value(key):
if key == FROB:
return FOO
Elif key == NIK:
return BAR
[...]
else:
return BAZ
これはかなり明確です。これは、case
ステートメントまたはディクショナリルックアップに相当し、return foo == bar
の上位バージョンです。
KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ
def value(key):
return KEY_VALUES.get(key, default=DEFAULT_VALUE)
トークンの数が多い(2つのキーと値のペアを持つ元のコードでは32対24)にもかかわらず、これはより明確です関数のセマンティクスは明示的になりました:これは単なるルックアップです。
(これはリファクタリングに影響を与えます-もしkey == NIK
の場合に副作用を持ちたいなら、3つの選択肢があります:
if
/Elif
/else
スタイルに戻し、key == NIK
ケースに1行挿入します。if
ステートメントをvalue
に追加します。if
ステートメントを挿入します。これで、ルックアップ関数が強力な単純化である理由がわかります。これにより、3番目のオプションが明らかに最も簡単なソリューションになります。これは、最初のオプションよりも差が小さいことに加えてvalue
は1つのことだけを行います。)
OPのコードに戻ると、これはelse
ステートメントの複雑さのガイドとして役立つと思います:明示的なelse
ステートメントを含むコードは客観的により複雑ですが、より重要なのはそれがコードのセマンティクスを明確かつ単純にするかどうかそして、コードの各部分は異なる意味を持つため、ケースバイケースで回答する必要があります。
それはあなたが使っているif
ステートメントの種類に依存すると思います。一部のif
ステートメントは式として見ることができ、他のステートメントは制御フローステートメントとしてのみ見ることができます。
あなたの例は私にとって表現のように見えます。 Cのような言語では、三項演算子?:
はif
ステートメントに非常によく似ていますが、これは式であり、else部分は省略できません。式は常に値を持っている必要があるため、elseブランチは式で省略できないことは理にかなっています。
三項演算子を使用すると、例は次のようになります。
bool CanLeaveWithoutUmbrella()
{
return (sky.Color == Color.Blue)
? true
: false;
}
ただし、これはブール式であるため、次のように完全に簡略化する必要があります。
bool CanLeaveWithoutUmbrella()
{
return (sky.Color == Color.Blue);
}
明示的なelse句を含めると、意図がより明確になります。ガードは状況によっては意味をなす場合がありますが、2つの可能な値から選択する場合、どちらも例外的でも異常でもないため、役に立ちません。
この議論で考慮すべきもう1つのことは、各アプローチが促進する習慣です。
if/elseは、コーディングサンプルをブランチの観点からリフレームします。あなたが言うように、時々この明示性は良いです。実際には2つの実行パスが考えられますが、それを強調したいと思います。
他の場合では、実行パスが1つしかなく、プログラマが注意しなければならないすべての例外的なものがあります。あなたが述べたように、ガード句はこれに最適です。
もちろん、3つ以上のケース(n)があります。通常、if/elseチェーンを放棄し、case/switchステートメントまたはポリモーフィズムを使用することをお勧めします。
ここで心に留めておくべき原則は、メソッドまたは関数の目的は1つだけであるべきだということです。 if/elseまたはswitchステートメントを含むコードは、分岐専用です。したがって、それは不条理に短く(4行)、正しいメソッドに委譲するか、(例のように)明白な結果を生成する必要があります。コードが短い場合、それらの複数の戻りを見逃すことは困難です:)「有害と見なされるgoto」のように、分岐ステートメントが悪用される可能性があるため、elseステートメントに重要な考えを入れることは通常価値があります。おっしゃったように、elseブロックを用意するだけで、コードを束ねることができます。あなたとあなたのチームはあなたがそれについてどのように感じるかを決める必要があります。
ツールが本当に不満を言うのは、ifブロック内にreturn/break/throwがあるという事実です。つまり、それに関しては、保護条項を作成しましたが、それを台無しにしました。あなたはツールを幸せにするか、それを受け入れずにその提案を「楽しませる」ことができます。
答えは状況次第だと思います。 (間接的に指摘するように)コードサンプルは、条件の評価を返すだけであり、ご存じのとおり、単一の式として表現するのが最適です。
Else条件を追加することでコードが明確になるか不明瞭になるかを判別するには、IF/ELSEが何を表すかを判別する必要があります。それは(あなたの例のように)式ですか?その場合、その式を抽出して使用する必要があります。ガード条件ですか?その場合、2つのブランチが同等に見えるため、ELSEは不要で誤解を招く可能性があります。手続き的多型の例ですか?それを抽出します。前提条件を設定していますか?次に、それが不可欠になることができます。
ELSEを含めるか制限するかを決定する前に、ELSEが何を表すかを決定します。これにより、ELSEが存在するかどうかがわかります。
答えは少し主観的です。 else
ブロックは、1つのコードブロックが別のコードブロックに対して排他的に実行されることを確立することで、両方のブロックを読み取る必要なく読みやすくすることができます。これは、たとえば、両方のブロックが比較的長い場合に役立ちます。ただし、if
sなしでelse
sを使用すると読みやすくなる場合があります。次のコードをいくつかのif/else
ブロックと比較してください。
if(a == b) {
if(c <= d) {
if(e != f) {
a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;
} else {
e++;
return 3;
}
} else {
return 1;
}
} else {
return 0;
}
と:
if(a != b) {
return 0;
}
if(c > d) {
return 1;
}
if(e != f) {
e++;
return 3;
}
a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;
コードの2番目のブロックは、ネストされたif
ステートメントをガード句に変更します。これによりインデントが減り、一部の戻り条件が明確になります( "a != b
の場合、0
!を返します")。
私の例にはいくつかの穴があるかもしれないので、コメントで指摘されたので、もう少し具体化します。
ここでは、コードを読みやすくするために、最初のコードブロックを書き出すこともできます。
if(a != b) {
return 0;
} else if(c > d) {
return 1;
} else if(e != f) {
e++;
return 3;
} else {
a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;
}
このコードは上記の両方のブロックと同等ですが、いくつかの課題があります。ここでのelse
句connectsこれらのifステートメントを組み合わせます。上記のガード節バージョンでは、ガード節は互いに独立しています。新しい変数を追加するということは、最後のif
と残りのロジックの間に新しいif
を書き込むだけです。ここでも、わずかな量の認知的負荷でそれを行うことができます。ただし、新しいガード句の前に追加のロジックを実行する必要がある場合が異なります。ステートメントが独立している場合、次のことができます。
...
if(e != f) {
e++;
return 3;
}
g.doSomeLogicWith(a);
if(!g.isGood()) {
return 4;
}
a.doSomeStuff();
...
接続されたif/else
チェーンでは、これはそれほど簡単ではありません。実際のところ、最も簡単な方法は、チェーンを分割して、ガード句バージョンのように見せることです。
しかし、本当に、これは理にかなっています。 if/else
を使用すると、パーツ間の関係が弱くなります。 a != b
はc > d
連鎖バージョンのif/else
に何らかの形で関連していると推測できます。ガード条項のバージョンから、それらが実際には関連性がないことがわかるように、その仮定は誤解を招くでしょう。つまり、(句のパフォーマンスを最適化したり、論理フローを改善したりするために)それらを再配置するなど、独立した句でより多くのことを実行できます。それらを過ぎたら、認識的に無視することもできます。 if/else
チェーンでは、a
とb
の間の同値関係が後でポップアップ表示されないことはあまりわかりません。
else
が暗示する関係は、深くネストされたコード例でも明らかですが、方法は異なります。 else
ステートメントは、それらが接続されている条件から分離され、reverseの順序で条件に関連付けられています(最初のelse
は最後のif
に接続され、最後のelse
は最初のif
に接続されます) )。これは認知の不協和を生み出し、コードを逆戻りして、脳のインデントを揃えようとします。これは、かっこをたくさん合わせようとするようなものです。インデントが間違っていると、さらに悪化します。オプションのブレースも役に立ちません。
Elseブロックの省略に同意するケースについて言及するのを忘れており、ifブロックを使用して、null-check(またはその他のガードなど)に続くすべてのコードに対して論理的に満たす必要がある制約を適用する場合)。
これは素晴らしい譲歩ですが、実際に回答を無効にすることはありません。私はあなたのコード例でそれを言うことができます:
bool CanLeaveWithoutUmbrella() { if(sky.Color != Color.Blue) { return false; } return true; }
このようなコードを書くための有効な解釈は、「空が青くない場合に限り、傘を持って出発しなければならない」というものである可能性があります。次のコードを実行するには、空が青でなければならないという制約があります。私はあなたの譲歩の定義を満たしました。
空の色が黒の場合、晴れた夜なので、傘は必要ありません。 (これを書く簡単な方法がありますが、例全体を書く簡単な方法があります。)
bool CanLeaveWithoutUmbrella()
{
if(sky.Color == Color.Blue)
{
return true;
}
if(sky.Color == Color.Black)
{
return true;
}
return false;
}
上記のより大きな例のように、あまり必要とせずに新しい節を追加するだけです。 if/else
では、特にロジックがより複雑である場合、何かを台無しにしないかどうか確信が持てません。
また、あなたの単純な例もそれほど単純ではないことも指摘しておきます。非バイナリ値のテストは、その結果の逆のテストの逆とは異なります。
はい、else句がredundantであるため、複雑さが増します。したがって、コードを無駄のない意味のあるものにするために、除外することをお勧めします。冗長なthis
修飾子(Java、C++、C#)を省略したり、return
ステートメントで括弧を省略したりするのと同じコードです。
優れたプログラマーは、「何を追加できるか」と考えています。一方、優れたプログラマーは「何を削除できるか」と考えています。
例を単純化しすぎています。 Eitherの方がより大きな状況に応じて読みやすくなる場合があります。具体的には、条件の2つのブランチのいずれかがabnormal。お見せしましょう:どちらかのブランチが同等に可能性がある場合、...
void DoOneOfTwoThings(bool which)
{
if (which)
DoThingOne();
else
DoThingTwo();
}
...は...より読みやすい.
void DoOneOfTwoThings(bool which)
{
if (which) {
DoThingOne();
return;
}
DoThingTwo();
}
...最初のものは parallel structure を示し、2番目のものは示さないためです。ただし、関数の大部分が1つのタスクに費やされ、いくつかの前提条件を最初に確認する必要がある場合は、...
void ProcessInputOfTypeOne(String input)
{
if (InputIsReallyTypeTwo(input)) {
ProcessInputOfTypeTwo(input);
return;
}
ProcessInputDefinitelyOfTypeOne(input);
}
...は...より読みやすい.
void ProcessInputOfTypeOne(String input)
{
if (InputIsReallyTypeTwo(input))
ProcessInputOfTypeTwo(input);
else
ProcessInputDefinitelyOfTypeOne(input);
}
...故意にnot並列構造を設定すると、将来の読者に、「本当に2種類」の入力を取得する手掛かりがabnormal。 (実際には、ProcessInputDefinitelyOfTypeOne
は独立した関数ではないため、違いはさらに大きくなります。)