アサート悪ですか?[閉まっている]


199

Go言語の作成者は書きます

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

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


4
正接:囲碁は異常に意見が分かれた言語です。これは必ずしも悪いことではありません。しかし、それはあなたがより大きな粒の塩でその意見を取るべきであることを意味します。また、同意しない場合は、言語を使用するときに歯を食いしばるようになります。現実にもかかわらずGoがどのようにその意見に固執するかを示す証拠として、2つのコレクションが等しいかどうかを判断するために、反射の魔法に頼る必要があることを考慮してください。
allyourcode

@allyourcodeを参照している場合reflect.DeepEqual、確かにそれは必要ありません。便利ですが、パフォーマンスが犠牲になります(単体テストは良いユースケースです)。それ以外の場合は、あまり問題なく、「コレクション」に適切な等価チェックを実装できます。
Igor Dubinskiy

1
いいえ、それは私が話していることではありません。反射せずにslice1 == slice2のようなものはありません。他のすべての言語には、この超基本的な操作と同等の機能があります。囲碁がしない唯一の理由は偏見です。
allyourcode

forGoのループを使用して(Cのように)、反射なしで2つのスライスを比較できます。考えポインタと構造体が関与しているとき、比較は複雑になるものの、一般的なスライスの操作を持っていることは本当に素晴らしいこと。
kbolino 2018年

回答:


321

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

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

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

109

いいえ、どちらgotoassert悪ではありません。しかし、どちらも誤用される可能性があります。

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


goto賢く使う方法?
ar2015

1
@ ar2015 goto純粋に宗教的な理由で回避することを推奨する不合理に不自然なパターンの1つを見つけて、gotoあなたがしていることを難読化するのではなく、単に使用してください。言い換えるgotoと、本当に必要であることが証明でき、唯一の選択肢が、最終的に同じことを行う無意味な足場を実行することですが、後藤警察を変更せずに...を使用するだけgotoです。もちろん、これの前提条件は、「本当に必要であることが証明できる場合」というビットですgoto。多くの場合、人々はそうではありません。それでも、それが本質的に悪いことだとは限りません。
underscore_d

2
gotoLinuxカーネルでコードのクリーンアップに使用されます
malat

61

その論理では、ブレークポイントも悪です。

アサートはデバッグの補助としてのみ使用してください。「悪」とは、エラー処理の代わりにそれらを使用しようとするときです。

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

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


40

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

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

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


31

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

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

panicスタックトレースを出力するため、何らかの目的でを使用しassertます。


30

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

正しく使用すれば、害はありません


13

これは非常に多く発生しますが、アサーションの防御を混乱させる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

時々悪い入力を受け取ると予想するので、入力に例外を使用します。リストは、アルゴリズムのバグを見つけるのに役立つようにソートされていると主張しますが、これは当然のことながら予期しないものです。アサーションはデバッグビルドにのみ存在するため、チェックにコストがかかりますが、ルーチンを呼び出すたびにチェックを実行してもかまいません。

プロダクションコードを単体テストする必要がありますが、これは、コードが正しいことを確認するための別の補完的な方法です。ユニットテストは、ルーチンがそのインターフェースに準拠していることを確認しますが、アサーションは、実装が期待どおりに動作していることを確認するためのきめ細かい方法です。


8

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

個人的には、アサーションを使用するのが好きです。アサーションは、コードの記述中に行った可能性のある仮定を文書化するためです。コードのメンテナンス中にこれらの前提が破られた場合、テスト中に問題を検出できます。ただし、本番ビルド(つまり、#ifdefsを使用)を実行するときは、コードからすべてのアサートを削除するようにします。プロダクションビルドでアサーションを削除することにより、それらを松葉杖として誤用するリスクを排除します。

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

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


5

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

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


5

デバッグとリリースで異なることをするコードは避けたい。

条件に基づいてデバッガーを中断し、すべてのファイル/行情報を取得することは、正確な式と正確な値も役立ちます。

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

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

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


4
引数で使用される統計の90%は誤りです。
ジョアン・ポルテラ

5

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

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

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

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


Goには例外はありません。例外ではなくアサートを使用する通常の理由は、パフォーマンス上の理由からデプロイメントでアサートを除外したいためです。アサートは古風ではありません。耳障りになって申し訳ありませんが、この回答は元の質問にリモートで役立つわけではなく、正解でもありません。
Nir Friedman、


3

私は最近、コードにいくつかのアサートを追加し始めました。

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

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

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

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


2

あなたが話しているアサートがプログラムが嘔吐し、その後存在することを意味する場合、アサーションは非常に悪い可能性があります。これは、それらが常に間違った使用法であると言っているのではなく、非常に簡単に誤用されている構造です。彼らはまた、多くのより良い選択肢があります。そのようなものは悪と呼ばれるための良い候補です。

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

アサートのいくつかの代替:

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

いくつかの参照:

主張することを支持する人々でさえ、彼らは本番ではなく、開発でのみ使用されるべきであると考えています:

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


1

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

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

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


1
悪くない:-)例外かどうか、主張かどうか、囲碁ファンはコードがいかに短いかについてまだ話している。
Moshe Revah 2012年

