Design by Contract



  • Hallo Leute.

    Mir ist heute in der Schule ziemlich fad, also habe ich mal ein kleines Design by Contract 'Framework' entworfen.

    eigentlich schaut es ja gut aus - aber ihr entdeckt sicher gravierende mängel.

    naja, sagt halt, was ihr darüber denkt.

    anwendungsbeispiel sieht so aus:

    #define DESIGNBYCONTRACT
    #include <iostream>
    #include "dbc.h"
    
    class Test
    {
    private:
        bool b;
        int i;
    
        bool Invariant()
        {
            return b && (0<=i && i<=10);
        }
    public:
        Test(bool b, int i)
        : b(b), i(i)
        {
            INVARIANT();
        }
    
        void foo(char c)
        {
            PARAM(c,char);
            c='x';
            ENSURE(param_c != c);
        }
    
        void foo2(int* pi)
        {
            REQUIRE(pi!=0 && *pi!=5);
            PARAM_POINTER(pi,int);
            int i=5;
            pi=&i;
            ENSURE(*param_pi != *pi);
            ENSURE(param_pi==pi);
    
        }
    
    };
    
    using namespace std;
    using namespace DesignByContract;
    
    int main(int argc, char *argv[])
    {
        try
        {
            Test t(false,5);
        }
        catch(DesignByContractException& e)
        {
            cout<<e<<endl;
        }
        try
        {
            Test t(true,5);
            t.foo('c');
            int i=7;
            t.foo2(&i);
        }
        catch(DesignByContractException& e)
        {
            cout<<e<<endl;
        }
        try
        {
            Test t(true,5);
            t.foo2(0);
        }
        catch(DesignByContractException& e)
        {
            cout<<e<<endl;
        }
        cin.get();
    }
    

    ausgabe:

    File: main.cpp (19) - Invariant Violation

    File: main.cpp (36) - Postcondition Violation

    File: main.cpp (31) - Precondition Violation

    kurze erklärung dazu.
    REQUIRE ist für Preconditions und REQUIRET gibt zusätzlich noch eine Meldung aus
    ENSURE ist für postconditions und ENSURET gibt zusätzlich noch eine Meldung aus
    INVARIANT ist für invariants (in klassen). dazu muss die Klasse eine Methode namens Invariant() haben die bool returned. INVARIANTT gibt zusätzlich noch eine Meldung aus.
    INVARIANTF ruft eine invariant funktion auf (die man selber angeben kann). INVARIANTFT gibt zusätzlich noch eine Meldung aus
    PARAM speichert den Parameter in param_name
    PARAM_POINTER legt einen smartpointer an, der eine kopie des parameter enthält, name ist param_name
    PARAM_NOCOPY legt eine Kopie des params an, indem eine userdefinierte funktion aufgerufen wird.

    #ifndef ts_DesignByContract_HEADER
    #define ts_DesignByContract_HEADER
    
    #include <stdexcept>
    #include <ostream>
    #include <string>
    
    namespace DesignByContract
    {
    class DesignByContractException : public std::runtime_error
    {
    private:
        std::string file;
        unsigned long line;
        std::string msg;
    
    protected:
        DesignByContractException(const std::string& file, unsigned long line, const std::string& msg)
        : std::runtime_error(msg), file(file), line(line), msg(msg)
        {}
    
    public:
        virtual void writeAt(std::ostream& stream) const
        {
            stream<<"File: "<<file<<" ("<<line<<") - "<<msg<<'\n';
        }
        ~DesignByContractException() throw()
        {}
    };
    
    class PreconditionException : public DesignByContractException
    {
    public:
        PreconditionException(const std::string& file, unsigned long line, const std::string& msg="")
        : DesignByContractException(file,line,msg==""?"Precondition Violation" : "Precondition Violation: "+msg)
        {}
        ~PreconditionException() throw()
        {}
    };
    
    class PostconditionException : public DesignByContractException
    {
    public:
        PostconditionException(const std::string& file, unsigned long line, const std::string& msg="")
        : DesignByContractException(file,line,msg==""?"Postcondition Violation" : "Postcondition Violation: "+msg)
        {}
        ~PostconditionException() throw()
        {}
    };
    
    class InvariantException : public DesignByContractException
    {
    public:
        InvariantException(const std::string& file, unsigned long line, const std::string& msg="")
        : DesignByContractException(file,line,msg==""?"Invariant Violation" : "Invariant Violation: "+msg)
        {}
        ~InvariantException() throw()
        {}
    
    };
    
    template<typename Type>
    class DBCSmartPointer
    {
    private:
        Type* value;
        Type* orig;
        DBCSmartPointer(DBCSmartPointer<Type>&);
        void operator=(DBCSmartPointer<Type>&);
        DBCSmartPointer();
    
    public:
        DBCSmartPointer(Type* value)
            : value(new Type(*value)), orig(value)
        {
        }
    
        ~DBCSmartPointer()
        {
            delete value;
        }
    
        Type* operator->()
        {
            return value;
        }
    
        const Type* const operator->() const
        {
            return value;
        }
    
        Type& operator*()
        {
            return *value;
        }
    
        const Type& operator*() const
        {
            return *value;
        }
    
        template<typename T>
        friend inline bool operator==(const DBCSmartPointer<T>& a, T* b)
        {
            return a.orig==b;
        }
    };
    
    }
    
    std::ostream& operator<<(std::ostream& stream, const DesignByContract::DesignByContractException& e)
    {
        e.writeAt(stream);
        return stream;
    }
    
    #ifdef DESIGNBYCONTRACT
        #define PARAM(variable, type) \
            type param_##variable (variable)
        #define PARAM_POINTER(variable, type) \
            ::DesignByContract::DBCSmartPointer<type> param_##variable(variable)
        #define PARAM_NOCCOPY(variable, new_type, function) \
            new_type param_##variable(function)
    
        #define REQUIRE(expression) \
            do{if(!(expression)) throw ::DesignByContract::PreconditionException(__FILE__,__LINE__);}while(false)
        #define REQUIRET(expression, text) \
            do{if(!(expression)) throw ::DesignByContract::PreconditionException(__FILE__,__LINE__,(text));}while(false)
    
        #define ENSURE(expression) \
            do{if(!(expression)) throw ::DesignByContract::PostconditionException(__FILE__,__LINE__);}while(false)
        #define ENSURET(expression, text) \
            do{if(!(expression)) throw ::DesignByContract::PostconditionException(__FILE__,__LINE__,(text));}while(false)
    
        #define INVARIANT() \
            do{if(!(this->Invariant())) throw ::DesignByContract::InvariantException(__FILE__,__LINE__);}while(false)
    
        #define INVARIANTF(function) \
            do{if(!(function)) throw ::DesignByContract::InvariantException(__FILE__,__LINE__);}while(false)
    
        #define INVARIANTT(text) \
            do{if(!(this->Invariant())) throw ::DesignByContract::InvariantException(__FILE__,__LINE__,(text));}while(false)
    
        #define INVARIANTFT(function, text) \
            do{if(!(function)) throw ::DesignByContract::InvariantException(__FILE__,__LINE__,(text));}while(false)
    #else
        #define PARAM(variable) \
            do{}while(false)
        #define PARAM_NOCOPY(variable, new_type, method) \
            do{}while(false)
    
        #define REQUIRE(expression) \
            do{}while(false)
        #define REQUIRET(expression, text) \
            do{}while(false)
    
        #define ENSURE(expression) \
            do{}while(false)
        #define ENSURET(expression, text) \
            do{}while(false)
    
        #define INVARIANT() \
            do{}while(false)
    
        #define INVARIANTF(function) \
            do{}while(false)
    
        #define INVARIANTE(text) \
            do{}while(false)
    
        #define INVARIANTFE(function, text) \
            do{}while(false)
    
    #endif
    
    #endif
    

    bitte sagt mir eure meinung, danke 🙂



  • Original erstellt von Shade Of Mine:
    bitte sagt mir eure meinung, danke 🙂

    zu hoch für mich 😞

    könntest du mir erklären was ich jetzt genau damit machen kann?!
    bzw. was mir dein (laaanger) code bringt?!?



  • Original erstellt von <aka>:
    könntest du mir erklären was ich jetzt genau damit machen kann?!

    naja, das sind einige Makros um das Design by Contract Prinzip in C++ zu verwenden.

    interessant ist hauptsächlich PARAM sowie PARAM_POINTER - denn dies beiden sachen sind mir noch nie untergekommen, aber IMHO ist es wichtig in der ENSURE klausel die originalen übergebenen variablen zu kennen.

    wenn du nicht weisst, was design by contract ist, dann google mal danach - da gibts sicher vieles lesenswertes.



  • Hallo,
    das Prinzip von Design-by-Contract gefällt mir schon sehr gut. Allerdings gibt es in meinen Augen einfachere/schönere Methoden dort hinzukommen bzw. zu prüfen ob Verträge eingehalten werden.

    Mir fällt da z.B. spontan test-first programming ein. Damit bastle ich mir erst die Tests, damit das Interface und damit auch die Verträge. Da die Tests zu jedem Build-cycle gehören, wird dabei geprüft, ob noch alles stimmt. Natürlich habe ich dadurch nur ein paar ausgewählte Tests und nicht eine ständige Überwachung, dafür dokumentieren die Tests eine Klasse (und ihre Anwendung) IMHO besser.

    Naja, ich muss mir das noch mal genauer durch den Kopf gehen lassen. Weiß noch nicht ob mir dein Ansatz gefällt 🙂

    Nur mal kurz:

    PARAM(c,char);

    Was soll das?



  • was soll immer dieses while(false) da? 😮 😮



  • @Hume:
    mittels PARAM kannst du den übergeben parameter 'sichern'

    dh, du legst eine kopie ab, die allerdings nur da ist, wenn du auch DBC verwenden willst.

    vorteil ist zB

    void add(int& a, int b) //asm style add
    {
    PARAM(a,int);
    a+=b;
    ENSURE( (param_a-b)==a );
    }

    do{}while ist dafür da, um ein semikolon nach der anweisung zu erzwingen, sonst schreibt vielleicht jemand

    PARAM(a,int)

    und das führt uU zu problemen...

    zB
    if(foo)PARAM(a,int)
    do_something();

    wenn PARAM jetzt nicht da ist (weil dbc ausgeschaltet ist) dann gibts probleme...


Anmelden zum Antworten