あまりにも多くのアサートを書くことは可能ですか?


33

私は執筆の大ファンです assert開発中に発生する可能性のあるケースをキャッチする方法としてC ++コードでチェック。プログラムのロジックバグが原因で発生する可能性があります。これは一般的に良い習慣です。

しかし、私が書いたいくつかの関数(複雑なクラスの一部である)には5つ以上のアサートがあることに気づきました。それぞれが関数の事前条件と事後条件について考える必要があり、それらは本当にバグをキャッチするのに役立つので、それはまだ素晴らしいと思います。ただし、多数のチェックが必要な場合にロジックエラーをキャッチするためのより良いパラダイムがあるかどうかを尋ねるために、これをそこに置きたかっただけです。

Emacsのコメント:Emacsが私の選択のIDEであるため、アサートステートメントを少しグレー表示にして、提供できる混乱を軽減します。.emacsファイルに追加するものは次のとおりです。

; gray out the "assert(...)" wrapper
(add-hook 'c-mode-common-hook
  (lambda () (font-lock-add-keywords nil
    '(("\\<\\(assert\(.*\);\\)" 1 '(:foreground "#444444") t)))))

; gray out the stuff inside parenthesis with a slightly lighter color
(add-hook 'c-mode-common-hook
  (lambda () (font-lock-add-keywords nil
    '(("\\<assert\\(\(.*\);\\)" 1 '(:foreground "#666666") t)))))

3
これは私の頭をよぎる質問であることを認めなければなりません。これについて他の人の意見を聞きたい。
キャプテンセンシブル

回答:


45

誰かがより多くのアサートを記述した場合、より速く解決されるであろう数百のバグを見てきましたが、より少ない記述でより速く解決される単一のバグではありません。

[C]読みやすさと保守性の観点から、[主張が多すぎる]プログラミングの悪い習慣になる可能性があります[?]

おそらく、読みやすさが問題になる可能性があります-良い主張を書く人も読みやすいコードを書くことは私の経験ですが。そして、関数の始まりがアサートのブロックで始まることを見て、引数がゴミではないことを確認することは決してありません-それの後に空白行を置くだけです。

また、私の経験では、単体テストと同様に、アサートによって保守性は常に向上します。アサートは、コードが意図された方法で使用されていることの健全性チェックを提供します。


1
いい答えだ。また、Emacsで読みやすさを改善する方法の質問に説明を追加しました。
アランチューリング

2
「良い主張を書く人は、読みやすいコードも書くというのが私の経験でした」<<すばらしい点。コードを読み取り可能にすることは、個々のプログラマーが行うことであり、それは彼または彼女が使用する技術であり、使用することは許可されません。良いテクニックは間違った人には読めなくなり、悪いテクニックと思われるものでさえも、抽象化とコメントの適切な使用によって完全に明確になり、エレガントにさえなります。
グレッグジャクソン

誤ったアサーションが原因でアプリケーションがクラッシュすることがあります。だから私は、誰か(自分自身)がより少ない主張を書いたなら存在しなかったであろうバグを見てきました。
-CodesInChaos

@CodesInChaos間違いなく、タイプミスはさておき、これは問題の定式化におけるエラーを指します-つまり、バグは設計にあったため、アサーションと(他の)コード間の不一致です。
ローレンス

12

あまりにも多くのアサートを書くことは可能ですか?

まあ、もちろんです。[ここで不快な例を想像してください。]ただし、以下に詳述するガイドラインを適用すれば、実際にその限界を押し進めるのに苦労することはないはずです。私もアサーションの大ファンであり、これらの原則に従ってアサーションを使用しています。このアドバイスの多くはアサーションに特別なものではなく、一般的な優れたエンジニアリングの実践のみがアサーションに適用されます。

ランタイムとバイナリフットプリントのオーバーヘッドを念頭に置いてください

アサーションは素晴らしいですが、もしそれがあなたのプログラムを容認できないほど遅くするなら、それは非常に迷惑になるか、遅かれ早かれそれらをオフにします。

