私には知り合いがおり、私よりも熟練した開発者です。私たちはプログラミングの実践について話していましたが、私は彼の「if」ステートメントに対するアプローチにびっくりしました。彼は、私がかなり変だと思うifステートメントに関するいくつかの慣行を主張しています。
最初に、何かを入れるかどうかにかかわらず、ifステートメントの後にelseステートメントを続ける必要があります。これにより、コードは次のようになります。
if(condition)
{
doStuff();
return whatever;
}
else
{
}
2番目に、falseではなく、true値をテストすることをお勧めします。つまり、「!doorOpened」変数ではなく「doorClosed」変数をテストする方が良いということです。
彼の議論は、それがコードがしていることをより明確にすることです。
この2つのルールを組み合わせると、条件が満たされていないときに何かを実行したい場合に、この種のコードを書くようになる可能性があるため、かなり混乱します。
if(condition)
{
}
else
{
doStuff();
return whatever;
}
これについての私の感じは、それは確かに非常に醜いこと、および/または品質の改善があるとしても無視できるほどのものであるということです。しかし、ジュニアとして、私は自分の本能を疑う傾向があります。
だから私の質問は:それは良い/悪い/「問題ではない」練習ですか?それは一般的な練習ですか?
else
ブロック最初のルールはコードを汚染し、コードを読みやすくしたり、エラーを起こしにくくしたりするだけです。あなたの同僚の目的は、おそらく条件がfalse
と評価される可能性があることを開発者が完全に認識していることを示すことによって、明示的であることです。 明示的にすることは良いことですが、そのような明示性は3行の追加コードを犠牲にしてはなりません。
if
ステートメントの後に必ずしもelse
が続く、または何もないという事実さえも触れていません。1つ以上のElif
sが続くこともあります。
return
ステートメントの存在は事態を悪化させます。実際にelse
ブロック内で実行するコードがあったとしても、次のようにすると読みやすくなります。
if (something)
{
doStuff();
return whatever;
}
doOtherThings();
return somethingElse;
これにより、コードの所要時間が2行少なくなり、else
ブロックのインデントが解除されます。 Guard句 がすべてです。
ただし、同僚の手法は、スペースのない積み重ねられた条件付きブロックの非常に厄介なパターンを部分的に解決できることに注意してください。
if (something)
{
}
if (other)
{
}
else
{
}
前のコードでは、最初のif
ブロックの後にまともな改行がないため、コードが誤って解釈されやすくなっています。ただし、同僚の規則ではコードを読み間違えることが難しくなりますが、簡単な解決策は改行を追加することです。
true
ではなくfalse
をテストします2番目のルールはある程度の意味があるかもしれませんが、現在の形式では意味がありません。
閉じたドアのテストは、開いていないドアのテストよりも直感的であることは間違いありません。否定、特にネストされた否定は、通常、理解するのが困難です。
if (!this.IsMaster || (!this.Ready && !this.CanWrite))
これを解決するには、空のブロックを追加する代わりに、関連する追加のプロパティ、またはローカル変数を作成します。
上記の条件は、かなり簡単に読みやすくすることができます。
if (this.IsSlave || (this.Synchronizing && this.ReadOnly))
最初のルールに関しては、これは無駄なタイピングの例です。タイプするのに時間がかかるだけでなく、コードを読む人に大きな混乱を引き起こします。コードが不要な場合は記述しないでください。これは、コードがelse
ブロックから戻るため、ケースにif
が入力されていないことにも拡張されます。
if(condition)
{
doStuff();
return whatever;
}
doSomethingElse(); // no else needed
return somethingelse;
2番目の点に関しては、負の値を含むブール名を回避することをお勧めします。
bool doorNotOpen = false; // a double negative is hard to reason
bool doorClosed = false; // this is much clearer
ただし、これをネガティブのテストを再度行わないように拡張すると、ご指摘のとおり、より無駄なタイピングにつながります。次の例は、空のif
ブロックよりもはるかに明確です。
if(!condition)
{
doStuff();
return whatever;
}
else
ステートメントを支持する引数。私はしばしば、最初の構成に似たもの、空のそれ以外のものを使用します(そして主張します)。これは、プログラマーが状況を考慮したことをコードの読者(人間および自動分析ツールの両方)に通知します。存在すべきだったelse
ステートメントの欠落は、人々を殺し、車両を墜落させ、数百万ドルの費用をかけました。たとえば、MISRA-Cは、少なくとも、コメントが欠落しているfinal elseがif (condition_1) {do_this;} else if (condition_2) {do_that;} ... else if (condition_n) {do_something_else;}
シーケンスで意図的であると述べています。他の高い信頼性基準はさらに進んでいます。いくつかの例外を除いて、elseステートメントの欠落は禁止されています。
1つの例外は、_/* Else not required */
_に沿った単純なコメントです。これは、3行が空の場合と同じ意図を示します。空のelseが不要なもう1つの例外は、コードの読者と自動分析ツールの両方にとって、他の要素が空であることは明白であることです。たとえば、if (condition) { do_stuff; return; }
同様に、_throw something
_または_goto some_label
_の場合、空のelseは必要ありません。1 return
の代わりに。
if (condition)
よりもif (!condition)
を優先するための引数。これはヒューマンファクターのアイテムです。複雑なブール論理は多くの人々をつまずかせます。熟練したプログラマーでもif (!(complex || (boolean && condition))) do_that; else do_this;
について考える必要があります。少なくとも、これをif (complex || (boolean && condition)) do_this; else do_that;
に書き換えてください。
then
ステートメントを優先する必要があるという意味ではありません。2番目のセクションでは、「なた」ではなく「優先する」と述べています。ルールというよりもガイドラインです。そのガイドラインが肯定的なif
条件を好む理由は、コードが明確で明白であるべきだからです。空のthen句(例:if (condition) ; else do_something;
)はこれに違反しています。それは難読化されたプログラミングであり、最も熟練したプログラマでさえ、if
条件をバックアップし、否定された形式で再読み取りします。そのため、最初は否定形式で記述し、elseステートメントを省略します(または、そうするように強制されている場合は、空のelseまたはその旨のコメントを付けます)。
1次に、return
、throw
、またはgoto
で終わる節は、空のelseを必要としないと書きました。 else句が不要であることは明らかです。しかし、goto
はどうですか?余談ですが、安全性が重要なプログラミングルールでは、早期の復帰が許可されない場合があり、ほとんどの場合、例外のスローが禁止されています。ただし、制限された形式(_goto cleanup1;
_など)ではgoto
は許可されます。 goto
のこの制限された使用は、一部の場所で推奨される方法です。たとえば、Linuxカーネルはそのようなgoto
ステートメントでいっぱいです。
非常にまれなケースで空のelseブランチ(場合によっては空のifブランチ)を使用します。ifとelseの両方の部分を何らかの方法で処理する必要があることが明らかであるが、いくつかの重要な理由でケースを処理できる場合何もしない。したがって、必要なelseアクションでコードを読んだ人は、何かが欠けているとすぐに疑い、時間を浪費します。
if (condition) {
// No action needed because ...
} else {
do_else_action()
}
if (condition) {
do_if_action()
} else {
// No action needed because ...
}
だがしかし:
if (condition) {
do_if_action()
} else {
// I was told that an if always should have an else ...
}
あなたが書いていないこと、誰も読んで理解する必要はありません。
明示的であると便利な場合がありますが、これは、記述した内容が本当に意図したとおりであることを過度の冗長性なしに明らかにした場合にのみ当てはまります。
したがって、空のブランチは避けてください。それらは役に立たないだけでなく、一般的でもないため、混乱を招きます。
また、if分岐から直接抜ける場合は、else分岐を記述しないでください。
明示性の有用なアプリケーションは、スイッチケース_// FALLTHRU
_に陥るたびにコメントを付け、空のステートメントfor(a;b;c) /**/;
が必要な場合にコメントまたは空のブロックを使用することです。
私の知る限りでは、IFステートメントの肯定的条件または否定的条件に関する厳格な規則はありません。私は個人的にprefer該当する場合、否定ではなく肯定のケースをコーディングします。ただし、これで空のIFブロックが作成され、その後にELSEがロジックでいっぱいになった場合は、これを実行しません。このような状況が発生した場合、とにかく陽性のケースをテストするためにリファクタリングするのに3秒ほどかかります。
しかし、私があなたの例について本当に気に入らないのは、空白のELSEが占める完全に不必要な垂直スペースです。これを行う理由はまったくありません。それはロジックに何も追加しません、それはコードが何をしているかを文書化するのを助けません、そしてそれは全く読みやすさを増しません。実際、追加された垂直方向のスペースが減少読みやすさになる可能性があると私は主張します。
else
ブロックすべてのif
ステートメントをカバーする包括的なステートメントとしてこれに同意しませんが、習慣からelse
ブロックを追加するのが良い場合があります。
if
ステートメントは、私の考えでは、実際には2つの異なる関数をカバーしています。
何かをすることになっている場合は、ここでそれを行ってください。
このようなものは明らかにnotにはelse
の部分が必要です。
if (customer.hasCataracts()) {
appointmentSuggestions.add(new CataractAppointment(customer));
}
if (customer.isDiabetic()) {
customer.assignNurse(DiabeticNurses.pickBestFor(customer));
}
場合によっては、else
の追加を主張すると誤解を招く可能性があります。
if (k > n) {
return BigInteger.ZERO;
}
if (k <= 0 || k == n) {
return BigInteger.ONE;
}
is notと同じ
if (k > n) {
return BigInteger.ZERO;
} else {
if (k <= 0 || k == n) {
return BigInteger.ONE;
}
}
機能的には同じですが。空のif
を使用して最初のelse
を書き込むと、不必要に醜い2番目の結果が表示される場合があります。
特定の状態をチェックしている場合は、空のelse
を追加して、その不測の事態をカバーすることを思い出させることをお勧めします
// Count wins/losses.
if (doors[firstChoice] == Prize.Car) {
// We would have won without switching!
winWhenNotSwitched += 1;
} else {
// We win if we switched to the car!
if (doors[secondChoice] == Prize.Car) {
// We picked right!
winWhenSwitched += 1;
} else {
// Bad choice.
lost += 1;
}
}
これらの規則が適用されることに注意してください新しいコードを記述している場合のみ。 IMHO空のelse
句は、チェックイン前に削除する必要があります。
true
ではなくfalse
をテストしますこれも一般的なレベルでは良いアドバイスですが、多くの場合、コードが不必要に複雑になり、読みにくくなります。
コードのように
if(!customer.canBuyAlcohol()) {
// ...
}
読者には不快ですが、それを作ります
if(customer.canBuyAlcohol()) {
// Do nothing.
} else {
// ...
}
悪くはないとしても、少なくとも同じくらい悪いです。
私は何年も前にBCPLでコーディングしましたが、その言語にはIF
句andUNLESS
句があるため、次のように読みやすくコーディングできます。
unless(customer.canBuyAlcohol()) {
// ...
}
これはかなり良いですが、まだ完璧ではありません。
一般的に、私がnewコードを書いているとき、空のelse
ブロックをif
ステートメントに追加することがよくあります。これは、DFS
トラップを回避するのに役立ち、コードを確認すると、まだやらなければならないことがあることに気づきます。ただし、追跡するために通常はTODO
コメントを追加します。
if (returnVal == JFileChooser.APPROVE_OPTION) {
handleFileChosen();
} else {
// TODO: Handle case where they pressed Cancel.
}
コードの臭いを示すことがあるので、通常、コードでelse
を使用することはほとんどありません。
最初のポイントとして、IFステートメントをこのように強制的に使用する言語(Opalでは、メインフレームシステムにGUIフロントエンドを配置するためのメインフレームスクリーンスクレーパーの背後にある言語)を使用し、IFには1行しかありませんとELSE。それは楽しい経験ではありませんでした!
どんなコンパイラーもそのような追加のELSE節を最適化することを期待します。ただし、ライブコードの場合は何も追加されません(開発中は、追加コードのマーカーとして役立ちます)。
これらの余分な句のようなものを使用するのは、CASE/WHENタイプの処理を使用するときです。デフォルトの句が空でも常に追加します。これは、そのような句が使用されない場合にエラーになる言語の長期的な習慣であり、物事が本当に失敗するべきかどうかについての考えを強制します。
ずっと前のメインフレーム(PL/1やCOBOLなど)の慣例では、否定的なチェックの効率が悪いことが認められていました。これは2番目のポイントを説明する可能性がありますが、最近では、マイクロ最適化として無視されている非常に重要な効率の節約があります。
このような単純なIFステートメントではそれほど多くはありませんが、負論理は読みにくくなる傾向があります。
空のelse
ブロックは事実上常に電子インクの有害な廃棄物であるというほとんどの回答のスタンドを次に示します。正当な理由がない限り、これらを追加しないでください。その場合、空のブロックはまったく空であってはならず、そこにある理由を説明するコメントが含まれている必要があります。
ただし、ネガティブの回避に関する問題にはもう少し注意が必要です。通常、ブール値を使用する必要がある場合は常に、設定時に動作するコードブロックと設定されていないときに動作する他のブロックが必要です。そのため、否定でないルールを適用する場合は、if() {} else {...}
ステートメント(空のif
ブロックを使用!)を適用するか、またはそれを含むブールごとに2番目のブールを作成します。否定された値。 読者を混乱させるため、どちらのオプションも不適切です。
役立つポリシーは次のとおりです。ブール名の中で否定形式を使用しないでください。否定は単一の_!
_として表現してください。 if(!doorLocked)
のようなステートメントは完全に明確です。if(!doorUnlocked)
のようなステートメントは頭脳を結びつけます。後者のタイプの式は、if()
条件に単一の_!
_が存在するのではなく、絶対に回避する必要があるものです。
間違いなく悪い習慣だと思います。 elseステートメントを追加すると、何も実行しない無意味な行がコードに追加され、さらに読みにくくなります。
空のifブロックを持つ2番目のケースの処理についてまだ言及されていない別のパターンがあります。これに対処する1つの方法は、ifブロックで戻ることです。このような:
void foo()
{
if (condition) return;
doStuff();
...
}
これは、エラー状態をチェックするための一般的なパターンです。肯定的なケースはまだ使用されており、その内部はもはや空ではなく、将来のプログラマーは何もしないことが意図的であったかどうか疑問に思いました。また、新しい関数を作成する必要があるため、読みやすさも向上します(そのため、大きな関数を小さな個々の関数に分割します)。
質問の2番目の部分についてのみ説明します。これは、産業システムでの作業と実世界でのデバイスの操作という私の視点から来ています。
ただし、これは回答ではなく、拡張コメントになる場合があります。
記載されている場合
次に、falseではなく、true値をテストする方が適切です。つまり、「!doorOpened」変数ではなく「doorClosed」変数をテストする方が良いということです。
彼の主張は、それがコードが何をしているかをより明確にするということです。
私の観点から、ドアの状態は実際には少なくとも3値の状態であるのに、2値の状態であるという仮定がここで行われています。
したがって、単一のバイナリデジタルセンサーが配置されている場所に応じて、次の2つの概念のうち1つしか推測できません。
また、バイナリ否定を使用してこれらの概念を交換することはできません。したがって、doorClosed
および!doorOpened
は同義語ではない可能性が高く、それらが同義語であると偽装しようとする試みは、実際に存在するよりもシステム状態の知識が多いと想定する誤った考えです。
あなたの質問に戻って、変数が表す情報の起源に一致する表現をサポートします。したがって、ドアの閉じた側から情報を取得する場合は、doorClosed
などを使用します。これは、doorClosed
または!doorClosed
必要に応じて、さまざまなステートメントで必要に応じて、否定の使用と混乱する可能性があります。しかし、それが行わないことは、システムの状態に関する仮定を暗黙的に伝播することです。
私が行う作業の説明のために、システムの状態について入手できる情報の量は、システム全体に必要な機能によって異なります。
時々私はドアが閉まっているかどうかを知る必要があるだけです。その場合、単一のバイナリセンサーのみで十分です。しかし、他の場合には、ドアが開いている、閉じている、または移行中であることを知る必要があります。このような場合、2つのバイナリセンサー(ドアの動きの両極端-ドアが同時に開閉することに対するエラーチェック)が存在するか、またはドアがどれだけ「開いている」かを測定するアナログセンサーが存在します。
最初の例は、電子レンジのドアです。ドアが閉じているか閉じていないかに基づいて、オーブンの操作を有効にします。ドアがどれほど開いているかは気にしません。
2番目の例は、単純なモーター駆動アクチュエータです。アクチュエーターが完全に停止している場合、モーターの前進を禁止します。そして、アクチュエーターが完全に入っているときは、モーターの逆転を禁止します。
基本的に、センサーの数とタイプは、必要な機能を実現するために必要なものの要件分析に対してセンサーのコスト分析を実行することになります。
私が他の回答で見たことのない「常にelse節を持っている」という議論を検討するとき、1つのポイントがあります。それは、関数型プログラミングスタイルで意味をなすことができます。ちょっと。
関数型プログラミングスタイルでは、ステートメントではなく式を扱います。そのため、すべてのコードブロックには戻り値があります-if-then-else
式を含みます。ただし、空のelse
ブロックは除外されます。例を挙げましょう。
var even = if (n % 2 == 0) {
return "even";
} else {
return "odd";
}
さて、CスタイルまたはCスタイルにインスパイアされた構文(Java、C#、JavaScriptなど)を持つ言語では、これは奇妙に見えます。ただし、次のように書くと見た目がはるかによくなります。
var even = (n % 2 == 0) ? "even" : "odd";
ここでelse
ブランチを空のままにすると、値が未定義になります。ほとんどの場合、機能をプログラミングするときに有効なケースにしたいものではありません。完全に省略しても同じです。ただし、繰り返しプログラミングしているときは、常にelse
ブロックを使用する理由はほとんどありません。