torutkのブログ

ソフトウェア・エンジニアのブログ

契約による設計(Design by Contract)の実装〜事後条件判定の改善

d:id:torutk:20080914で契約による設計をC++言語で実装してみました。しかし、事後条件については、簡潔に書くのが難しいです。

事後条件のコード検討

ちょっとした名簿を例にコードを見ていきます。PersonListクラスとPersonクラスがあり、PersonListのaddメンバ関数について事後条件を記述したとします。

  • 事後条件
    • PersonListクラスのメンバ変数list_の要素数は、addメンバ関数を呼ぶ前の要素数に1を加えた値である
    • PersonListクラスのメンバ変数list_の最後尾の要素は、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);
}
その他