プログラミング言語の設計における基本的な間違いについて

ある日、プログラミング言語の設計で最も費用のかかる間違いは、Cの行の終わりをNULLバイトで判断することであるという記事に出会いました。 この記事をHabréで翻訳するためのオプションの1つ(ただし、私は別の記事を読みます)。 この記事は私を少し驚かせました。 まず、あたかもメモリの各ビットを保存したかのように、各行に別の2〜4バイトを割り当ててサイズを保存することができました。 第二に、このソリューションは、プログラマにとって特に壊滅的な結果をもたらしません。 このテーマで発生する可能性のある2つのエラーが発生する可能性があります。文字列にメモリを割り当てるのは正しくありません(NULLの下の場所を忘れてください)。 コンパイラはすでに最初のエラーについて警告しています。ライブラリ関数を使用すると、2番目のエラーを回避できます。 すべてのトラブル。



C言語の設計(そしてC ++)の時代からのはるかに大きな問題は、私にとっては別のように思えます-forステートメント。 明らかな無害性のために、それは潜在的なエラーと問題の単なる倉庫です。



その古典的なアプリケーションを思い出しましょう:



for(int i = 0; i <vec.size(); i ++)

{...}


ここで何が間違っているのでしょうか?



for ( int i = 0; i < vec.size(); i++)







intを使用した例は最初のページの教科書で最もよく使用されるという事実にもかかわらず、intの使用はほとんどの場合正しくありません。 主に配列\ベクトル\リストを調べます。 つまり まず、符号なしの型が必要です。次に、使用するコレクションの最大サイズに対応するデータ型が必要です。 つまり 書くのは正しいでしょう



 std::vector<int>::size_type
      
      





教えてください、あなたはよくこれを書きますか? ここにある。 とても恐ろしいように見えるので、あちこちにそのように書く意志のある人はほとんどいません。 その結果、数百万の誤った記述サイクルがあります。 プログラミング言語の設計に間違いがなければ、これは何ですか?



2. for (int i = 0; i < vec.size(); i++)





すべてのプログラマーは、変数に正しく名前を付けるように教えられています。 「a、b、temp、var、val、abra_kadabra」のような名前の場合、若い後輩にペアで、よく、または先輩の同僚に手を差し伸べます。 ただし、例外があります。 「まあ、ループ内のカウンターなら、iまたはjだけで構いません。」 Br-rr やめて! つまり、すべてのケースで変数に正しい名前を付ける必要があります...これらのケースを除いて、何らかの理由で変数に明確な名前が必要でなく、理解できない文字を1つ書くことができますか? これがなぜそうなったのですか? プログラマに変数に「currentRowIndex」という名前を付けさせると、forループで3回記述する必要があるためです。



 for (int currentRowIndex = 0; currentRowIndex < vec.size(); currentRowIndex++)
      
      





その結果、文字列の長さが37文字から79文字に増え、読み取りまたは書き込みが不便になります。 だから私はiと書く。 これは、内部forループで既にjを使用しているという事実につながります。一部のFloyd-Worshellアルゴリズムでは、 Wikipediaはサイクルの3番目のレベルで変数kを使用することを推奨しています。 記述されたコードの明らかな非自明性に加えて、ここではコピーアンドペーストエラーもあります。 最初にどこでもi変数とj変数を混同することなく、ある種の行列乗算を記述します。コード内の1つの場所は列を意味し、別の場所は行列の行を意味します。



ループの設計が貧弱なため、これに耐えています。



for (int i = 0 ; i < vec.size(); i++)





forループの問題は、原則として、ゼロ要素で表示を開始する必要があることです。 最初、2番目、先に見つかった、最後に見つかった、キャッシュされているなど、必要な場合を除きます。 プログラマーのぎゅうぎゅうな手は習慣的にcopy-paste = 0を書き、その後、kuzkinの母親のデバッグと記憶は、希望するオプションのそのようななじみ深い= 0を修正するために必要です。 欠点はないと言いますが、不注意なプログラマーはいますか? 私は同意しません。 同じプログラマーにdo \ whileまたはwhileを使用して同じコードを書くように依頼した場合、彼はエラーなしで初めてそれを書きます。 この場合、目の前に退屈なパターンはなく、do \ whileまたはwhileサイクルは非常にユニークであるため、プログラマーは毎回、サイクルの始まりと停止基準を考えます。 forループの設計では、この考える必要性は時々不必要に思えます。そのため、ほとんどの場合無視されます。



4. for ( int i = 0 ; i < vec.size(); i++)





