質問

Go 言語作成者 書く:

Go はアサーションを提供しません。 これらが便利であることは間違いありませんが、私たちの経験では、プログラマは適切なエラー処理とレポートについて考えることを避けるために、それらを杖として使用しています。適切なエラー処理とは、致命的ではないエラーの後でもサーバーがクラッシュせずに動作を継続することを意味します。適切なエラー報告とは、エラーが直接的かつ要点を絞ったものであることを意味し、プログラマが大規模なクラッシュ トレースを解釈する手間を省きます。エラーを見たプログラマがコードに精通していない場合、正確なエラーは特に重要です。

これについてどう思いますか?

役に立ちましたか?

解決

いいえ、何も問題ありません assert 意図どおりに使用する限り。

つまり、通常のエラー処理ではなく、デバッグ中に「起こり得ない」ケースを検出するためのものであると考えられています。

  • 主張する:プログラムのロジック自体の失敗。
  • エラー処理:プログラムのバグによるものではない、誤った入力またはシステム状態。

他のヒント

いや、どちらでもない goto または assert 悪者だ。しかし、どちらも悪用される可能性があります。

Assert は健全性チェック用です。正しくない場合にプログラムを強制終了するもの。検証やエラー処理の代替としてではありません。

その論理によれば、ブレークポイントも悪です。

アサートはデバッグ補助として使用する必要があり、それ以外には使用しないでください。「悪」とは、使ってみることだ その代わり エラー処理の。

アサートは、プログラマが存在してはいけない問題を検出して修正し、仮定が正しいことを確認するのに役立ちます。

これらはエラー処理とは何の関係もありませんが、残念ながら一部のプログラマはそれらをそのように悪用し、「悪」であると宣言します。

私はassertをよく使うのが好きです。初めてアプリケーションを構築するとき (おそらく新しいドメインの場合) に非常に便利だと思います。非常に手の込んだエラー チェック (時期尚早な最適化を検討する) を行う代わりに、私は高速にコードを作成し、多くのアサートを追加します。物事がどのように機能するかをさらに理解した後、エラー処理を改善するために書き直してアサートの一部を削除し、変更します。

アサートのおかげで、プログラムのコーディング/デバッグに費やす時間が大幅に減りました。

また、アサートはプログラムを壊す可能性のある多くのことを考えるのに役立つことにも気づきました。

これらはプログラムのバグを検出するために使用する必要があります。ユーザー入力は悪くありません。

正しく使用すると、次のようになります。 ない 悪。

追加情報として、go は組み込み関数を提供します panic. 。これは代わりに使用できます assert. 。例えば。

if x < 0 {
    panic("x is less than 0");
}

panic スタックトレースを出力するので、何らかの形で次の目的があります。 assert.

これはよく出てきますが、主張の防御を混乱させる問題の 1 つは、主張がしばしば議論のチェックに基づいていることだと思います。そこで、アサーションを使用する場合の別の例を考えてみましょう。

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

時々不正な入力が得られることが予想されるため、入力に例外を使用します。あなたは、アルゴリズムのバグを見つけやすくするためにリストが並べ替えられていると主張しますが、これは定義上、あなたが予期しないことです。アサーションはデバッグ ビルドのみに含まれるため、チェックにコストがかかるとしても、ルーチンの呼び出しごとにチェックを実行しても問題ありません。

実稼働コードの単体テストは依然として必要ですが、これはコードが正しいことを確認するための別の補完的な方法です。単体テストは、ルーチンがインターフェイスに従って動作していることを確認するのに対し、アサーションは、実装が期待どおりに動作していることを確認するためのより詳細な方法です。

アサーションは悪ではありませんが、簡単に悪用される可能性があります。「アサーションは、適切なエラー処理とレポートについて考えることを避けるための杖として使用されることが多い」という意見に私は同意します。これはよく見ました。