アサーションのコストを、それが含まれる関数のコストと比較して測定するのが好きです。次の2つの例を検討してください。

// Precondition:  queue is not empty
// Invariant:     queue is sorted
template <typename T>
const T&
sorted_queue<T>::max() const noexcept
{
  assert(!this->data_.empty());
  assert(std::is_sorted(std::cbegin(this->data_), std::cend(this->data_)));
  return this->data_.back();
}

関数自体はO(1)操作ですが、アサーションは On)オーバーヘッドをます。非常に特別な状況でない限り、そのようなチェックをアクティブにしたいとは思わない。

同様のアサーションを持つ別の関数を次に示します。

// Requirement:   op : T -> T is monotonic [ie x <= y implies op(x) <= op(y)]
// Invariant:     queue is sorted
// Postcondition: each item x in the queue is replaced by op(x)
template <typename T>
template <typename FuncT>
void
sorted_queue<T>::apply_monotonic_function(FuncT&& op)
{
  assert(std::is_sorted(std::cbegin(this->data_), std::cend(this->data_)));
  std::transform(std::cbegin(this->data_), std::cend(this->data_),
                 std::begin(this->data_), std::forward<FuncT>(op));
  assert(std::is_sorted(std::cbegin(this->data_), std::cend(this->data_)));
}

関数自体はOn)操作であるため、アサーションに追加のOn)オーバーヘッドを追加してもそれほど害はありません。関数を小さな(この場合、おそらく3未満)の一定の係数で遅くすることは、デバッグビルドでは通常は余裕がありますが、リリースビルドではできない場合があります。

次に、この例を検討してください。

// Precondition:  queue is not empty
// Invariant:     queue is sorted
// Postcondition: last element is removed from queue
template <typename T>
void
sorted_queue<T>::pop_back() noexcept
{
  assert(!this->data_.empty());
  return this->data_.pop_back();
}

多くの人々は、おそらくこれではるかに快適になりますがO 2と比べて(1)アサーションOnは前の例)アサーションですが、私の意見では道徳的に同等です。それぞれが、関数自体の複雑さの順序にオーバーヘッドを追加します。

最後に、含まれる関数の複雑さによって支配される「本当に安い」アサーションがあります。

// Requirement:   cmp : T x T -> bool is a strict weak ordering
// Precondition:  queue is not empty
// Postcondition: if x is returned, then there is no y in the queue
//                such that cmp(x, y)
template <typename T>
template <typename CmpT>
const T&
sorted_queue<T>::max(CmpT&& cmp) const
{
  assert(!this->data_.empty());
  const auto pos = std::max_element(std::cbegin(this->data_),
                                    std::cend(this->data_),
                                    std::forward<CmpT>(cmp));
  assert(pos != std::cend(this->data_));
  return *pos;
}

ここでは、On)関数に2つのO(1)アサーションがあります。リリースビルドでもこのオーバーヘッドを維持することはおそらく問題になりません。

ただし、漸近的な複雑さが常に適切な推定値を与えるとは限らないことに注意してください。実際には、「Big- -O」無視しているためです。

そこで、さまざまなシナリオを特定しましたが、それらについて何ができるでしょうか?(おそらくあまりにも)簡単なアプローチは、「含まれている機能を支配するアサーションを使用しない」などのルールに従うことです。一部のプロジェクトでは機能するかもしれませんが、他のプロジェクトではより差別化されたアプローチが必要になる場合があります。これは、ケースごとに異なるアサーションマクロを使用することで実行できます。