forループの便利な機能は、変数iがループのスコープ内で作成され、終了すると破棄されることです。 これは一般的には良いことであり、メモリを節約したり、RAIIを使用したりすることもできます。 しかし、ループ内で何かを見つけて停止する必要がある場合、これはまったく機能しません。 停止できますが、見つかった要素のインデックスを返すには、追加の変数が必要です。 または、ループの前のiの定義。 余分な変数は、何も見つからない場合の不当なコストです。 ループの前にiを宣言すると、コードの一貫性が崩れます。最初のforセクションは空のままです。これにより、読者は上記のコードを熟考し、これがエラーであるか、必要であるかを理解しようとします。



一見ピッキングのように見えるかもしれませんが、私にとってはforループには、早期停止の場合にインデックス値を返す機能がありません。 ある種のポストブロックのように見えるかもしれませんが(whileループの場合と同様)、繰り返しカウンターの最後の値が利用できます。 または、ブレークが呼び出された時点で変数iの最後の値を返すGetLastError()の精神の関数。



5. for (int i = 0; i < vec.size() ; i++)





forステートメントの2番目のブロックで条件をチェックすることは論理的に見えません。ループの各反復(最初を除く)で、カウンターの増分(3番目のブロック)が最初に実行され、次に条件チェック(2番目のブロック)が実行されるためです。 条件チェックは2番目のブロックにあり、カウンターiが初期化された直後のループの最初の反復中に実行されるという事実を強調しています-この説明でのみ、すべてが多少論理的に見えます。 その結果、最初の繰り返しに構文が集中し、その後のすべての処理(通常は何倍も多い)で発生していることを反映していないループができました。 それがforステートメントの設計です。



6. for (int i = 0; i < vec.size() ; i++)





「少ない」 それとも同等ではありませんか? または「等しくない」? 「.size()」の前または「.size()-1」の前? はい、これらの質問に対する答えを見つけるのは簡単ですが、なぜ、これらの質問は一般的に尋ねられるのでしょうか? そして、これが間違いではないことを仲間のプログラマーに知らせるために非標準バージョンを書く必要があるまれなケースで、それはまさにあなたが書くつもりだったものですか?



for (int i = 0; i < vec .size(); i++)





これは一般的に、どのコレクションを取得するかをサイクルに伝える唯一の場所です。 そして、それでも、サイズのコンテキストでのみ言及します。 ここで、彼らは言う、非常に多くの措置を講じる必要がある。 同時に、ループ自体で、vec2ベクトルに沿って歩くことができます。もちろん、平均の法則によれば、デバッグではまったく同じ長さであり、リリースでは必然的に異なります。そのため、このバグはその瞬間よりもずっと後に発見されますが、それをする必要があったとき。



8. for (int i = 0; i < vec .size() ; i++)





コレクション内の要素数の指定を人々が思い付かないのはどうしてでしょう! はい、サイズ()のSTLは非常に一貫していますが、他のライブラリはlength()、count()、number()、およびtotalSize()を使用します。これらはすべて、CamelCaseおよびunder_score書き込みスタイルのさまざまなバリエーションです。 その結果、「コレクションサイズ」の概念を使用するには、この特定のコレクションの実装に関するforループの知識を提供する必要があります。 コレクションを別のコレクションに変更する場合は、すべてを書き換えてください。



for (int i = 0; i < vec.size(); i++ )





もちろん、ここでは、インクリメントの前置および後置形式に関するホリバーがあります。 同僚と戦い、言語標準を覚えて現代のコンパイラによるコード最適化の結果を研究するのに半日費やしたいなら、古き良きスレッド「++ i vs i ++」へようこそ。 あなたはそれについて話すことができる多くの異なる場所があります(そしてHabrはそれらの1つです)が、すべての最初のプロジェクトで数千人が使用するforステートメントの3番目のブロックを作ることが本当に必要でしたか?



10. for (;;)





ここには、「はい、これは無限のサイクルを整理する最も効果的な方法です!」という古典的な論争もあります。 ホリバーの神へのより多くのホリバー!



11. for (int i = 0; i++; i < vec.size() )





このコードはコンパイルされます。 一部のコンパイラは警告を出しますが、誰もエラーをスローしません。 場所に混在している2番目と3番目のブロックは、目立ったものではありません。 forステートメントはある種のハードウェアコネクタのように見えます。プラグは上下に挿入できますが、1つの場合にのみ機能し、2番目の場合は燃えます。



プログラミング言語のさらなる進化の重要な部分は、修正する試みのように見えます。 高レベルの言語(およびその後C ++)でfor_each演算子が導入されました。 標準ライブラリには、コレクションを検索および変更するためのアルゴリズムが補充されています。 C ++はautoキーワードを導入しました-基本的にワイルドを書く必要性を取り除くため
 std::vector<int>::iterator
      
      



すべてのサイクルで。 関数型言語は、ループを再帰に置き換えることを提案しています。 動的言語は、最初のブロックの型宣言を放棄することを提案しました。 誰もが何らかの形で状況を修正しようとしましたが、すぐに設計を改善できました。



All Articles