web-dev-qa-db-ja.com

悪を主張するのですか?

Go言語作成者 書き込み

Goはアサーションを提供しません。紛れもなく便利ですが、私たちの経験では、プログラマーは適切なエラー処理や報告。適切なエラー処理とは、サーバーがクラッシュするのではなく、致命的ではないエラーが発生しても操作を続行することを意味します。適切なエラー報告とは、エラーが直接的なものであり、プログラマーが大きなクラッシュトレースを解釈するのを防ぐことです。正確なエラーは、エラーを見ているプログラマーがコードに慣れていない場合に特に重要です。

これについてあなたはどう思いますか?

195
Frank

いいえ、意図したとおりに使用する限り、assertに問題はありません。

つまり、通常のエラー処理とは対照的に、デバッグ中に「発生しない」ケースをキャッチするためのものです。

  • アサート:プログラムのロジック自体の障害。
  • エラー処理:プログラムのバグが原因ではない、誤った入力またはシステム状態。
318
caf

いいえ、gotoassertも悪ではありません。しかし、両方とも悪用される可能性があります。

アサートは健全性チェック用です。正しくない場合にプログラムを強制終了する必要があるもの。検証用ではなく、エラー処理の代替としても。

106
gahooa

そのロジックによって、ブレークポイントも悪です。

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

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

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

61
jalf

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

アサートのため、プログラムのコーディング/デバッグに費やす時間が大幅に短縮されます。

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

39
arhuaco

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

正しく使用すると、それらはnot evilです。

30
Alex Budovski

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

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

panicはスタックトレースを出力するため、何らかの方法でassertの目的があります。

30

これはよく起こりますが、アサーションの防御を混乱させる問題の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

入力に例外を使用するのは、時として間違った入力が得られることが予想されるためです。アルゴリズムのバグを見つけるのに役立つようにリストがソートされていると断言しますが、定義上、これは予期していません。このアサーションはデバッグビルドのみに含まれているため、チェックのコストは高くなりますが、ルーチンのすべての呼び出しで実行してもかまいません。

運用コードの単体テストを行う必要がありますが、これはコードが正しいことを確認するための別の補完的な方法です。単体テストでは、ルーチンがインターフェイスに対応していることを確認します。一方、アサーションは、実装が期待どおりに機能していることを確認するためのきめ細かい方法です。

13
jtolle

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

個人的には、アサーションを使用するのが好きです。アサーションは、コードの作成中に行ったかもしれない仮定を文書化するからです。コードの保守中にこれらの仮定が破られた場合、テスト中に問題を検出できます。ただし、実稼働ビルドを実行するときに(つまり#ifdefsを使用して)、コードからすべてのアサートを削除するようにしています。プロダクションビルドのアサーションを取り除くことにより、それらを松葉杖として誤用するリスクを排除します。

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

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

8
figurassa

デバッグとリリースで異なることを行うコードを避けることを好みます。

ただし、条件に応じてデバッガーを中断し、すべてのファイル/行情報を取得すると便利です。また、正確な式と正確な値も役立ちます。

「デバッグでのみ条件を評価する」というアサートを持つことは、パフォーマンスの最適化である可能性があり、そのため、プログラムの0.0001%でのみ有用です。他のすべての場合、式は実際にプログラムの状態を変更する可能性があるため、これは有害です。

assert(2 == ShroedingersCat.GetNumEars());は、プログラムにデバッグとリリースで異なることをさせます。

例外をスローするアサートマクロのセットを開発し、デバッグバージョンとリリースバージョンの両方で実行します。たとえば、THROW_UNLESS_EQ(a, 20);は、ファイル、行、および実際の値の両方を持つwhat()メッセージで例外をスローします。マクロのみがこの機能を備えています。デバッガーは、特定の例外タイプの「スロー」でブレークするように構成できます。

5

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

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

5
StackedCrooked

私は強く主張することを嫌います。私は彼らが悪であると言っている限りでは行きません。

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

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

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

5
Newtopian

簡単な答え:いいえ、アサーションが役立つと思います

3
Brendan

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

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

内部コードは他のすべてです。たとえば、クラスに変数を設定する関数は次のように定義されます

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::fassertステートメントと一緒に、何らかの内部コードがClass::f、私はまだ健全性チェックを持っています。私の内部コードは有効な引数を渡さない可能性があります(いくつかの複雑な一連の関数からvalueを計算した可能性があるため)、設定関数にアサーションを入れて、誰が関数を呼び出しているかに関係なく、 valueend以上であってはなりません。

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

3
David Stone

アサートを防御するこれらの答えの私の問題は、通常の致命的なエラーと何が違うのか、そしてアサートが例外のサブセットになれない理由を明確に指定している人がいないことです。さて、これで、例外がキャッチされない場合はどうなりますか?それは命名法による主張になりますか?そして、/ nothing /が処理できる例外が発生する可能性があるという制限を言語に課したいのはなぜですか?

1
Evan Carroll

あなたが話している主張がプログラムが吐き出して存在することを意味する場合、主張は非常に悪いものになり得ます。これは、それらがalways間違っていると言うことではなく、非常に簡単に誤用される構成体です。また、多くのより良い代替手段があります。そのようなことは悪と呼ばれるための良い候補です。

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

アサートの代替案:

  • デバッガーを使用して、
  • コンソール/データベース/その他のログ
  • 例外
  • 他の種類のエラー処理

いくつかの参照:

主張を主張する人々でさえ、開発でのみ使用し、本番ではnotのみを使用する必要があると考えています。

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

1
hhafez

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が悪用される可能性があることに同意しなければなりません。私はいつもそれをします。

1
Agnel Kurian

はい、主張は悪です。

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

通常、それらは単体テストの作成を妨げます(テストハーネスと対話するカスタムのアサートを作成しない限り)。これは、適切なエラー処理を使用する必要がある場所で使用されるためです。

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

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

これについては、2005年のブログで次のように書いています。 http://www.lenholgate.com/blog/2005/09/assert-is-evil.html

1
Len Holgate

assertは、入力が少ないため、エラー処理のために悪用されています。

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

1
akuhn

私はそれを見たときに著者を頭の中で蹴りたくなりました。

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

例外は、私が嫌いな量産コードにも簡単に溶け込みます。アサートは、throw new Exception("Some generic msg or 'pretend i am an assert'");よりもわかりやすい

1
user34537

私は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.
}
0
Bob

一般的に非生産的なほど悪ではありません。永続的なエラーチェックとデバッグには分離があります。 Assertは、すべてのデバッグが永続的であるべきだと人々に考えさせ、多く使用すると大きな可読性の問題を引き起こします。永続的なエラー処理は、必要な場合よりも優れている必要があり、アサートはそれ自体のエラーを引き起こすため、かなり疑わしいプラクティスです。

0