個人的には、アサーションを使用するのが好きです。なぜなら、アサーションは、コードを書いているときに行ったかもしれない仮定を文書化するからです。コードの保守中にこれらの前提が崩れた場合、テスト中に問題を検出できる可能性があります。ただし、実稼働ビルドを実行するとき (つまり #ifdefs を使用するとき)、コードからすべてのアサートを削除するようにしています。運用ビルドでアサーションを削除することで、誰かがアサーションを杖として悪用するリスクを排除します。

アサーションには別の問題もあります。アサーションは実行時にのみチェックされます。ただし、実行したいチェックがコンパイル時に実行されている可能性がある場合がよくあります。コンパイル時に問題を検出することが望ましいです。C++ プログラマー向けに、boost はこれを可能にする BOOST_STATIC_ASSERT を提供します。C プログラマ向けのこの記事 ( リンクテキスト ) では、コンパイル時にアサーションを実行するために使用できる手法について説明します。

要約すると、私が従う経験則は次のとおりです。運用ビルドではアサーションを使用しないでください。また、可能であれば、コンパイル時に検証できない (つまり、実行時にチェックする必要がある) ものに対してのみアサーションを使用してください。

私は、適切なエラー報告を考慮せずにアサートを使用したことを認めます。ただし、正しく使用すれば非常に役立つことに変わりはありません。

これらは、「早期クラッシュ」原則に従いたい場合に特に役立ちます。たとえば、参照カウント メカニズムを実装しているとします。コード内の特定の場所では、refcount が 0 または 1 でなければならないことがわかります。また、refcount が間違っている場合、プログラムはすぐにはクラッシュしませんが、次のメッセージ ループ中に、問題が発生した理由を見つけるのが困難になると仮定します。アサートは、エラーの発生源に近い場所でエラーを検出するのに役立ちます。

私は、デバッグとリリースで異なることを行うコードは避けたいと考えています。

ただし、条件に応じてデバッガーをブレークインし、すべてのファイル/行情報、正確な式と正確な値を取得すると便利です。

「デバッグ時にのみ条件を評価する」というアサーションを持つことはパフォーマンスの最適化になる可能性があるため、ユーザーが何をしているのかを知っているプログラムの 0.0001% でのみ役に立ちます。それ以外の場合はすべて、式によってプログラムの状態が実際に変更される可能性があるため、これは有害です。

assert(2 == ShroedingersCat.GetNumEars()); プログラムがデバッグとリリースで異なることを実行するようになります。

私たちは、例外をスローし、デバッグ バージョンとリリース バージョンの両方で実行する一連のアサート マクロを開発しました。例えば、 THROW_UNLESS_EQ(a, 20); ファイル、行、および 実際の値 の、など。これを実行できるのはマクロだけです。デバッガーは、特定の例外タイプの「スロー」でブレークするように構成できます。

私は主張が激しく嫌いです。彼らが悪であるとまでは言いませんが。

基本的に、アサートはチェックされていない例外と同じことを行いますが、唯一の例外は、アサートが (通常は) 最終製品のために保持されるべきではないことです。

システムのデバッグと構築中に自分自身のためのセーフティ ネットを構築する場合、顧客、サポート ヘルプ デスク、または現在構築しているソフトウェアを使用することになる人に対するこのセーフティ ネットをなぜ拒否するのでしょうか。例外は、アサートと例外状況の両方に排他的に使用します。適切な例外階層を作成すると、一方を他方から非常に迅速に識別できるようになります。今回を除いて、アサーションは所定の位置に残り、失敗した場合に失われる可能性がある貴重な情報を提供できます。

したがって、私は Go の作成者がアサートを完全に削除し、プログラマに状況を処理するために例外を使用することを強制したことを完全に理解しています。これには簡単な説明があります。例外は単に仕事のためのより良いメカニズムであるだけです。なぜ古めかしいアサートに固執するのでしょうか?

短い答え:いいえ、アサーションは役に立つと思います

私は最近、コードにいくつかのアサートを追加し始めました。これが私がそれを行っている方法です。

私は頭の中でコードを境界コードと内部コードに分割します。境界コードは、ユーザー入力を処理し、ファイルを読み取り、ネットワークからデータを取得するコードです。このコードでは、入力が有効な場合 (対話型ユーザー入力の場合) にのみ終了するループで入力を要求するか、回復不能なファイル/ネットワーク破損データの場合に例外をスローします。

内部コードはそれ以外のすべてです。たとえば、クラスに変数を設定する関数は次のように定義される可能性があります。

void Class::f (int value) {
    assert (value < end);
    member = value;
}

ネットワークから入力を取得する関数は次のようになります。

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

これにより、2 層のチェックが得られます。実行時にデータが決定されるものはすべて、常に例外または即時エラー処理を受け取ります。ただし、その追加のチェックイン Class::f とともに assert ステートメントは、内部コードが呼び出しを行った場合に意味します。 Class::f, 、まだ正気度チェックがあります。内部コードが有効な引数を渡さない可能性があります (計算した可能性があるため) value いくつかの複雑な一連の関数から)なので、誰が関数を呼び出しているかに関係なく、設定関数にアサーションを記述してそれを文書化するのが好きです。 value 以上であってはなりません end.

これは、私がいくつかの場所で読んだ内容に当てはまります。つまり、正常に機能するプログラムではアサーションに違反することは不可能であるべきですが、それでも可能性がある例外的で誤ったケースには例外が存在する必要があります。理論的にはすべての入力を検証しているため、アサーションがトリガーされることはあり得ません。もしそうなら、私のプログラムは間違っています。

assert これは非常に便利で、問題の最初の兆候でプログラムを停止することで、予期しないエラーが発生したときに後戻りする手間を大幅に節約できます。

その一方で、悪用は非常に簡単です assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

適切な正しいバージョンは次のようになります。

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

それで...長い目で見れば...全体像で...私はそれに同意しなければなりません assert 悪用される可能性があります。いつもそうしてます。

