はじめに
契約による設計を知ると、よりよいプログラミングができそうだったので個人的にまとめました。
契約による設計とは
契約による設計(けいやくによるせっけい、Design By Contract)とは、プログラムコードの中にプログラムが満たすべき仕様についての記述を盛り込む事で設計の安全性を高める技法。
具体的には、「もしそちらが事前条件及びクラス不変条件1を満たした状態で私を呼ぶと約束して下さるならば、お返しに、事後条件及びクラス不変条件を満たす状態を最終的に実現することをお約束します。」2という契約を結ぶことを言います。
事前条件、事後条件、クラス不変条件の意味
- 事前条件は、呼び出し側が守らなければならない条件。
- 事後条件は、呼ばれる側が守らなければならない条件。
- クラス不変条件は、呼び出し前、呼び出し後で維持されなければならない性質。
契約による設計のメリット
事前条件、事後条件、クラス不変条件で期待されている条件が明示されるため、
- 仕様がコード内で表現される
- 落ちる場所(例外3が挙げられるタイミング)が早いためデバッグがしやすくなる
といったメリットがあると思います。
契約による設計のコード例
コード4で表すと下記のようになります(あくまで説明用のコードです)。
class Rectangle {
int _height;
int _width;
Rectangle({int height, int width}) {
if (height <= 0) throw new ArgumentError("height must be positive integer."); // 事前条件
if (width <= 0) throw new ArgumentError("width must be positive integer."); // 事前条件
this._height = height;
this._width = width;
assert(_height == height); // 事後条件
assert(_width == width); // 事後条件
_invariant();
}
set height(int height) {
_invariant(); // クラス不変条件
if (height <= 0) throw new ArgumentError("height must be positive integer."); // 事前条件
_height = height;
assert(_height == height); // 事後条件
_invariant(); // クラス不変条件
}
int get height {
_invariant(); // クラス不変条件
return _height;
}
set width(int width) {
if (width <= 0) throw new ArgumentError("width must be positive integer."); // 事前条件
_width = width;
assert(_width == width); // 事後条件
}
int get width {
_invariant(); // クラス不変条件
return _width;
}
area() {
_invariant(); // クラス不変条件
assert(_width > 0 && _height > 0); // 事前条件
var area = _width * _height;
assert(area > 0);
_invariant();
return area; // 事後条件
}
_invariant() {
assert(_width > 0 && _height > 0);
}
}
本来クラス不変条件は、インスタンス作成時とメソッド呼び出しの前後ですが、メソッド呼び出しの後の一部(getter部分)は書き足しても意味がないので省略しています。
表明とは
表明とは、
プログラミングにおける概念のひとつであり、そのプログラムの前提条件を示すのに使われる。アサーションとも呼ばれる。表明は、プログラムのその箇所で必ず真であるべき式の形式をとる。
表明より
下記のようにassertが記述されている部分が表明です。
assert(_width == width);
例外とは
ここでの例外は、一般的な例外5 (exception)のことを表します。
下記コードのthrow new ArgumentError("height must be positive integer.")
の部分が例外です。
if (height <= 0) throw new ArgumentError("height must be positive integer.");
表明と例外の使い分
「表明6は、起こり得ないことにたいして記述するもの。」
「例外は、起こり得ることに対して記述するもの。」
という形で使い分けてます。
終わりに
- ライブラリや言語のサポートがない場合には、クラス不変条件や事後条件は必要に応じて書く
- assertがサポートされている場合には、事前条件、事後条件だけでも書く
- テストコードを書くときに事前条件、事後条件、クラス不変条件を意識して網羅性や仕様の読み取りやすさを上げる
といったことを考えるとよりよいプログラミングにつながるのではないかと思いました。
参考
- 契約による設計の紹介
- PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計
- アジャイルソフトウェア開発の奥義 ロバート・C・マーチン
- オブジェクト指向入門 第2版 原則・コンセプト バートランド・メイヤー
- 達人プログラマ Andrew Hunt, David Thomas
-
必ずしも表明ではないと考えているので、クラス不変表明と表現せず、クラス不変条件と記述しています。 ↩
-
「オブジェクト指向入門 第2版 原則・コンセプト」では、直接的にクラス不変条件の記述はされていません。クラス不変条件は、暗黙的に事前条件、事後条件に追加されると記述されています。 ↩
-
この部分での例外には表明違反も含めています。 ↩
-
このコードは本来の契約による設計とは意味が異なると思いながら書いています。というのは、本家(オブジェクト指向 第2版 原則・コンセプト)の契約による設計では条件に表明が使われており、呼び出す側が値をチェックした状態で呼ぶことが前提になっているように読み取れるからです。(つまりラッパー相当あるいは呼び出し側モジュールがバリデーション(引数の検査)を行うような作りになっており、内部の処理するモジュールが引数として取るものは必ず正しいという意味で事前条件としての表明が存在する作り。)表明は、起こり得ない事に対して使うものという立場に立って考えると、どこからでも呼び出せる(publicな)メソッドは事前条件に相当するものとして検査して例外を上げるほうがよいのではないかと思い変更しております(Effective Javaではpublicなメソッドの引数は通常のバリデーション(引数の検査を行い条件に適合しない場合には例外を上げる作り)、privateなメソッドの引数の検査には表明という作りになっている箇所がありました)。 ↩
-
「オブジェクト指向入門 第2版 原則・コンセプト」では、表明違反が例外に含まれている記述が見られるので、その部分の例外とは異なるという意味で、ここでは一般的な例外という言葉を使用しています。 ↩
-
「達人プログラマー 職人から名匠への道」の中で「もし起こり得ないというのであれば、表明を用いてそれを保証すること」という記述があります。また、別の箇所で「本来のエラー処理に表明を使ってはいけません。表明は起こり得ないことをチェックするためのものです」と記述されています。このことから、起こり得ないことは表明。起こり得ることは例外と使い分けています。 ↩