ブログ@kaorun55

HoloLensやKinectなどのDepthセンサーを中心に書いています。

契約による設計

プログラムを安全性を高める手法の一つに契約による設計(Design By Contract)というものがある。


これは、

プログラムコードの中にプログラムが満たすべき仕様についての記述を盛り込む事で設計の安全性を高める技法

Wikipediaより


となっていて、契約の種類は以下の3種類がある。

表明契約
表明(assert)。実行時に恒真となるべき条件を記述する。
事前契約、事後契約
関数の開始、終了時で満たすべき条件を記述する。
不変契約
クラスなどのオブジェクトが常に満たすべき条件を記述する。

Wikipediaより


で、またこれをクラス化したんですよ、昔の自分は(笑)

Assertを基準とする例外クラスとして実装しました。
相関図はこんなカンジかな。

std::exception ←-- shared::AssertException(表明契約)
                      ↑
                      ├ shared::PreConditionException(事前契約)
                      ├ shared::PostConditionException(事後契約)
                      └ shared::InvariantException(不変契約)

こいつらをマクロで包んで使いやすくしたのが今回のソース。
#ご使用は自己責任で!

ヘッダ

#if !defined( ASSERTION_H_INCLUDE )
#define ASSERTION_H_INCLUDE

#include <stdexcept>
#include <string>

/// 共通ライブラリ
namespace shared
{
    /**
     * @def Assert( expr, msg ) \n
     * アサート \n
     * 有効時:式exprが偽のときAssertExceptionの送出 \n
     * 無効時:無処理 \n
     */
    /**
     * @def PreCondition( expr ) \n
     * 事前条件チェック \n
     * 有効時:式exprが偽のときPreConditionExceptionの送出 \n
     * 無効時:無処理 \n
     */
    /**
     * @def PostCondition( expr ) \n
     * 事後条件チェック \n
     * 有効時:式exprが偽のときPostConditionExceptionの送出 \n
     * 無効時:無処理 \n
     */
    /**
     * @def Invariant( expr ) \n
     * 不変条件チェック \n
     * 有効時:式exprが偽のときInvariantExceptionの送出 \n
     * 無効時:無処理 \n
     */

    // アサート無効
    #ifdef DISABLE_ASSERT
     #define Assert( expr, msg )   { (void)expr; }
     #define PreCondition( expr )  { (void)expr; }
     #define PostCondition( expr ) { (void)expr; }
     #define Invariant( expr )     { (void)expr; }
    // アサート有効
    #else // #ifdef DISABLE_ASSERT
     #define Assert( expr, msg )   { if( !(expr) ) throw shared::AssertException( __FILE__, __LINE__, msg, #expr ); }
     #define PreCondition( expr )  { if( !(expr) ) throw shared::PreConditionException( __FILE__, __LINE__, #expr ); }
     #define PostCondition( expr ) { if( !(expr) ) throw shared::PostConditionException( __FILE__, __LINE__, #expr ); }
     #define Invariant( expr )     { if( !(expr) ) throw shared::InvariantException( __FILE__, __LINE__, #expr ); }
    #endif // #ifdef DISABLE_ASSERT

    /// アサート例外
    class AssertException : public std::exception
    {
    public:

        // コンストラクタ
        AssertException( const char* fileName, unsigned int lineNo,
                        const char* message, const char* expression );

        // デストラクタ
        virtual ~AssertException();

        // エラーメッセージの取得
        virtual const char* what() const;

        // ファイル名の取得
        virtual const std::string& getFileName() const;

        // 行数の取得
        virtual unsigned int getLineNo() const;

        // エラーメッセージの取得
        virtual const std::string& getMessage() const;

        // 評価した式の取得
        virtual const std::string& getExpression() const;

    private:

        std::string     m_fileName;     ///< ファイル名
        unsigned int    m_lineNo;       ///< 行数
        std::string     m_message;      ///< エラーメッセージ
        std::string     m_expression;   ///< 評価した式
    };

    /// 事前条件例外
    class PreConditionException : public AssertException
    {
    public:

        // コンストラクタ
        PreConditionException( const char* fileName, unsigned int lineNo,
                            const char* expression );

        // デストラクタ
        virtual ~PreConditionException();
    };

    /// 事後条件例外
    class PostConditionException : public AssertException
    {
    public:

        // コンストラクタ
        PostConditionException( const char* fileName, unsigned int lineNo,
                            const char* expression );

        // デストラクタ
        virtual ~PostConditionException();
    };

    /// 不変条件例外
    class InvariantException : public AssertException
    {
    public:

        // コンストラクタ
        InvariantException( const char* fileName, unsigned int lineNo,
                            const char* expression );

        // デストラクタ
        virtual ~InvariantException();
    };
} // namespace shared

#endif // !defined( ASSERTION_H_INCLUDE )
// EOF


ソース