#define MY_ASSERT_IMPL(COST, CONDITION)                                       \
  (                                                                           \
    ( ((COST) <= (MY_ASSERT_COST_LIMIT)) && !(CONDITION) )                    \
      ? ::my::assertion_failed(__FILE__, __LINE__, __FUNCTION__, # CONDITION) \
      : (void) 0                                                              \
  )

#define MY_ASSERT_LOW(CONDITION)                                              \
  MY_ASSERT_IMPL(MY_ASSERT_COST_LOW, CONDITION)

#define MY_ASSERT_MEDIUM(CONDITION)                                           \
  MY_ASSERT_IMPL(MY_ASSERT_COST_MEDIUM, CONDITION)

#define MY_ASSERT_HIGH(CONDITION)                                             \
  MY_ASSERT_IMPL(MY_ASSERT_COST_HIGH, CONDITION)

#define MY_ASSERT_COST_NONE    0
#define MY_ASSERT_COST_LOW     1
#define MY_ASSERT_COST_MEDIUM  2
#define MY_ASSERT_COST_HIGH    3
#define MY_ASSERT_COST_ALL    10

#ifndef MY_ASSERT_COST_LIMIT
#  define MY_ASSERT_COST_LIMIT MY_ASSERT_COST_MEDIUM
#endif

namespace my
{

  [[noreturn]] extern void
  assertion_failed(const char * filename, int line, const char * function,
                   const char * message) noexcept;

}

これで、3つのマクロを使用することができMY_ASSERT_LOWMY_ASSERT_MEDIUMそしてMY_ASSERT_HIGH代わりに、標準ライブラリの「フリーサイズ」assertによって支配さもなく、それぞれ自分含む関数の複雑さを支配し、支配どちらも、によって支配されているアサーションのマクロを。ソフトウェアをビルドするときに、プリプロセッサシンボルMY_ASSERT_COST_LIMITを事前定義して、実行可能ファイルに含めるアサーションの種類を選択できます。定数MY_ASSERT_COST_NONEおよびMY_ASSERT_COST_ALLは、どのアサートマクロにも対応せずMY_ASSERT_COST_LIMIT、すべてのアサーションをオフまたはオンにするための値として使用することを意図しています。

ここでは、優れたコンパイラは以下のコードを生成しないという仮定に依存しています。

if (false_constant_expression && run_time_expression) { /* ... */ }

そして変換

if (true_constant_expression && run_time_expression) { /* ... */ }

if (run_time_expression) { /* ... */ }

これは最近の安全な仮定だと思います。

上記のコードを微調整しようとしている場合は、__attribute__ ((cold))on my::assertion_failedまたは__builtin_expect(…, false)on などのコンパイラ固有の注釈を検討し!(CONDITION)て、渡されたアサーションのオーバーヘッドを削減してください。リリースビルドでは、関数呼び出しを、診断メッセージを失うという不便さでフットプリントを減らすmy::assertion_failedようなものに置き換えることも検討でき__builtin_trapます。

これらの種類の最適化は、すべてのメッセージ文字列を組み込むことによって蓄積されるバイナリの追加サイズを考慮せず、それ自体が非常にコンパクトな関数での非常に安価なアサーション(引数として既に与えられている2つの整数の比較など)にのみ関連しています。

このコードを比較する

int
positive_difference_1st(const int a, const int b) noexcept
{
  if (!(a > b))
    my::assertion_failed(__FILE__, __LINE__, __FUNCTION__, "!(a > b)");
  return a - b;
}

次のアセンブリにコンパイルされます

_ZN4test23positive_difference_1stEii:
.LFB0:
        .cfi_startproc
        cmpl    %esi, %edi
        jle     .L5
        movl    %edi, %eax
        subl    %esi, %eax
        ret
.L5:
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        movl    $.LC0, %ecx
        movl    $_ZZN4test23positive_difference_1stEiiE12__FUNCTION__, %edx
        movl    $50, %esi
        movl    $.LC1, %edi
        call    _ZN2my16assertion_failedEPKciS1_S1_
        .cfi_endproc
.LFE0:

一方、次のコード

int
positive_difference_2nd(const int a, const int b) noexcept
{
  if (__builtin_expect(!(a > b), false))
    __builtin_trap();
  return a - b;
}

このアセンブリを提供します

_ZN4test23positive_difference_2ndEii:
.LFB1:
        .cfi_startproc
        cmpl    %esi, %edi
        jle     .L8
        movl    %edi, %eax
        subl    %esi, %eax
        ret
        .p2align 4,,7
        .p2align 3
.L8:
        ud2
        .cfi_endproc
.LFE1:

私ははるかに快適に感じています。(例は、GCC 5.3.0を用いて試験し-std=c++14-O3及び-march=native4.3.3-2-ARCH x86_64のGNU / Linux上のフラグは上記のスニペットに示されていないの宣言であるtest::positive_difference_1sttest::positive_difference_2ndどのIを添加__attribute__ ((hot))します。my::assertion_failedと宣言されました__attribute__ ((cold))。)

それらに依存する関数の前提条件をアサートする

指定したコントラクトで次の関数があるとします。

/**
 * @brief
 *         Counts the frequency of a letter in a string.
 *
 * The frequency count is case-insensitive.
 *
 * If `text` does not point to a NUL terminated character array or `letter`
 * is not in the character range `[A-Za-z]`, the behavior is undefined.
 *
 * @param text
 *         text to count the letters in
 *
 * @param letter
 *         letter to count
 *
 * @returns
 *         occurences of `letter` in `text`
 *
 */
std::size_t
count_letters(const char * text, int letter) noexcept;

書く代わりに

assert(text != nullptr);
assert((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'));
const auto frequency = count_letters(text, letter);

各呼び出しサイトで、そのロジックを一度定義します count_letters

std::size_t
count_letters(const char *const text, const int letter) noexcept
{
  assert(text != nullptr);
  assert((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'));
  auto frequency = std::size_t {};
  // TODO: Figure this out...
  return frequency;
}

さらに苦労せずに呼び出します。

const auto frequency = count_letters(text, letter);

これには次の利点があります。

  • アサーションコードを記述する必要があるのは1回だけです。関数の目的は、多くの場合複数回呼び出されることであるため、assertコード内のステートメントの総数を減らす必要があります。
  • 前提条件をチェックするロジックを、それらに依存するロジックの近くに保ちます。これが最も重要な側面だと思います。クライアントがインターフェースを誤用している場合、アサーションを正しく適用することも想定できないため、関数が通知する方が適切です。

明らかな欠点は、呼び出しサイトのソースの場所を診断メッセージに含めないことです。これは小さな問題だと思います。優れたデバッガーを使用すると、契約違反の原因を簡単に追跡できるはずです。

同じ考えが、多重定義された演算子のような「特別な」関数にも当てはまります。イテレータを作成するとき、通常、イテレータの性質上許可されている場合は、イテレータにメンバー関数を与えます

bool
good() const noexcept;

これにより、イテレータを逆参照しても安全かどうかを確認できます。(もちろん、実際には、イテレータを逆参照することが安全でないことを保証することはほとんど常に可能です。しかし、そのような関数で多くのバグをまだキャッチできると信じています。)assert(iter.good())文でイテレータを使用する場合は、イテレータの実装のassert(this->good())最初の行として単一を配置operator*します。

標準ライブラリを使用している場合、ソースコードの前提条件を手動でアサートする代わりに、デバッグビルドでそれらのチェックをオンにします。イテレータが参照するコンテナがまだ存在するかどうかをテストするなど、さらに高度なチェックを実行できます。(libstdc ++およびlibc ++のドキュメントを参照してください(作業中)ください。)

一般的な条件を除外する

線形代数パッケージを書いているとします。多くの関数には複雑な前提条件があり、それらに違反すると、すぐには認識できないような誤った結果がしばしば発生します。これらの関数が前提条件をアサートした場合、それは非常に良いことです。構造に関する特定のプロパティを示す一連の述語を定義すると、それらのアサーションがはるかに読みやすくなります。

template <typename MatrixT>
auto
cholesky_decompose(MatrixT&& m)
{
  assert(is_square(m) && is_symmetric(m));
  // TODO: Somehow decompose that thing...
}

また、より有用なエラーメッセージが表示されます。

cholesky.hxx:357: cholesky_decompose: assertion failed: is_symmetric(m)

と言うよりもはるかに役立ちます

detail/basic_ops.hxx:1289: fast_compare: assertion failed: m(i, j) == m(j, i)

実際にテストされたものを把握するために、最初にコンテキスト内のソースコードを確認する必要があります。

あなたが持っている場合 class非自明な不変量と、それはおそらくあなたが内部の状態を台無しにし、あなたがリターン時に有効な状態でオブジェクトを残していることを確実にしたいしている時間までの時間からそれらのアサートに良いアイデアです。

この目的のために、私がprivate慣習的に呼び出すメンバー関数を定義することは有用であることがわかりましたclass_invaraiants_hold_。あなたが再実装していたとしましょうstd::vector(私たちは皆、それが十分ではないことを知っているので)。このような機能を持っているかもしれません。

template <typename T>
bool
vector<T>::class_invariants_hold_() const noexcept
{
  if (this->size_ > this->capacity_)
    return false;
  if ((this->size_ > 0) && (this->data_ == nullptr))
    return false;
  if ((this->capacity_ == 0) != (this->data_ == nullptr))
    return false;
  return true;
}

これについていくつか注意してください。

  • 述語関数自体はconstandでありnoexcept、ガイドラインによれば、アサーションには副作用はありません。理にかなっている場合は、それも宣言しますconstexprます。
  • 述語自体は何も主張しません。などの内部アサーションと呼ばれることを意図していますassert(this->class_invariants_hold_())。このように、アサーションがコンパイルアウトされている場合、実行時のオーバーヘッドが発生しないことを確認できます。
  • 関数内の制御フローは、大きな式ではなく、if早いreturns を持つ複数のステートメントに分割されます。これにより、デバッガーで関数をステップ実行し、アサーションが発生した場合に不変式のどの部分が破損したかを簡単に確認できます。

愚かなことを主張しないでください

いくつかのことは主張する意味がありません。

auto numbers = std::vector<int> {};
numbers.push_back(14);
numbers.push_back(92);
assert(numbers.size() == 2);  // silly
assert(!numbers.empty());     // silly and redundant

これらのアサーションは、コードを少しでも読みやすくしたり、推論しやすくしたりしません。すべてのC ++プログラマーは、std::vector、上記のコードが見ただけで正しいことを確実にするために、機能するあります。コンテナのサイズを決して主張してはいけないと言っているのではありません。自明でない制御フローを使用して要素を追加または削除した場合、そのようなアサーションが役立ちます。ただし、上記の非アサーションコードで記述されたものを単に繰り返す場合、得られる価値はありません。

また、ライブラリ関数が正しく機能することを主張しないでください。

auto w = widget {};
w.enable_quantum_mode();
assert(w.quantum_mode_enabled());  // probably silly

ライブラリがそれほど信頼できない場合は、代わりに別のライブラリを使用することを検討してください。

一方、ライブラリのドキュメントが100%明確ではなく、ソースコードを読んでその契約についての信頼を得た場合、その「推論された契約」を主張することは理にかなっています。ライブラリの将来のバージョンで破損した場合、すぐに気付くでしょう。

auto w = widget {};
// After reading the source code, I have concluded that quantum mode is
// always off by default but this isn't documented anywhere.
assert(!w.quantum_mode_enabled());

これは、仮定が正しいかどうかを通知しない次のソリューションよりも優れています。

auto w = widget {};
if (w.quantum_mode_enabled())
  {
    // I don't think that quantum mode is ever enabled by default but
    // I'm not sure.
    w.disable_quantum_mode();
  }

アサーションを悪用してプログラムロジックを実装しないでください

アサーションは、アプリケーションをすぐに殺すに値するバグを発見するためにのみ使用してください。それらの条件に対する適切な対応がすぐに終了することであっても、それらを使用して他の条件を検証しないでください。

したがって、これを書いて…

if (!server_reachable())
  {
    log_message("server not reachable");
    shutdown();
  }

…代わりに。

assert(server_reachable());

また、アサーションを使用して、信頼されていない入力を検証したり、それstd::mallocが実行されなかったreturnことを確認したりしないでくださいnullptr。リリースビルドであっても、アサーションをオフにしないことがわかっている場合でも、アサーションは、プログラムにバグがなく、目に見える副作用がないことを考えると、常に真であるものをチェックすることを読者に伝えます。これが通信したい種類のメッセージではない場合throw、例外を実行するなどの代替エラー処理メカニズムを使用します。非アサーションチェック用のマクロラッパーがあると便利な場合は、作成してください。「アサート」、「仮定」、「必須」、「保証」などと呼ばないでください。その内部ロジックはassert、もちろんコンパイルされないことを除いて、forと同じです。

詳しくは

私はジョンLakos'話見つけ正しく行わ守備プログラミング CppCon'14で与えられ、(1 回目の部分2 回目の部分は)非常に啓発します。彼は、このアサーションで私が行ったよりもさらに、どのアサーションを有効にし、失敗した例外にどのように対応するかをカスタマイズするという考えを取り入れています。


4
Assertions are great, but ... you will turn them off sooner or later.-うまくいけば、コードが出荷される前のように。本番環境でプログラムを停止させるために必要なものは、アサーションではなく「実際の」コードの一部である必要があります。
Blrfl

4

それらの多くは「コンパイラーが動作している」と「ライブラリーが動作している」のであるため、時間の経過とともに私はより少ないアサートを記述します。正確に何をテストしているのか考え始めたら、アサートする回数が減ると思います。

たとえば、(たとえば)コレクションに何かを追加するメソッドは、コレクションが存在することをアサートする必要はありません。これは通常、メッセージを所有するクラスの前提条件であるか、ユーザーに戻す必要がある致命的なエラーです。 。したがって、非常に早い段階で一度確認してから、それを想定してください。

私へのアサーションはデバッグツールであり、一般に2つの方法で使用します。デスクでバグを見つける(チェックインされません。まあ、おそらく1つの重要な方法です)。顧客のデスクでバグを見つけます(そして彼らはチェックインします)。どちらの場合も、できるだけ早く例外を強制した後にスタックトレースを生成するために、主にアサーションを使用しています。この方法でアサーションを使用すると、ヘイゼンバグが簡単に発生する可能性があることに注意してください。アサーションが有効になっているデバッグビルドでバグが発生することはほとんどありません。


4
あなたが言うとき、私はあなたのポイントを得ることができません 「それは一般に、メッセージを所有するクラスの前提条件であるか、ユーザーに返されるべき致命的なエラーである」と。だから、それを前提とし、非常に早い段階で、一度それを確認。」あなたはアサーションを使用しているものではありません場合は、あなたの仮定を検証するために?
5gon12eder

4

アサーションが少なすぎます。隠された前提に惑わされたコードを変更して幸運を祈ります。

アサーションが多すぎる:読みやすさの問題や潜在的にコードの匂いにつながる可能性があります-クラス、関数、APIは、assertステートメントに非常に多くの仮定があるときに設計されていますか?

実際には何もチェックしない、または各関数のコンパイラー設定などをチェックしないアサーションもあります:/

スイートスポットを目指しますが、それでも劣りません(他の誰かがすでに言ったように、アサーションの「多く」は、少なすぎるか、神が私たちを助けるよりも害が少ない-なし)。


3

ブール型のCONSTメソッドへの参照のみを取得するAssert関数を作成できれば、素晴らしいでしょう。この方法では、ブール型のconstメソッドを使用してアサートをテストすることにより、アサーションに副作用がないことを確認できます。

特に、(c ++ 0xの)ラムダに何らかのクラスのconstとして注釈を付けることができないとは思わないので、読みやすさから少し引き出します。つまり、そのためにラムダを使用することはできません。

あなたが私に尋ねると過剰になりますが、私が断定のために特定のレベルの汚染を見始めたら、私は2つのことを警戒します:

  • アサートで副作用が発生していないことを確認します(上記で説明した構造によって提供されます)
  • 開発テスト中のパフォーマンス。これは、アサート機能にレベル(ロギングなど)を追加することで対処できます。パフォーマンスを向上させるために、開発ビルドからいくつかのアサートを無効にすることができます

2
「確実」という言葉とその派生語が好きです。使用回数は8回です。
ケーシーパットン

はい、申し訳ありませんが、私は言葉であまりにも多くの傾向があります-修正済み、ありがとう
-lurscher

2

私はC ++で書いたよりもC#で書いたことがありますが、2つの言語はそれほど離れていません。.Netでは、発生してはならない条件に対してアサートを使用しますが、続行する方法がない場合にも例外をスローすることがよくあります。VS2010デバッガーは、リリースビルドの最適化に関係なく、例外に関する多くの有益な情報を表示します。可能であれば、単体テストを追加することもお勧めします。デバッグの補助として、ロギングも有効な場合があります。

だから、あまりにも多くのアサートがあるのでしょうか?はい。1分間に中止/無視/続行を15回選択すると、迷惑になります。例外は1回だけスローされます。アサーションが多すぎるポイントを定量化することは困難ですが、アサーションがアサーション、例外、単体テスト、ロギングの役割を果たしている場合は、何かが間違っています。

発生してはならないシナリオのアサーションを予約します。アサーションは書くのが速いので、最初にオーバーアサートするかもしれませんが、後でコードをリファクタリングします-それらのいくつかを例外に、いくつかをテストに、などに変えてください。やり直す予定のそれぞれの隣にコメントし、後でTODOに取り組むことを忘れないでください。


コードが1分間に15回のアサーションに失敗した場合、より大きな問題が関係していると思います。アサーションは、バグのないコードで実行されることはありません。実行すると、さらなる損害を防ぐためにアプリケーションを強制終了するか、デバッガーにドロップして何が起こっているかを確認する必要があります。
5gon12eder

2

私はあなたと働きたいです!たくさん書いている人assertsは素晴らしいです。「多すぎる」などのことがあるかどうかはわかりません。私にとってはるかに一般的なのは、書くことが少なすぎて、最終的に単純なもので簡単に繰り返し再現できた満月にしか現れない時折致命的なUB問題に突き当たってしまう人々ですassert

失敗メッセージ

私が考えることができる1つのことは、次のように、失敗情報をassertまだ実行していない場合に埋め込むことです。

assert(n >= 0 && n < num && "Index is out of bounds.");

こうすることで、仮定や前提条件を文書化する際にアサーションがより強力な役割を果たすようになり、まだこれを行っていなかったとしても多すぎると感じることはなくなります。

副作用

もちろん、assert実際には次のように誤用され、エラーが発生する可能性があります。

assert(foo() && "Call to foo failed!");

... foo()副作用を引き起こす場合、それについて非常に注意する必要がありますが、あなたはすでに非常に寛大に主張する人(「経験豊富な主張者」)であると確信しています。願わくば、あなたのテスト手順が、仮定を主張するためのあなたの注意と同じくらい良いことを願っています。

デバッグ速度

デバッグの速度は一般に優先順位リストの一番下にあるはずですが、一度デバッガでデバッグビルドを実行するとリリースより100倍以上遅くなる前に、コードベースで多くのことをアサートすることになりました。

これは主に、次のような機能があったためです。

vec3f cross_product(const vec3f& lhs, const vec3f& rhs)
{
    return vec3f
    (
        lhs[1] * rhs[2] - lhs[2] * rhs[1],
        lhs[2] * rhs[0] - lhs[0] * rhs[2],
        lhs[0] * rhs[1] - lhs[1] * rhs[0]
    );
}

...へのすべての呼び出しoperator[]は、境界チェックアサーションを行います。パフォーマンスが重要なもののいくつかを、実装の詳細レベルの安全性にわずかなコストで大幅にデバッグビルドを高速化するだけではなく、その速度のヒットが始まったために主張しない安全でない同等物に置き換えました生産性を著しく低下させます(デバッグを高速化する利点は、いくつかのアサーションを失うコストを上回りますが、operator[]一般的にではなく、最も重要な測定パスで使用されていたこのクロス積関数のような関数に対してのみ)。

単一責任の原則

私はあなたが本当にもっと多くのアサートで間違って行くことはできないと思いますが(少なくとも、少なすぎるよりも多すぎる側で誤解する方がはるかに良いです)、アサート自体は問題ではないかもしれませんが、それを示しているかもしれません。

たとえば、1つの関数呼び出しに対して5つのアサーションがある場合、それはやりすぎている可能性があります。そのインターフェイスには前提条件と入力パラメーターが多すぎる可能性があります。たとえば、健全な数のアサーションを構成するトピックのトピックとは無関係であると考えます(一般的には「もっと楽しい!」と答えます)。可能性のある赤旗(または可能性が非常に低い)。


1
さて、理論上は「多すぎる」アサーションが存在する可能性がありますが、その問題は非常に速く明らかになります:アサーションが関数の肉よりもかなり長くかかる場合。確かに、野生ではまだ反対の問題がproblem延していることを発見したことを思い出せません。
デデュプリケーター

@Deduplicatorああ、そういった重要なベクトル数学ルーチンでそのようなケースに遭遇しました。間違いなく、少なすぎるよりも多すぎる側で過ちを犯す方がはるかに良いようです!

-1

コードにチェックを追加することは非常に合理的です。単純なアサート(CおよびC ++コンパイラに組み込まれているもの)の場合、アサートの失敗は、修正が必要なコードにバグがあることを意味するという使用パターンです。これを少し寛大に解釈します。Webリクエストがステータス200を返し、他のケースを処理せずにアサートすることを期待する場合、アサーションの失敗実際にコードにバグを示すため、アサートは正当化されます。

したがって、人々が、コードが何をするかをチェックするだけだと断言するのは不必要であり、それはまったく正しくありません。そのアサートは、彼らがコードが行うと思うことをチェックし、アサートの全体的なポイントは、コードにバグがないという仮定が正しいことをチェックすることです。また、アサートはドキュメントとしても機能します。ループを実行した後、i == nであり、コードから100%明らかでないと仮定した場合、「assert(i == n)」が役立ちます。

さまざまな状況に対処するために、レパートリーに「アサート」以上のものを含めることをお勧めします。たとえば、バグを示すものが発生しないことを確認したが、それでも引き続きその状態を回避する状況。(たとえば、キャッシュを使用している場合、エラーをチェックします。予期しないエラーが発生した場合は、キャッシュを破棄することでエラーを修正しても安全です。ほとんどアサートで、開発中に通知するものが必要です、まだ続行できます。

別の例は、何かが起こるとは思わない状況であり、一般的な回避策がありますが、この問題が発生した場合は、それについて知り、調べたいと思います。繰り返しますが、ほぼアサートのようなもので、開発中にそれがわかります。しかし、ではない非常にアサート。

アサーションが多すぎる:ユーザーの手にあるときにアサーションがプログラムをクラッシュさせる場合、偽陰性のためにクラッシュするアサーションがあってはなりません。


-3

場合によります。コード要件が明確に文書化されている場合、アサーションは常に要件に一致する必要があります。その場合、それは良いことです。ただし、要件がない場合や要件の記述が不適切な場合、新しいプログラマーは、毎回単体テストを参照して要件を把握することなくコードを編集することは困難です。


3
これは、以前の8つの回答で作成され、説明されたポイントに対して実質的なものを提供していないようです
-gnat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.