私は約1年間プロのソフトウェアエンジニアであり、CSの学位を取得しています。私はしばらくの間C++とCのアサーションについて知っていましたが、最近までC#と.NETにアサーションが存在することはまったく知りませんでした。
私たちの製品コードには断定的な主張は一切含まれておらず、私の質問はこれです...
製品コードでアサートを使用する必要がありますか?もしそうなら、いつその使用が最も適切ですか?それはもっと理にかなっていますか
Debug.Assert(val != null);
または
if ( val == null )
throw new exception();
Microsoft .NET 2.0アプリケーションのデバッグ John Robbinsには、アサーションに関する大きなセクションがあります。彼の主なポイントは次のとおりです。
PS:Code Completeが気に入ったら、この本でフォローアップすることをお勧めします。 WinDBGとダンプファイルの使用方法を学習するために購入しましたが、最初の半分には、バグを回避するためのヒントがそろっています。
Debug.Assert()
を、不変条件を確認するために健全性チェックを行うコードのすべての場所に配置します。リリースビルドをコンパイルするとき(つまり、DEBUG
コンパイラ定数なし)、Debug.Assert()
の呼び出しは削除され、パフォーマンスに影響を与えません。
Debug.Assert()
を呼び出す前に例外をスローする必要があります。 assertは、開発中にすべてが期待どおりであることを確認するだけです。
から コード完了
8防御的なプログラミング
8.2アサーション
アサーションとは、開発中に使用されるコード(通常はルーチンまたはマクロ)であり、プログラムが実行中に自身をチェックできるようにします。アサーションが真の場合、すべてが期待どおりに動作していることを意味します。 falseの場合、それはコードで予期しないエラーを検出したことを意味します。たとえば、顧客情報ファイルのレコードが50,000件を超えないとシステムが想定する場合、プログラムにはレコード数が50,000以下であるというアサーションが含まれる場合があります。レコードの数が50,000以下である限り、アサーションはサイレントになります。ただし、50,000件を超えるレコードが検出されると、プログラムにエラーがあると大声で「アサート」します。
アサーションは、大規模で複雑なプログラムや信頼性の高いプログラムで特に役立ちます。プログラマーは、不一致のインターフェイスの仮定、コードの変更時に発生するエラーなどをより迅速にフラッシュできます。
通常、アサーションには2つの引数が必要です。1つは想定される仮定を記述するブール式で、もう1つはそうでない場合に表示するメッセージです。
(…)
通常、実稼働コードでアサーションメッセージをユーザーに見せたくないでしょう。アサーションは主に開発およびメンテナンス中に使用されます。アサーションは通常、開発時にコードにコンパイルされ、本番用のコードからコンパイルされます。開発中、アサーションは矛盾した仮定、予期しない条件、ルーチンに渡された不適切な値などを洗い流します。実稼働中にアサーションがシステムパフォーマンスを低下させないように、コードからコンパイルされます。
FWIW ...私のパブリックメソッドは、メソッドが正しく呼び出されるようにするためにif () { throw; }
パターンを使用する傾向があることがわかりました。私のプライベートメソッドはDebug.Assert()
を使用する傾向があります。
私のプライベートメソッドでは、私がコントロール下にあるという考えですので、間違ったパラメータで自分のプライベートメソッドの1つを呼び出し始めると、私はどこかで自分の仮定を破った-私は決して得てはいけなかったその状態に。本番環境では、これらのプライベートアサーションは、理想的には不必要な作業である必要があります。これは、内部状態を有効かつ一貫性のある状態に保つことになっているためです。実行時にだれでも呼び出すことができるパブリックメソッドに与えられたパラメーターとは対照的です。例外をスローすることで、パラメーターの制約を強制する必要があります。
さらに、実行時に何かが機能しない場合(ネットワークエラー、データアクセスエラー、サードパーティサービスから取得した不正なデータなど)、プライベートメソッドは例外をスローできます。私の主張は、オブジェクトの状態に関する自分自身の内部的な仮定を破っていないことを確認するためだけにあります。
アサートを使用して開発者の前提条件と例外を確認し、環境の前提条件を確認します。
もし私があなただったら:
Debug.Assert(val != null);
if ( val == null )
throw new exception();
または、条件チェックの繰り返しを避けるため
if ( val == null )
{
Debug.Assert(false,"breakpoint if val== null");
throw new exception();
}
実稼働コード(つまり、ビルドのリリース)でアサートが必要な場合は、Debug.Assertの代わりにTrace.Assertを使用できます。
もちろんこれにより、実動実行可能ファイルにオーバーヘッドが追加されます。
また、アプリケーションがユーザーインターフェイスモードで実行されている場合、アサーションダイアログがデフォルトで表示されますが、これはユーザーを少し混乱させる可能性があります。
DefaultTraceListenerを削除すると、この動作をオーバーライドできます。MSDNのTrace.Listenersのドキュメントを参照してください。
要約すれば、
Debug.Assertを自由に使用して、デバッグビルドのバグをキャッチします。
ユーザーインターフェイスモードでTrace.Assertを使用する場合、ユーザーの混乱を避けるために、DefaultTraceListenerを削除することをお勧めします。
テストする条件がアプリで処理できないものである場合は、おそらく実行を継続しないように、例外をスローする方が良いでしょう。ユーザーはアサーションを無視することを選択できることに注意してください。
アサートは、ユーザーエラーではなく、プログラマー(ユーザー)のエラーをキャッチするために使用されます。ユーザーがアサートを起動する可能性がない場合にのみ使用してください。たとえば、APIを記述している場合、APIユーザーが呼び出すことのできるメソッドで引数がnullでないことを確認するためにアサートを使用しないでください。ただし、APIの一部として公開されていないプライベートメソッドで使用して、想定していないときにYOURコードがnull引数を渡さないことをアサートできます。
よくわからないときは、通常、アサートよりも例外を優先します。
略して
Asserts
は、ガードおよび契約ごとの制約の制約をチェックするために使用されます。
Asserts
は、デバッグおよび非実動ビルド専用です。通常、アサートはリリースビルドのコンパイラによって無視されます。Asserts
は、システムの制御下にあるバグ/予期しない状態をチェックできますAsserts
は、ユーザー入力またはビジネスルールの最初の行の検証のためのメカニズムではありませんAsserts
は、notを使用して、予期しない環境条件(コードの制御外)を検出する必要があります。メモリ不足、ネットワーク障害、データベース障害など。まれではありますが、これらの状態は予期されるものです(また、ハードウェア障害やリソース枯渇などの問題をアプリコードで修正することはできません)。通常、例外がスローされます-アプリケーションは修正アクション(データベースまたはネットワーク操作の再試行、キャッシュされたメモリの解放など)を実行するか、例外を処理できない場合は正常に中止できます。Asserts
をキャッチまたは処理しないでください。コードは予期しない領域で動作しています。スタックトレースとクラッシュダンプを使用して、問題の原因を特定できます。アサーションには大きな利点があります:
Debug
ビルドで実行時にチェックされます。...詳細
Debug.Assert
は、プログラムの制御内の残りのコードブロックによって状態に関して想定されている条件を表します。これには、提供されたパラメーターの状態、クラスインスタンスのメンバーの状態、またはメソッド呼び出しからの戻りがその縮小/設計範囲内にあることが含まれます。通常、アサートは、設計されていないバグまたは考慮されていない状態の存在を示すため、すべての必要な情報(スタックトレース、クラッシュダンプなど)でスレッド/プロセス/プログラムをクラッシュさせる必要がありますアサーションの失敗を処理します)。ただし、アサーション自体がバグよりも大きなダメージを与える可能性がある場合を除きます(たとえば、航空管制官は、航空機が潜水艦に乗ったときにYSODを望まないでしょう。生産...)
いつAsserts?
を使用すべきか-関数への入力またはクラスの状態が有効であると想定されるシステム、ライブラリAPI、またはサービスの任意の時点で(たとえば、システムのプレゼンテーション層、ビジネス層およびデータ層のクラスは通常、入力でのヌルチェック、範囲チェック、文字列長チェックなどがすでに行われていることを前提としています)。 -一般的なAssert
チェックには、無効な仮定がnullオブジェクトの逆参照、ゼロ除数、数値または日付の算術オーバーフロー、一般的な帯域外/動作用に設計されていない場合(たとえば、32ビットint人間の年齢をモデル化するために使用されますが、年齢が実際に0〜125の間であることがAssert
に賢明です--100と10 ^ 10の値は設計されていません)。
。Net Code Contracts
。Netスタックでは、 コードコントラクト を使用できます 追加または代替としてDebug.Assert
を使用します。コードコントラクトは、状態チェックをさらに形式化でき、〜コンパイル時(またはIDEでバックグラウンドチェックとして実行される場合はその後すぐ)での前提違反の検出を支援できます。
利用可能な契約による設計(DBC)チェックには以下が含まれます。
Contract.Requires
-契約の前提条件Contract.Ensures
-契約後の条件Invariant
-寿命のすべてのポイントでのオブジェクトの状態に関する仮定を表します。Contract.Assumes
-コントラクト以外の装飾されたメソッドの呼び出しが行われたときに、静的チェッカーを緩和します。私の本にはほとんどありません。ほとんどの場合、すべてが正常かどうかを確認し、そうでない場合はスローします。
私が嫌いなのは、デバッグビルドがリリースビルドと機能的に異なるという事実です。デバッグのアサートが失敗しても、機能がリリースで機能する場合、どのようにそれが意味をなしますか?アサーターが長い間会社を辞めていて、コードのその部分を誰も知らない場合はさらに良いです。それからあなたはそれが本当に問題であるかどうか見るために問題を調査するあなたの時間のいくらかを殺さなければなりません。それが問題であれば、なぜ人はそもそも投げないのでしょうか?
私にとってこれは、Debug.Assertsを使用することで、問題を他の誰かに延期し、自分で問題に対処することを示唆しています。何かがそうであると想定され、それがスローされない場合。
アサーションを最適化する必要があるパフォーマンスクリティカルなシナリオが存在する可能性があると思いますが、それらはそこで役立ちますが、そのようなシナリオにはまだ遭遇していません。
IDesign Standard に従って、
すべての仮定を主張します。平均して、5行ごとにアサーションがあります。
using System.Diagnostics;
object GetObject()
{...}
object someObject = GetObject();
Debug.Assert(someObject != null);
免責事項として、このIRLを実装することは実際的ではないことに言及する必要があります。しかし、これは彼らの標準です。
アサーションは、リリースビルドのチェックを削除する場合にのみ使用してください。デバッグモードでコンパイルしないと、アサーションは起動しません。
Nullのチェックの例を考えると、これが内部専用APIにある場合、アサーションを使用できます。パブリックAPIの場合は、明示的なチェックとスローを必ず使用します。
すべてのアサートは、次のように最適化できるコードでなければなりません。
_Debug.Assert(true);
_
あなたがすでに仮定していることをチェックしているからです。例えば。:
_public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
if(source != null)
using(var en = source.GetEnumerator())
RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
if(source == null)
throw new ArgumentNullException("source");
using(var en = source.GetEnumerator())
{
if(!en.MoveNext())
throw new InvalidOperationException("Empty sequence");
T ret = en.Current;
RunThroughEnumerator(en);
return ret;
}
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
Debug.Assert(en != null);
while(en.MoveNext());
}
_
上記では、nullパラメータに3つの異なるアプローチがあります。最初は、それを許容可能なものとして受け入れます(何もしません)。 2番目は、呼び出しコードが処理するための例外をスローします(または、しない場合、エラーメッセージが表示されます)。 3番目は、それが起こり得ないと仮定し、そうなると断言します。
前者の場合、問題はありません。
2番目のケースでは、呼び出し元のコードに問題があります。nullでGetFirstAndConsume
を呼び出すべきではないため、例外が返されます。
3番目のケースでは、_en != null
_が呼び出される前に既にチェックされているはずなので、このコードには問題があります。または、言い換えると、理論的にDebug.Assert(true)
に最適化できるコードである必要があり、sicne _en != null
_は常にtrue
でなければなりません!
Debug.Assertが正しい選択である場合、さらに4つのケースを追加すると思いました。
1)ここで言及したことはありませんが、自動テスト中にアサートが提供できる追加の概念カバレッジです。簡単な例として:
いくつかの高レベルの呼び出し元が、追加のシナリオを処理するためにコードの範囲を拡張したと考える著者によって変更された場合、理想的には(!)この新しい条件をカバーするユニットテストを作成します。その場合、完全に統合されたコードが正常に機能しているように見える場合があります。
ただし、実際には微妙な欠陥が導入されていますが、テスト結果では検出されていません。この場合、呼び出し先は非決定的となり、happensのみが期待される結果を提供します。または、気付かれていなかった丸め誤差が生じた可能性があります。または、他の場所で同等に相殺されるエラーを引き起こしました。または、要求されたアクセスだけでなく、付与すべきではない追加の特権を付与します。等。
この時点で、ユニットテストによって駆動される新しいケース(またはエッジケース)と組み合わされた呼び出し先に含まれるDebug.Assert()ステートメントは、テスト中に元の作者の仮定が無効にされ、コードが追加のレビューなしでリリースされます。単体テストでのアサートは完璧なパートナーです。
2)さらに、一部のテストは簡単に記述できますが、初期の仮定を考えると高コストで不要です。例えば:
特定の保護されたエントリポイントからのみオブジェクトにアクセスできる場合、すべてのオブジェクトメソッドからネットワーク権利データベースに対して追加のクエリを実行して、呼び出し元にアクセス許可があることを確認する必要がありますか?確かにそうではありません。おそらく、理想的なソリューションにはキャッシングまたはその他の機能拡張が含まれますが、設計には必要ありません。 Debug.Assert()は、オブジェクトが安全でないエントリポイントにアタッチされたときにすぐに表示します。
)次に、場合によってはリリースモードで展開された場合、製品の操作のすべてまたは一部に対して有用な診断操作が行われない場合があります。例えば:
組み込みリアルタイムデバイスであるとします。例外をスローし、不正な形式のパケットが検出されたときに再起動すると、逆効果になります。その代わりに、デバイスは、その出力にノイズをレンダリングするポイントまで、ベストエフォート型の操作から恩恵を受ける場合があります。また、リリースモードで展開した場合、ヒューマンインターフェイス、ロギングデバイス、または人間が物理的にアクセスすることもできず、同じ出力を評価することでエラーを認識できます。この場合、リベラルなアサーションとリリース前の徹底的なテストは、例外よりも価値があります。
4)最後に、呼び出し先が非常に信頼できると認識されているために、一部のテストは不要です。ほとんどの場合、コードの再利用性が高いほど、コードの信頼性を高めるための努力が注がれています。したがって、呼び出し側からの予期しないパラメーターについては例外ですが、呼び出し側からの予期しない結果についてはアサートするのが一般的です。例えば:
コアString.Find
操作が、検索条件が見つからないときに-1
を返すことを示す場合、3つではなく1つの操作を安全に実行できる場合があります。ただし、実際に-2
が返された場合、適切な対応策がない可能性があります。単純な計算を-1
値に対して個別にテストする計算に置き換えることは役に立ちません。また、ほとんどのリリース環境では、コアライブラリが期待どおりに動作することを確認するテストでコードを散らかすのは無理です。この場合、アサートが理想的です。
アサーションをオンのままにする
コンパイラーと言語環境を書く人々によって公布されたアサーションについての一般的な誤解があります。次のようになります。
アサーションは、コードにオーバーヘッドを追加します。彼らは決して起こるべきでないことをチェックするため、コードのバグによってのみトリガーされます。コードがテストされて出荷されると、コードは不要になります。コードをより速く実行するには、オフにする必要があります。アサーションはデバッグ機能です。
ここには、2つの特許的に間違った仮定があります。最初に、彼らはテストがすべてのバグを見つけると仮定します。実際には、複雑なプログラムでは、コードが実行される置換のごくわずかな割合でさえテストすることはほとんどありません(Ruthless Testingを参照)。
第二に、楽観主義者はあなたのプログラムが危険な世界で実行されることを忘れています。テスト中、ラットはおそらく通信ケーブルをかじりません。ゲームをプレイしている人はメモリを使い果たしず、ログファイルはハードドライブをいっぱいにしません。これらのことは、プログラムが実稼働環境で実行されるときに発生する可能性があります。最初の防衛線はエラーの可能性をチェックし、2番目の防衛線はアサーションを使用して見逃したエラーを検出しようとします。
プログラムを本番環境に配信するときにアサーションをオフにすることは、実際にはネットを介さずにハイワイヤをクロスするようなものです。劇的な価値がありますが、生命保険に加入するのは難しいです。
パフォーマンスの問題がある場合でも、実際にヒットするアサーションのみをオフにします。
常に2番目のアプローチを使用する必要があります(例外をスロー)。
また、本番環境(およびリリースビルド)の場合は、無効な値を操作して顧客のデータを破壊するよりも例外をスローする(そして最悪の場合アプリをクラッシュさせる)方が良いでしょうドルの)。
ここで答えを読みましたが、重要な区別を追加する必要があると考えました。アサートを使用する方法は大きく2つあります。 1つは、プログラムが続行できる場合の条件付きブレークポイントのような、「これは実際には発生しないはずなので、何をすべきかを判断できるようにしてくれれば」という一時的な開発者のショートカットです。もう1つは、コードに有効なプログラムの状態に関する仮定を置く方法です。
最初のケースでは、アサーションは最終的なコードにある必要さえありません。開発中にDebug.Assert
を使用する必要があり、不要になった場合は削除できます。それらを残したい場合、またはそれらを削除するのを忘れた場合、それらはリリースのコンパイルに影響を与えないため、問題ありません。
しかし、2番目のケースでは、アサーションはコードの一部です。まあ、彼らはあなたの仮定が真実であると断言し、またそれらを文書化します。その場合、本当にコードに残しておく必要があります。プログラムが無効な状態にある場合、続行を許可しないでください。パフォーマンスに打撃を与える余裕がない場合は、C#を使用しません。一方では、デバッガーが発生した場合にアタッチできると便利です。一方、ユーザーにスタックトレースが表示されることは望ましくありませんし、おそらくもっと重要なのは、ユーザーがそれを無視できないようにすることです。また、サービス内にある場合は常に無視されます。したがって、実稼働環境では、正しい動作は例外をスローし、プログラムの通常の例外処理を使用することです。これにより、ユーザーにNiceメッセージが表示され、詳細が記録される場合があります。
Trace.Assert
には、これを達成するための完璧な方法があります。本番環境では削除されず、app.configを使用して異なるリスナーで構成できます。したがって、開発ではデフォルトのハンドラーで問題ありません。本番環境では、例外をスローして本番環境設定ファイルでアクティブ化する以下のような簡単なTraceListenerを作成できます。
using System.Diagnostics;
public class ExceptionTraceListener : DefaultTraceListener
{
[DebuggerStepThrough]
public override void Fail(string message, string detailMessage)
{
throw new AssertException(message);
}
}
public class AssertException : Exception
{
public AssertException(string message) : base(message) { }
}
そして、実動構成ファイルで:
<system.diagnostics>
<trace>
<listeners>
<remove name="Default"/>
<add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
</listeners>
</trace>
</system.diagnostics>
Debug.Assertを使用して、プログラムの論理エラーをテストする必要があります。コンパイラーは構文エラーのみを通知できます。したがって、Assertステートメントを明確に使用して、論理エラーをテストする必要があります。たとえば、青のBMWだけが15%の割引を受けるべき車を販売するプログラムをテストするとします。コンパイラーは、プログラムがこれを実行する上で論理的に正しいかどうかについては何も伝えることができませんが、アサートステートメントはできます。