d:id:torutk:20080914で契約による設計をC++言語で実装してみました。しかし、事後条件については、簡潔に書くのが難しいです。
事後条件のコード検討
ちょっとした名簿を例にコードを見ていきます。PersonListクラスとPersonクラスがあり、PersonListのaddメンバ関数について事後条件を記述したとします。
- 事後条件
べたに記述
class PersonList { ... }; void PersonList::add(const Person& aPerson) { int preSize = list_.size(); // 事後条件判定に使用 list_.push_back(aPerson); // 本来の処理 int postSize = list_.size(); // 事後条件判定に使用 // 事後条件判定 if (postSize != preSize + 1 || list_.back() != aPerson) { throw PostConditionException(); } }
関数のあちらこちらに事後条件コードが散在しており、あまりきれいなコードではありません。事後条件処理を#ifdefで取り除けるようにする場合、#ifdefだらけになってしまいます。
RAIIイディオムを使ってみる
事後条件コードを1箇所に記述するために、RAIIイディオムを使用したコードを以下に記述します。
void PersonList::add(const Person& aPerson) { // 事前・事後条件起動用RAII内部クラス struct Sentry { int preSize_, postSize_; const list<Person>& list_; const Person& person_; Sentry(const list<Person>& list, const Person& person) : list_(list), person_(person) { preSize = list_.size(); } ~Sentry() { postSize = list_.size(); if (postSize != preSize + 1 || list_.back() != person_) { throw PostConditionException(); } } } sentry(list_, aPerson); list_.push_back(aPerson); }
C++の内部クラスは外側の変数にアクセスできないため、事後条件判定に必要な変数をコンストラクタで取るようにしています。
事後条件を1箇所で記述できるようになったものの、最初のコードより格段に複雑となってしまいました。
関数を委譲する
実際の処理を記述する関数と事後条件を検査する処理を記述する関数を分離します。
void PersonList::add(const Person& aPerson) { int preSize = list_.size(); addUnchecked(const Person& aPerson); int postSize = list_.size(); if (postSize != preSize + 1 || list_.back() != aPerson) { throw PostConditionException(); } } void PersonList::addUnchecked(const Person& aPerson) { list_.push_back(aPerson); }