Jubatus において assert マクロを使う場合には,標準ライブラリのそれではなく,
jubatus/core/common/assert.hpp に定義されたマクロを使うことが推奨される.
使用できるマクロの一覧は,以下の通り:
JUBATUS_ASSERT(cond)condがtrueになることをチェックする. 失敗した場合,エラーメッセージを吐いてプログラムの実行を停止する.
JUBATUS_ASSERT_MSG(cond, messages)condがtrueになることをチェックする. 失敗した場合,メッセージとしてmessagesに渡された要素が追加で出力される.messagesはstream << messagesのような形でストリームに出力されるため,"a: " << aのように<<で区切って要素を渡すことで,複数の要素を出力させることができる. 渡される各要素はstd::ostreamに出力できる必要がある.
JUBATUS_ASSERT_UNREACHABLE()- このマクロが置かれた場所まで処理が到達しないことをチェックする. コンパイラの警告を避けるため,このマクロが呼ばれた場合, assert が無効になっている場合であっても,プログラムを異常終了させる. ( assert が有効でも無効でも動作が変わらない,という意味ではない. 具体的には, assert が有効ならば読みやすいエラーメッセージが表示されるが,無効の場合には表示されない.)
JUBATUS_ASSERT_EQ(a, b, messages)a == bであることをチェックする. 失敗した場合,メッセージとしてaとbの内容およびmessagesに渡された要素が追加で出力される.messagesの扱いはJUBATUS_ASSERT_MSGに準ずる. 追加のメッセージが必要ない場合には""を渡せばよい.
JUBATUS_ASSERT_NE(a, b, messages)a != bであることをチェックする以外はJUBATUS_ASSERT_EQと同じ.
JUBATUS_ASSERT_LE(a, b, messages)a <= bであることをチェックする以外はJUBATUS_ASSERT_EQと同じ.
JUBATUS_ASSERT_LT(a, b, messages)a < bであることをチェックする以外はJUBATUS_ASSERT_EQと同じ.
JUBATUS_ASSERT_GE(a, b, messages)a >= bであることをチェックする以外はJUBATUS_ASSERT_EQと同じ.
JUBATUS_ASSERT_GT(a, b, messages)a > bであることをチェックする以外はJUBATUS_ASSERT_EQと同じ.
jubatus/core/common/assert.hpp は Jubatus のコードには依存していないため,
これらのマクロは,該当ヘッダをインクルードしさえすれば,どこでも利用できる.
また,これらのマクロは, JUBATUS_DISABLE_ASSERTIONS マクロを定義すると,
NDEBUG が定義された場合の assert と同じように,条件チェックがスキップされる.
そのため,条件として副作用のある処理を書いてはならない.
(この機能は NDEBUG とは独立して動作する.)
JUBATUS_DISABLE_ASSERTIONS は, waf の設定に
--enable-debug を指定しなかった場合(デフォルト)などに定義される.
現状,これらのマクロは Google Glog を用いて実装されている. そのため, glog を初期化しない場合に エラーメッセージの表示が若干不親切になる他, Waf を使わずにビルドした場合には glog をリンクする必要がある. (なお,将来は glog を使わずに実装される予定である.)
- assert で示されている条件は,正しくプログラムが書けている場合,必ず成立する
- 状況次第で失敗する可能性のある条件を assert にかけてはならない
- 例として,ユーザ入力や設定ファイルの妥当性検証を assert で行なってはならない
- 例外にするべきか assert にするべきか迷った場合には,とりあえず例外を使うことを検討する
- どの例外を投げるのが相応しいかを考える過程で,思考が整理されることが期待できる
- 思考整理ができなかった場合には,その旨をソースコードや pull req. のコメントに残す
- 例外送出を assert にするのは容易だが,逆は容易でない
- どの例外を投げるのが相応しいかを考える過程で,思考が整理されることが期待できる
- コメント代わりに使うことで,コーディング上の不安を取り除きたい場合
- 例として,
sqrt(x)を呼ぶ際,「バグがなければ必ずxは 0 以上になるが,その事実は パッと見では分からない」ような場合には,// x >= 0のようなコメントを書くよりJUBATUS_ASSERT_GE(x, 0, "");と書いたほうがよい - 同様に,関数末尾に
// this function never returnsというコメントを書きたくなった場合, コメントではなくJUBATUS_ASSERT_UNREACHABLE();と書くべきである.
- 例として,
- ローカル変数, private メンバ変数,ファイルスコープ変数など,
アクセスできる範囲が「閉じている」ものに対して不変条件のチェックを行う場合
- それらの変数は,単体テストで不変条件をチェックしにくいため, assert を使ってチェックすることが望ましい
- 例として,二つのポインタを管理しているクラスで,それらのポインタは 常に同時に(非 NULL の値に)設定され,常に同時に NULL にクリアされるような場合には, 片方のみが NULL になることは ありえないため, assert を使ってチェックすることが望ましい
- バグの存在を,もみ消されることなく 即座に検出したい場合
- 例として,外部ライブラリの関数を呼び出した結果に対し,何らかの条件の成立が期待される場合, 期待した通りにならないケースでは,その関数の仕様を勘違いしている可能性が高いため, assert を使って即座に勘違いを検出できるようにすることが望ましい. (もちろん,状況次第では失敗することが期待され,失敗すると処理を続行できない場合には, assert を使わずに runtime_error を投げることで失敗を通知することが望ましい)
-
ある関数を呼び出す際に必要な事前条件のチェックには, assert を使ってもいいし,例外を使ってもいい
- どちらを使うかの裁量は 基本的には関数の設計者に委ねられるが,大まかな指針は以下の通り:
- 比較的単純な条件であり,かつ その関数が呼ばれる頻度が高い場合には, assert を使う
- 条件が複雑であり,事前にチェックするより「試しに実行してみる」方が楽である場合には, 例外か,もしくは optional 的なものを使ったほうがスムーズになる
- どちらを使うかの裁量は 基本的には関数の設計者に委ねられるが,大まかな指針は以下の通り:
-
その他,例外を使った方が処理の見通しが良くなるケースには,例外を使う
- 失敗する可能性のある assert は assert ではないので書いてはいけない
- 失敗する可能性のある処理には,例えば
mallocがある. その結果は assert してはいけない
- 失敗する可能性のある処理には,例えば
if (x <= 0) { throw 〜; }に続くassert(x > 0);のような,書く必要のない assert は原則として書かない- ただし,条件をチェックする部分と assert を書きたくなった部分が 同じ画面に入りきらないくらい離れている場合は,その限りではない
- assert を書かないと不安になる程度に 複雑で入り組んだコードの場合は, assert より むしろ条件分割や関数化によって簡潔で分かりやすい条件に落とし込めないかを考える
👍