assert タイピングが少ないため、エラー処理に悪用されています。

したがって、言語設計者は、むしろ、より少ない入力で適切なエラー処理を実行できることを理解する必要があります。例外メカニズムが冗長であるためにアサートを除外することは解決策ではありません。ああ、待って、Go にも例外はありません。残念な :)

それを見たとき、作者の頭を殴りたくなった。

私はコード内で常にアサートを使用しており、さらにコードを作成するときに最終的にはすべてアサートを置き換えます。必要なロジックをまだ記述していない場合、プロジェクトが完了に近づくにつれて削除される例外を作成する代わりに、コードが実行されたときにアラートを受け取りたい場合にこれらを使用します。

また、例外は実稼働コードに溶け込みやすくなりますが、これが私は嫌いです。アサーションは、 throw new Exception("Some generic msg or 'pretend i am an assert'");

主張を擁護するこれらの回答に関する私の問題は、通常の回答との違いを明確に指定する人がいないことです。 致命的な誤り, 、およびアサートがサブセットになれない理由 例外. 。さて、そうは言っても、例外がまったくキャッチされなかったらどうなるでしょうか?それは命名法による主張になりますか?そして、なぜ /nothing/ が処理できない例外が発生する可能性があるという制限を言語に課す必要があるのでしょうか?

はい、アサーションは悪です。

多くの場合、これらは適切なエラー処理を使用する必要がある場所で使用されます。最初から適切な運用品質のエラー処理を書くことに慣れてください。

通常、これらは単体テストを作成する際に邪魔になります (テスト ハーネスと対話するカスタム アサーションを作成しない限り)。これは多くの場合、適切なエラー処理が必要な場所で使用されるためです。

ほとんどの場合、それらはリリース ビルドからコンパイルされます。つまり、実際にリリースするコードを実行しているときには、それらの「テスト」は利用できません。マルチスレッドの状況では、最悪の問題がリリース コードにのみ現れることが多いことを考えると、これは悪いことである可能性があります。

場合によっては、それがなければ破綻したデザインの松葉杖となることもあります。つまりコードの設計により、ユーザーは呼び出すべきではない方法でコードを呼び出すことができ、アサートはこれを「防止」します。デザインを修正してください!

このことについては、2005 年にブログに詳しく書きました。 http://www.lenholgate.com/blog/2005/09/assert-is-evil.html

あなたが話しているアサートが、プログラムが吐き出され、その後存在することを意味する場合、アサーションは非常に悪いものである可能性があります。これは彼らがそうだと言っているわけではありません いつも 使い方が間違っていると、非常に簡単に悪用されてしまう構造です。より良い代替手段もたくさんあります。そのようなものは悪と呼ばれるのに適しています。

たとえば、サードパーティのモジュール (または実際のモジュール) は、呼び出し元のプログラムを終了することがほとんどありません。これでは、呼び出し元のプログラマは、その時点でプログラムがどのようなリスクを負うべきかを制御できません。多くの場合、データは非常に重要であるため、破損したデータを保存しておくほうが、データを失うよりも優れています。アサートによりデータが失われる可能性があります。

アサートに代わるいくつかの方法:

  • デバッガを使用すると、
  • コンソール/データベース/その他のロギング
  • 例外
  • 他のタイプのエラー処理

いくつかの参考文献:

Assert を支持する人々でさえ、Assert は開発でのみ使用されるべきだと考えています。 ない 生産中:

この人は、例外がスローされた後も存続する破損したデータがモジュールにある場合にアサートを使用する必要があると述べています。 http://www.advogato.org/article/949.html 。これは確かに合理的な点ですが、外部モジュールは次のようにする必要があります。 一度もない 呼び出し側プログラムにとって破損したデータがどれほど重要であるかを規定します (プログラムを「のために」終了することで)。これに対処する適切な方法は、プログラムが矛盾した状態にある可能性があることを明らかにする例外をスローすることです。そして、優れたプログラムはほとんどがモジュール (メインの実行可能ファイルに小さな接着コードを含む) で構成されているため、アサートはほとんどの場合、間違った行為となります。

悪というほどではなく、一般に逆効果です。永続的なエラーのチェックとデバッグは分離されています。Assert を使用すると、すべてのデバッグは永続的であるべきだと思われ、頻繁に使用すると読みやすさに大きな問題が発生します。永続的なエラー処理は、必要に応じてそれよりも優れている必要がありますが、assert が独自のエラーを引き起こすため、これはかなり疑わしい実践です。

私はassert()を決して使いません。例は通常次のようなものを示します。

int* ptr = new int[10];
assert(ptr);

これは悪いことです、私はこれを決してしません、私のゲームが大量のモンスターを割り当てている場合はどうなりますか?なぜゲームをクラッシュさせる必要があるのですか。代わりに、エラーを適切に処理する必要があるため、次のようなことを実行してください。

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top