このトピックに関する人々の考えがどうなっているのか興味があります。オブジェクトの配列があり、オブジェクトに特定の値が含まれているかどうかを確認するためにそれらをループしたいとします。含まれている場合は、ループを停止します。どちらがより良い習慣です-ブレークのあるforループ、または条件付きループ?
私が提供した例の擬似コードは、議論のためだけのものです(これは、最近の私の第一言語であるため、ActionScriptにも含まれています)。また、構文に関するベストプラクティスのアイデアを探していません。
ブレーク付きのforループ:
var i:int;
var isBaxterInMilwaukee:Boolean;
for (i = 0; i < arrayLen; i++)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
break;
}
}
条件付きループ:
var i:int;
var isBaxterInMilwaukee:Boolean;
while (!isBaxterInMilwaukee && i < arrayLen)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
i++;
}
要するに、読みやすく、保守しやすいバージョンを選択する必要があります。
少し古い時代には、ループから抜け出すことはノーノーと見なされていたことがわかっています(gotoステートメントと同等)。ループはループ状態で壊れ、他の場所では壊れないはずでした。したがって、whileループが進むべき道でした。
(これはおそらくAssemblyからの引き継ぎであり、ループは基本的にコードのブロックであり、最後にgo-to-the-beginning-if-trueジャンプステートメントがあります。ブロック内の複数の条件付きジャンプステートメントにより、デバッグが非常に困難になります。 ;したがって、それらは回避され、最後に1つに結合されます。)
このアイデアは、特にforeachループとマネージドワールドで、今日少し変わっているように感じます。今は本当にスタイルの問題です。もちろん、一部の純粋主義者を除いて、ブレークオンファウンドのforループはおそらく多くの人に受け入れられるようになっています。ただし、whileループでbreakを使用することは避けます。これは、ループ条件を混乱させ、混乱させる可能性があるためです。
Foreachループの使用を許可する場合、以下のコードは、whileループの兄弟よりもlot読みやすいと思います。
bool isBaxterInMilwaukee;
foreach (var item in myArray)
{
if (item.name == "baxter" && item.location == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
break;
}
}
ただし、ロジックが複雑になるにつれて、break
ステートメントの近くにある目立つコメントを検討して、それが埋もれて見つけにくくなるのを防ぐことができます。
おそらく、このすべてを独自の関数にリファクタリングする必要がありますこれは見つかったときにbreak
ではありませんが、実際には結果はreturn
sです(代わりにforループバージョンを自由に使用してください):
bool isBaxterInMilwaukee(Array myArray)
{
foreach (var item in myArray)
{
if (item.name == "baxter" && item.location == "milwaukee")
{
barkTwice();
return true;
}
}
return false;
}
Esko Luontolaが指摘したように、副作用は関数の名前から明らかではなく、すべての場合にバクスターを見つけることに関連していないため、この関数の外でbarkTwice()
に呼び出しを移動するのがおそらく最善でしょう。 (または、ブールパラメータBarkTwiceIfFound
を追加し、行をif(BarkTwiceIfFound) barkTwice();
に変更して、副作用を明確にします。)
記録のために、forループでフラグチェックを中断せずに行うこともできますが、forループ定義で余分な条件を予期しないため、これは実際には読みやすさを損なうと思います。
var i:int;
var isBaxterInMilwaukee:Boolean;
for (i = 0; !isBaxterInMilwaukee && i < arrayLen; i++)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
}
また、whileループを使用して自動インクリメントのメカニズムをシミュレートすることもできます。私はいくつかの理由でこれが好きではありません-i
を実際の開始値より1小さい値に初期化する必要があり、コンパイラがループ条件ロジックを短絡する方法によっては、ループを終了するときのi
の値が異なります。それにもかかわらず、それは可能であり、一部の人々にとって、これは読みやすさを向上させることができます:
var i:int = -1;
var isBaxterInMilwaukee:Boolean;
while (!isBaxterInMilwaukee && ++i < arrayLen)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
}
私はいつもコードでのbreaks
の使用を嫌っていました...この場合、それは問題ではないようですが、より複雑なループでは、それを読んでいる別のコーダーにとってひどく混乱する可能性があります。一般に、コーダーがネストされたbreak
をループの奥深くに見つけるまで、ループがどのように終了するかを理解できないことがよくあります。ループの各反復をチェックするフラグ条件を指定することにより、これがはるかに明確になります。
この問題は、(return
変数を設定してメソッドの最後に戻るのではなく、メソッドの本体の奥深くにあるretVal
ステートメントが簡単に見つけられない場合に似ています。 )。小さな方法では、これは問題ないように見えますが、大きくなるほど、これはより混乱します。
それは運用効率の問題ではなく、保守性の問題です。
特定の状況で何が読みやすく理解できるかを同僚に尋ねてください...それが本当に重要なことです。
状況次第だと思います。この場合、ブレークのあるループは私にはより明確に見えます。
2つの間に概念的な違いがあります。 for
ループは離散セットを反復するためのものであり、while
ループは条件に基づいてステートメントを繰り返すためのものです。他の言語では、finally
句とforeach
やuntil
などのループ構造が追加されています。従来のfor
ループはかなり少ない傾向があります。
いずれにせよ、私が使用するルールは、for
ループが繰り返され、while
ループが繰り返されるというものです。次のようなものが表示された場合:
while (counter <= end) {
// do really cool stuff
++counter;
}
次に、反復しているので、for
ループを使用したほうがよいでしょう。ただし、次のようなループが発生します。
for (int tryCount=0; tryCount<2; ++tryCount) {
if (someOperation() == SUCCESS) {
break;
}
}
条件が真になるまで実際に何かを繰り返しているので、while
ループとして記述する必要があります。
break
を使用しないという考えはgoto
と同じくらい邪悪ですはかなり無意味です。では、どうすれば例外をスローすることを正当化できますか?これは、非ローカルで非決定論的な後藤です。ちなみに、これは例外処理に対する怒りではなく、単なる観察です。
Forループでは、forループ宣言に早期終了基準を設定することで早期終了することもできます。したがって、あなたの例では、次のように行うことができます。
var i:int;
var isBaxterInMilwaukee:Boolean;
isBaxterInMilwaukee = false;
for (i = 0; i < arrayLen && !isBaxterInMilwaukee; i++)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
}
そうすれば、休憩は必要なく、whileループよりも読みやすくなります。
最も理にかなっているのは、コードを最もよく読んでいる人間にアイデアを伝えるものです。コードの可読性firstを覚えておいてください。通常、正しい選択をします。通常、break
のようなものは、本当に必要な場合を除いて使用したくありません。頻繁に実行したり、深くネストされた式のセットで実行したりすると、追跡が困難になる可能性があるためです。 continue
は、ブレークと同じ目的を果たす場合があり、ループがブレークしたためではなく、正常に終了します。この場合、私がこれを書くかもしれないいくつかの異なる方法があります。
おそらく、ここで必要なのは、while
ループを変更することです。
while(!isBaxterInMilwaukee || i < arrayLen) {
if(myArray[i]["name"] == "baxter" && myArray[i]["location"] == "milwaukee") {
isBaxterInMilwaukee == true;
barkTwice()
} else {
i++;
}
}
これは明確で、break
またはcontinue
を使用していないため、while
式で指定された条件のいずれかの結果として常に終了することが一目でわかります。
[〜#〜] eta [〜#〜]:おそらくwhile
ループ内のi < arrayLen
である必要があります。そうでない場合、入力値がターゲット値と同じでない限り、最初は失敗します。 ..
breakは「構造化された後藤」と見なされるため、慎重に使用する必要があります。それが悪の少ない方であるならば、それを使ってください。
continueをさらに避けてください。
編集:私はかなりの数のコメントといくつかの反対票を得たので、私はいくつかの裏付けとなる参考文献を追加します
そしてJava(。52を参照) の弱いステートメント
両方のループで中断が見られますが、それは正しいですか?
とにかく:
私は間違いなくfor + breakで行きます。 「for」は、「iterate over sequence」のすぐに認識できるイディオムであり、「iterateoversequence」の方が理解しやすいです。ループと停止を組み合わせた条件よりも、値が見つかった場合は早期に終了します。
条件付きループコードで2つの間違いを犯したように見える方法で、これの証拠があるかもしれません!
while条件(!isBaxterInMilwaukee || i == arrayLen)—「(!(isBaxterInMilwaukee || i == arrayLen))」という意味ですか?
終了ループ変数を使用している場合は、breakステートメントは不要です。
個人的には、終了ループ変数を追跡するよりも、単純な「ブレーク」の方がはるかに読みやすいと思います。
問題には2つの側面があります。
どちらの例も2つを組み合わせており、howからwhatを理解するのは困難です。 whatの部分だけをコードで表現できれば最高です。 specification パターンを使用してこれを行う例(c#3.5)を次に示します。
// what we are looking for?
IsPersonInLocation condition = new IsPersonInLocation("baxter", "milwaukee");
// does the array contain what we are looking for?
bool found = myArray.Find(item => condition.IsSatifiedBy(item));
// do something if the condition is satisfied
if (found) {
barkTwice();
}
完全を期すために、条件のクラス定義を次に示します。
class IsPersonInLocation {
public string Person { get; set; }
public string Location { get; set; }
public IsPersonInLocation(string person, string location) {
this.Person = person;
this.Location = location;
}
bool IsSatifiedBy(item) {
return item["name"] == this.Person
&& item["location"] == this.Location;
}
}
ブレーク、クリアと言います(ループから抜ける理由をコメントに入れても)whileループがクリアではないので、ブレークに行きます
ループを独自のメソッドでカプセル化し、一致条件が成功したら、return toend処理を使用します。
いくつかのC#コードの例:
class Program
{
static bool IsBaxterInMilwaukee(IList<WhoAndWhere> peopleAndPlaces)
{
foreach (WhoAndWhere personAndPlace in peopleAndPlaces)
{
if (personAndPlace.Name == "Baxter"
&& personAndPlace.Location == "Milwaukee")
{
return true;
}
}
return false;
}
static void Main(string[] args)
{
List<WhoAndWhere> somePeopleAndPlaces = new List<WhoAndWhere>();
somePeopleAndPlaces.Add(new WhoAndWhere("Fred", "Vancouver"));
somePeopleAndPlaces.Add(new WhoAndWhere("Baxter", "Milwaukee"));
somePeopleAndPlaces.Add(new WhoAndWhere("George", "London"));
if (IsBaxterInMilwaukee(somePeopleAndPlaces))
{
// BarkTwice()
Console.WriteLine("Bark twice");
}
}
public class WhoAndWhere
{
public WhoAndWhere(string name, string location)
{
this.Name = name;
this.Location = location;
}
public string Name { get; private set; }
public string Location { get; private set; }
}
}
それは特定の状況が何であるかに大きく依存します。しかし、あなたの例では、制限された長さの配列を歩きたいので、forループを使用すると、それを簡単に実行でき、終わりから逃げるのを防ぐことができます。 whileループの例では、独自のインクリメントを実行する必要があります。これは、continue
ステートメントを使用して次のサイクルにスキップする場合に問題になる可能性があります。さらに複雑な条件式を作成します(これにより、ちなみに、バグがあります。あなたは&& i != arrayLen
を意味していると思います)。 forループが提供する効果を実現するには、追加のコードを実行する必要があります。
もちろん、一部の純粋主義者は、break
とcontinue
は使用すべきではなく、ループを継続したり中断したりするのではなく、必要に応じてif-else変数とブール変数を使用する必要があると主張します。しかし、特にこの例のように比較的短くて一般的に把握しやすい場合は、ループがはるかに見苦しく見える可能性があると思います。中断または続行が通知から簡単に隠れる可能性がある、はるかに長いコードを含むループの場合、ループはすでに把握が複雑であるため、純粋主義的なアプローチの方が明確な場合があります。ただし、これはforループの一部としていつでも実行でき、条件の一部として追加するだけです。
また、何かが原因でi
が正確な値をスキップした場合に備えて、完全に等しいかどうかではなく、i < arrayLen
でバインドされた配列をテストすることをお勧めします(これはY2Kバグで実際に発生しました。より良い実践によって回避できたかもしれません)。
私はC++のバックグラウンドを持っているので、「コンパイラのように考え」ようとする瞬間がまだあります。 whileループはコードをより厳密にする傾向があるため、forループは、配列内のすべての要素を毎回反復することがわかっている場合にのみ考慮されていました。
編集:.Netを使用している場合、またはVMオーバーヘッドといくつかのタイトなループを補うつもりがない場合は、これはやり過ぎだと思います。覚えていると思います。ただし、特定のプラクティスの「理由」は良いことです。
私の理論では、「信号対雑音比」、つまり「問題対ツール比」に似た有用なプログラミングの抽象化があります。問題について考えるのにどれだけの時間を費やすかによって、良さを1次元で測定できます。その解決策は、ツールの使用方法(この場合は言語構文)について考えるのに費やした時間と比較して。
その方法で、私(そしてできれば従う人)が私のコード構造の本質をより迅速かつ正確に理解できるので、より少ない構成をより頻繁に使用しようとします。そして、「forループ」のバリエーションは、他のものが(歪みなしで)使用される可能性がある場合をカバーするのにかなり良い仕事をするので、それらが交換可能であるとき、私はそれらを最初の好みとして使用します。
そして、「for」ループの上部にある1行に、ループルールについて(グロクワイズに)知る必要があるすべてのものがあるのは素晴らしいことです。同じ理由で、テストでは「デフォルト」スイッチを最初に配置する傾向があります。
しかし、一貫性と明快さは、明白な考慮事項です。もちろん、YMMV。
どちらも実際には興味深いものではないと思います。読みやすさを求めている場合は、より高いレベルの構成を探す必要があります。
JSの場合:
if(myArray.some(function(o) { o.name == "baxter" && o.location == "milwaukee" }))
barkTwice();
またはあなた自身のいくつかのユーティリティで
if(myArray.containsMatch({name:"baxter",location:"milwaukee"})
barkTwice();
私の一般的なスタンスは次のとおりです。
ループカウンターがある場合は、for()を使用します(whileループのように)。
休憩はgrokabilityを減らすので、私はwhile
に投票します。
ループが長くなりすぎて、実行する予定のコードを挿入しても、ループにブレークが含まれていないことに気付かない場合があります。
しかし、私はコーディングのモデルを考えさせないように購読しています。
ES6では、非常に簡単になり、breakキーワードを使用する必要がなくなりました。検索関数を使用できます。条件が満たされると、trueを返すことができます。
let barkTwice = () => {
console.log('bark twice');
}
let arr = [{
location: "waukee",
name: "ter"
}, {
location: "milwaukee",
name: "baxter"
},
{
location: "sample",
name: "sampename"
}
];
ここでは、条件を照合し、条件が一致すると、質問に従って関数を呼び出し、trueを返します。それが超えないように。
arr.find(item => {
if (item.location == "milwaukee" && item.name == "baxter") {
barkTwice();
return true
}
});