契約による設計の導入
C言語ライブラリをラップする際、信頼性の維持とエラー検証は重要な課題です。多くのCプログラムはエラーチェックを適切に実装しておらず、不正な入力や想定外の使用方法によって簡単にクラッシュする可能性があります。本章では、SWIGが提供するソフトウェア契約のサポートについて説明します。SWIGの文脈において、契約は宣言に付随する実行時制約として機能し、引数検証ルールの付加や関数戻り値のチェックなどを容易に実現できます。
%contractディレクティブの基本
契約は%contractディレクティブを使用して宣言に追加されます。以下は平方根関数に対する契約の例です:
%contract sqrt(double input_val) {
require:
input_val >= 0;
ensure:
result >= 0;
}
double sqrt(double);
%contractディレクティブは対象の宣言よりも前に記述する必要があります。契約内のrequireセクションは関数呼び出し前に満たすべき条件を、ensureセクションは関数呼び出し後に満たすべき条件をそれぞれ定義します。これらの条件はブール式で指定します。
契約が指定されると、生成されるモジュールの動作が変更されます:
>>> import math_module
>>> math_module.sqrt(4.0)
2.0
>>> math_module.sqrt(-1.0)
ContractViolationError: require条件違反: (input_val >= 0)
クラスメソッドへの契約適用
%contractディレクティブはクラスメソッドやコンストラクタにも適用できます:
%contract DataProcessor::calculate(int a, int b) {
require:
a > 0 && b > 0;
ensure:
calculate > 0;
}
%contract DataProcessor::DataProcessor(int init_val) {
require:
init_val != 0;
}
class DataProcessor {
public:
DataProcessor(int);
int calculate(int, int);
};
%contractの適用方法は%featureディレクティブと同様であり、基底クラスに指定された契約は継承されたメソッドにも適用されます。複数の契約が適用される場合、requireセクションの条件は論理AND演算で結合されます。
集約チェックの実装
特定のパラメータが許容値のいずれかであることを保証する制約を課す場合、SWIGは%aggregate_checkマクロを提供します:
%aggregate_check(int, validate_direction, NORTH, SOUTH, EAST, WEST);
%contract move(GameObject *, int dir, int dist) {
require:
validate_direction(dir);
}
#define NORTH 1
#define SOUTH 2
#define EAST 3
#define WEST 4
void move(GameObject *, int direction, int distance);
このマクロは以下の形式のユーティリティ関数を定義します:
int validate_direction(int value);
この関数は引数が列挙された値のいずれかであるかを検証します。契約だけでなく、タイプマップや他のディレクティブでも使用可能です:
%aggregate_check(int, validate_direction, NORTH, SOUTH, EAST, WEST);
%typemap(check) int direction {
if (!validate_direction($1)) {
SWIG_exception(SWIG_ValueError, "無効な方向指定");
}
}
技術的留意点
契約機能はSWIG-1.3.20で初めて導入されました。現在のバージョンでは列挙型値に対する自動検証メカニズムは提供されていませんが、%aggregate_checkマクロを使用することで同様の検証を実現できます。