1

それを見たとき、私は著者を頭で蹴ったような気がしました。

私はコードで常にアサートを使用し、さらにコードを書くときに最終的にそれらをすべて置き換えます。必要なロジックを記述していないときに使用し、コードが実行されたときに警告を表示したい場合は、プロジェクトが完了に近づくと削除される例外を記述しません。

例外は、私が嫌いな量産コードとより簡単に調和します。アサートは、throw new Exception("Some generic msg or 'pretend i am an assert'");


1

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


私の答えを見れば。私が使用するのは、デバッグに使用する「例外」(アサート)と、保持する例外を区別することです。それらを取り除く必要があるのはなぜですか?彼らが働かなければそれは完全ではないからです。例は、3つのケースを処理し、4番目がtodoである場合です。私は簡単にアサートを検索してコードでそれらを見つけ、その不完全な部分を知っているのではなく、誤って(別のプログラマーが)キャッチしたり、例外であるか、コードで解決する必要があるロジックチェックであるかを判断するのが難しい例外を使用します。

私の目には、これは「封印された」クラスと同じレベルで、同じ理由で、あまりお勧めできません。保持したい例外が、まだ知らないコードの使用に受け入れられると想定しています。すべての例外は同じチャネルを通過し、ユーザーがそれらをキャッチしたくない場合は、キャッチしないことを選択できます。もしそうなら、彼は能力も持っているべきです。いずれにせよ、あなたは単に仮定をしている、またはアサーションのような概念であなたの実践を押し進めているだけです。
エヴァンキャロル

サンプルシナリオが最適であると判断しました。簡単なものです。int func(int i){if(i> = 0){console.write( "The number is positive {0}"、i); } else {assert(false); // to lazy to do negatives ATMs} return i * 2; <-アサートなしでこれを行うにはどうすればよいですか、例外は本当に優れていますか?そして、このコードはリリース前に実装されます。

確かに例外はより良いです、私がユーザー入力を取りfunc()、負の数で電話するとします。さて、あなたの主張で突然あなたはカーペットを私の下から引き出し、回復のチャンスを与えずに、私が要求していることはできないことを礼儀正しく教えました。プログラムを誤動作させて引用を発行したと非難することには何も問題はありません。問題は、法律を施行し、重罪犯を判決する行為をぼかしていることです。
Evan Carroll、

つまり、ユーザーが実行したいことは実行できないと言うべきです。アプリはエラーメッセージで終了する必要があり、デバッグしている人はだれでもすべきことを覚えているはずですが、できません。あなたはそれが処理されることを望んでいません。あなたは解雇を望んでおり、そのケースを処理しなかったことを思い出させます。コード内のアサートを検索するだけなので、どのケースが残っているかを簡単に確認できます。コードは、プロダクションの準備ができたらエラーを処理できるはずなので、エラーの処理は間違っています。プログラマがそれをキャッチして他のことを行えるようにするのは間違っています。

1

はい、主張は悪です。

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

通常、それらは単体テストの作成を妨げます(テストハーネスと相互作用するカスタムアサートを作成する場合を除く)。これは多くの場合、適切なエラー処理を使用する必要がある場合に使用されるためです。

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

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

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


0

一般的に逆効果であるほどの悪ではありません。永続的なエラーチェックとデバッグの間には分離があります。アサートは、すべてのデバッグが永続的である必要があると人々に思わせ、頻繁に使用すると大きな可読性の問題を引き起こします。永続的なエラー処理は、必要な場合よりも優れている必要があり、assertはそれ自体のエラーを引き起こすため、かなり疑わしい方法です。


5
assertは、関数の上部で前提条件を宣言するのに適しています。明確に記述されている場合、関数のドキュメントの一部として機能します。
Peter Cordes、

0

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

14
new戻ることはnullptrありません。スローします。
David Stone

そのためにstd :: nothrowを使用できることに注意してください。
マルカンド、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.