/** @file
 * @brief アサーション関連定義
 *
 * @author 中村  薫
 *
 * @date 2004/11/1  新規作成
 *
 * $Revision: 1.1.1.1
 */
#include "Assertion.h"

namespace shared
{
    ///////////////////////////////////////////////////////////////////////////////
    //
    // AssertException
    //

    /**
     * コンストラクタ
     *
     * @param fileName ファイル名
     * @param lineNo 行数
     * @param message エラーメッセージ
     * @param expression 評価した式
     */
    AssertException::AssertException( const char* fileName, unsigned int lineNo,
                                    const char* message, const char* expression )
        : std::exception()
        , m_fileName( fileName )
        , m_lineNo( lineNo )
        , m_message( message )
        , m_expression( expression )
    {
    }

    /**
     * デストラクタ
     */
    AssertException::~AssertException()
    {
    }

    /**
     * エラーメッセージの取得
     *
     * @return エラーメッセージ
     */
    const char* AssertException::what() const
    {
        return m_message.c_str();
    }

    /**
     * ファイル名の取得
     *
     * @return ファイル名
     */
    const std::string& AssertException::getFileName() const
    {
        return m_fileName;
    }

    /**
     * 行数の取得
     *
     * @return 行数
     */
    unsigned int AssertException::getLineNo() const
    {
        return m_lineNo;
    }

    /**
     * エラーメッセージの取得
     *
     * @return エラーメッセージ
     */
    const std::string& AssertException::getMessage() const
    {
        return m_message;
    }

    /**
     * 評価した式の取得
     *
     * @return 評価した式
     */
    const std::string& AssertException::getExpression() const
    {
        return m_expression;
    }


    ///////////////////////////////////////////////////////////////////////////////
    //
    // PreConditionException
    //

    /**
     * コンストラクタ
     *
     * @param fileName ファイル名
     * @param lineNo 行数
     * @param expression 評価した式
     */
    PreConditionException::PreConditionException( const char* fileName,
                                unsigned int lineNo, const char* expression )
        : AssertException( fileName, lineNo, "事前条件エラー", expression )
    {
    }

    /**
     * デストラクタ
     */
    PreConditionException::~PreConditionException()
    {
    }

    ///////////////////////////////////////////////////////////////////////////////
    //
    // PostConditionException
    //

    /**
     * コンストラクタ
     *
     * @param fileName ファイル名
     * @param lineNo 行数
     * @param expression 評価した式
     */
    PostConditionException::PostConditionException( const char* fileName,
                                    unsigned int lineNo, const char* expression )
        : AssertException( fileName, lineNo, "事後条件エラー", expression )
    {
    }

    /**
     * デストラクタ
     */
    PostConditionException::~PostConditionException()
    {
    }

    ///////////////////////////////////////////////////////////////////////////////
    //
    // InvariantException
    //

    /**
     * コンストラクタ
     *
     * @param fileName ファイル名
     * @param lineNo 行数
     * @param expression 評価した式
     */
    InvariantException::InvariantException( const char* fileName, unsigned int lineNo,
                                            const char* expression )
        : AssertException( fileName, lineNo, "不変条件エラー", expression )
    {
    }

    /**
     * デストラクタ
     */
    InvariantException::~InvariantException()
    {
    }
} // namespace bcl
// EOF


使用例

#include <iostream>
#include "shared/Assertion.h"

class File
{
public:

    File() : handle_( 0 ) {}
    ~File() { close(); }

    void open( const char* fileName )
    {
        // いろいろ
    }

    void close()
    {
        // いろいろ
    }

    void write( const void* buffer, int size )
    {
        PreCondition( handle_ != 0 );

        // いろいろ
    }

    void read( const void* buffer, int size )
    {
        PreCondition( handle_ != 0 );

        // いろいろ
    }

private:

    int handle_;
};

void main()
{
    try {
        File file;

        std::cout << "File::open" << std::endl;
//      file.open( "test.txt" );    // デバッグでコメントしたとする

        std::cout << "File write" << std::endl;
        file.write( &file, sizeof(file) );

        std::cout << "File read" << std::endl;
        file.read( &file, sizeof(file) );
    }
    catch ( shared::AssertException& ex ) {
        std::cout << "Message    : " << ex.getMessage() << std::endl;
        std::cout << "FileName   : " << ex.getFileName() << std::endl;
        std::cout << "Line       : " << ex.getLineNo() << std::endl;
        std::cout << "Expression : " << ex.getExpression() << std::endl;
    }
}
// EOF


表示例

File::open
File write
Message    : 事前条件エラー
FileName   : d:\_program\asserttest\main.cpp
Line       : 23
Expression : handle_ != 0


使うときは、Assert( 条件式 )とかPreCondition( 条件式 )とかって書けばOK。
実際に今使ってるけど、結構使えるんだなコレが!