A Method for Support for Design By Contract on the .NET platform

advertisement
12
Chapters 2 & 6
A Method for Support for Design By Contract on the
.NET platform
Andreas Sjögren
Department of Computer Science
Mälardalen University
Västerås, Sweden
andreas.sjogren@mdh.se
Abstract: It is widely acknowledged that semantic information about a component's operations is needed
to use a component effectively. There is clearly a need for more formal specifications methods for
components. However, generally there is a gap in software engineering between formal specifications and
practical applications of these. Techniques based on Design By Contract are very useful for ComponentBased Systems, and at the same time quite simple to understand and use.
In this paper we discusses different practical implementations and models for support for Design By
Contract in Object-Oriented Programming Languages and Component-Based Development. We also
present a proposal for a method to provide support for Design By Contract for components on the .NET
platform, implemented in any .NET programming language. Component interfaces can be specified in
UML, with invariants, pre- and post-conditions, which later can be evaluated during runtime. We use the
attributes of the .NET platform to implement specifications, since the attributes of a component are
provided as metadata during runtime and thereby provide possibilities for monitoring violations to the
assertions.
1
Introduction
Reliability is today a very important concern in the software industry. Reliability is a system's
ability to perform its job according to the specification (correctness) and to handle abnormal
situations (robustness). Reliability is extra important with component software because of the
focus of reusability and off-the-shelf components. Unless we can obtain reusable software
components whose correctness we can trust much more than we trust the correctness of usual
software, reusability is unlikely to succeed. It is well known how to specify a component
interface on the syntactic level, but for semantic specifications practical approaches are rare.
CBSE is still lacking, as all software engineering disciplines, in many sense in the connection
between existing formal methods and the practical application of these.
To ensure that component software will perform properly we need a useful, systematic approach
to specify, implement and verify components and their relations in a system. This paper
discusses a method for this, called Design By Contract (DBC) [1]. Under the DBC theory, a
software system is viewed as a set of communicating components whose interaction is based on
contracts, precisely defined specifications of the mutual obligations.
In Section 2 we will discuss the basic concepts of DBC. In Section 3 we explore different
methods and practical implementations for providing support for Design By Contract. In Section
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
Chapters 2 & 6
13
4 we present a method for supporting these mechanisms and providing a way to monitor
violations to the constraints during runtime. In Section 5 we discusses future work on the
method discussed in Section 4. Finally, in Section 6 we summarize the paper.
2
Design By Contract
Bertrand Meyer from Interactive Software Engineering (ISE) developed Design By Contract as a
part of the object-oriented programming language Eiffel [1,2]. The DBC theory associates a
specification with every software element. These specifications, called contracts, describe the
interaction of the components with the rest of the world. It is a general valuable design
technique, which provides an effective framework for debugging, testing and, more generally,
quality assurance, and it also gives additional documentation about the software components. In
this model the relationships between a client and a supplier are viewed as a contract that takes
the form of assertions, as boolean invariants, pre-conditions and post-conditions. The preconditions are the contractual obligations a client must satisfy. In Eiffel the notation for this is
require. The post-conditions are the contractual obligations the supplier ensures to fulfill. In
Eiffel the notation for this is ensure. The invariants constrain all services of all instances of the
supplier. In Eiffel the child classes automatically inherits all the assertions from the parents. A
method re-declaration may keep or weaken the pre-conditions, and it may keep or strengthen the
post-conditions. Here is an example of a method push for a class Stack in Eiffel, pushing an
element has the pre-condition that the stack is not full before the invocation and the postcondition that the top element will be the top element of the stack after the invocation (the
implementation uses some methods of the class which is not presented, but there usage are quite
obvious):
push (x:T) is
require
not full
do
–-push element v of type T to the stack
–-pre-condition that the stack is not full
-- ..the pushing algorithm..
ensure
not isEmpty –-the stack cannot be empty afterwards
getItem = x –-the pushed element is the top on the stack
end
The question of what will happen when one of these conditions fails during execution is
depending on whether the assertions are monitored during runtime and is not a question of the
actual design technique. A runtime violation of an assertion is always a manifestation of a
software bug [1]. A pre-condition violation indicates a bug in the client and a post-condition
violation a bug in the supplier.
When talking about contracts in CBSE we mean interface contracts, and Bachman, et al, argues
in [3] that contracts shift the focus of specification of components to a specification of patterns
of interactions. DBC is very well suitable for specifying interactions, and thereby for specifying
components.
3
Implementations of Support for Design By Contract
In this section we will explore some other practical implementations and suggested methods for
support for DBC for Object-Oriented and Component-Based Systems.
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
14
Chapters 2 & 6
In principle we can identify five different methods of using DBC in a programming language or
environment, which does not support the theory from the beginning. The first alternative, is
simply add comments in a structured fashion which provides contracts for functions or classes,
but this is not very useful since we want to have executable contracts which can be monitored
during runtime. A better approach is to use either formal comments or special macros and run
the code through a preprocessor, for instance by using a tool, which both adds the macros and
runs the preprocessor. This notion somewhat relates to the assert macro in the programming
languages C and C++, which takes an expression and an error message. If the expression is false
it writes the message and terminate the execution. Another alternative is to add new mechanism
to the programming language itself. This is of course in many senses the best alternative, but it is
often not that easy done and sometimes not even possible. The fourth alternative is to use a
library approach where we add functions for supporting the DBC mechanisms, and suggests
conventions that must be followed for calling functions. The last identified alternative is to use a
tool, which makes is possible to write the contracts in another language, which supports DBC
(e.g. Eiffel) and then transforms the code to legal code for the original language.
Often implementation of these techniques for providing DBC has (as for Eiffel itself) the
disadvantages of the dependence of one particular programming language.
3.1
Contracts in Java
There exist several models for adding contracts to the Java programming language. One example
is the freely available iContract tool [4], which uses a preprocessor to generate Java Code with
contracts. The contracts are added as JavaDoc comments. The tool converts the comment tags
into assertion check code. The contracts has the following structure:
/**
* @pre <expr> #ExceptionName
* @post <expr>
* @invariant <expr>
*/
ExceptionName is the name of the exception to be raised if the assertion is violated. The
expressions in iContract are modeled after a own specification language, which is a subset of the
Object Constraint Language (see Section 3.5). The benefit of using contracts as comments is the
non-mandatory nature of these. The code with contracts remains completely compatible with
Java and can be processed with all Java implementations. This model provides a way of applying
DBC with monitoring in Java, but it will not allow switching contract checking dynamically.
Another model for Java is jContractor [5]. This is a purely library based approach which
together with a set of conventions support DBC in Java. I this solution it is possible to write the
contract in ordinary Java syntax. The contracts are specified in methods in a class definition
following the naming conventions. jContractor checks for these patterns in class definitions and
rewrites the classes on the fly. The approach takes advantage of the Java reflection API for
automatically adding assertions to the original methods. The advantage with this approach is that
it works with any Java implementation and does not require any special tool.
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
Chapters 2 & 6
3.2
15
Contracts in Python
Reinhold Plösch at Johannes Kepler University, Linz, has integrated DBC in the interpreted
object-oriented language Python [6]. In this solution the DBC concepts are added without
changing the language. Like for iContract the assertions are specified in comments by using the
keywords req, ensure and inv. Then a modified interpreter provides methods for parsing and
runtime check of the assertions.
3.3
Contracts in C++
There exist several models for adding contracts to the C++ programming language. An example
is a high level system developed at Johannes Kepler University, Linz, which is based on the
implementation of contracts for Python discussed in the previous section. This system
transforms Python assertions into C++ code. A parallel check class is generated for every
ordinary class. The check class contains methods fore assertion checking. This system has a lot
of technical problems in the transformation from Python classes to the C++ classes and the usage
of two different programming languages can also been considered a problem with this approach.
Another example is presented in [7]. Here all classes have to inherit from a class called
Assertions, which provides the needed mechanisms. Here is a simplified version of the Assertion
class:
class Assertions {
public:
class Exception: std::exception {
public:
Exception(const std::string& l):exception(“Assertion violation:”+l)
{}
};
//pre-conditions
virtual void require(bool b, const std::string& label) const {
if (!b)
throw Assertions::Exception(“require” + label);
}
//post-conditions
void ensure(bool b, const std::string& label) const {
if (!b)
throw Assertions::Exception(“require” + label);
if (!invariant())
throw Assertions::Exception(“invariant” + label);
}
//default invariant
virtual bool invariant() const {
return true;
}
}
An implementation of a method push of a class Stack which uses the require and ensure
methods will look like:
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
16
Chapters 2 & 6
virtual void push(const T& x) {
require(!isFull(), “not full”);
// ..do the push stuff..
ensure(!isEmpty(), “not empty”);
ensure(getItem()==x, “top element”);
}
The class Stack can have the following invariant:
virtual bool invariant() const {
return 0 <= getCount() && getCount() <= capacity();
}
If we want to handle violations to the assertions we have to catch the exceptions and take the
proper action.
3.4
Contracts on the .NET Platform
The easiest way to be able to use contracts on the .NET platform is to use the .NET version of
Eiffel. But of course all people are not likely willing to change the programming language to
Eiffel.
Arnout and Simon from ISE propose in [8] a tool they call the Contract Wizard. This tool adds a
possibility to define contracts for existing Assemblies to the .NET framework [9], independently
from the .NET language it was written in. In the toll a user can write invariants, pre- and postconditions as Eiffel expressions. The Wizard then generates Eiffel classes with the specified
contracts and calls an Eiffel compiler to generate new Assemblies with contracts, see Figure 1.
For the moment there is a discussion going on if the Contract Wizard is to be added to the main
release of Visual Studio.NET by default. One problem with this approach is that we still need to
have an existing Eiffel environment. As far as we understand it, another problem is that if you
are using another language than Eiffel you cannot add contracts to the components during the
design phase or the implementation, this has to be done in a separate phase after the actual
implementation of the components.
Figure 1. Generation of .NET assemblies with contracts.
3.5
Using Contracts in the Design Phase
To use contracts in the design phase of components we can use a specification language. There
exist different specification languages for specifying invariants, pre- and post-conditions. One
example is the object constraint language (OCL) [10], which is a part of the UML standard [11].
OCL is a textual language that allows construction of logical expressions. The purpose of OCL
is only to improve the precision of a UML specification.
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
Chapters 2 & 6
17
Cheesman and Daniels [12] describe how components can be specified in an extended UML
version and OCL with contractually specified interfaces. Here the contracts consist of invariants,
pre-conditions, post-conditions and intra interface constraints. They distinguish between two
types of contracts:
1. Usage – the contract between an component’s interface and its clients
2. Realization – the contract between a component specification and its implementation
The primary reason for keeping these things separate is to facilitate change. A change to the
realization constraints does not affect clients. This is important because it gives the ability to
change specifications that effect realization without having to change all client usage. This
method is used as a part of the method discussed in next section. To understand the (very
important) details of the first part of this method we have to understand the method in [12], in
particular the extended things in UML. For the sake of this paper we will not, because of space
and time limits, discuss this any further.
4
UML Contracts Specifications for Components on the .NET
Platform
Our method for designing components according to the DBC technique and providing a way of
monitoring and dynamically adding ways of handling violations to the assertions using more or
less standard methods and tools.
We have chosen to use the model with UML and OCL from [12] for specifying components.
The need to use another language for specifying the contracts than for the implementation of the
components approach can be considered to be a drawback of the method. However, the whole
idea is to provide a way of design components with contracts, which is not depending on any
particular programming language. Since OCL is a part of the UML standard this is a standard
specification language and it should be easy to find tools that support the language.
Unfortunately, for the moment there are few commercial UML tools that have support for OCL
despite it is a part of the UML standard. However, it is still easy to get documentation for it and
there are freely available parsers for it. The approach with OCL does not necessary have to
exclude other languages. Since we attempt to use a “componentized” structure of the whole
method we hope to be able to switch the specification language to another language if so is
desirable. The specifications will have an abstract state model connected
The components can then be implemented in any .NET programming language. The .NET
platform provides possibilities for monitoring violations to the assertions during runtime. In the
.NET framework there is support for a mechanism called attributes [9]. Using attributes on
components is a way to provide meta data. .NET makes is possible to create own customized
attributes by implementing a class which derives from the System.Attribute class. Using
.NET attributes give good support for implementing specifications for components. Attributes
can be attached to components, interfaces or methods. This feature can be utilized to implement
the assert expressions, invariants, pre- and post-conditions. An invariant attribute can only be
attached to a component and the invariant must hold true during the whole execution of the
component.
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
18
Chapters 2 & 6
Each time a component is compiled into the Microsoft intermediate language (IL) and stored in
the portable executable (PE) format, the attributes are stored as metadata. This meta data can
then be programmatically be retrieved when needed, for example, during execution of the
component.
4.1
Implementation Ideas
We have designed three different types of attributes, one for each type of condition that we want
to check during runtime. The invariant attribute can be only be used on components or classes
that have states, while pre- and post-condition attributes can be attached to methods of an
interface or component. Here is a simple example of an implementation in C# of the attribute for
a precondition (the example only shows how the attributes are used to provide the syntactic
possibility of adding pre-conditions):
//attribute class for precondition attributes
[AttributeUsage(AttributeTargets.Method)]
public class PreConditionAttribute: System.Attribute {
public PreConditionAttribute(string expression) {
m_Expression = new OCLExpression(expression);
}
public OCLExpression m_Expression;
}
//class for OCL expressions
public class OCLExpression {
public OCLExpression(string expression) {
m_OCLExpression=expression;
}
public string getExpression() {return m_OCLExpression;}
private string m_OCLExpression;
}
To enable the possibility of using another specification language than OCL we can later simply
change the OCLExpression component to a more generic expression component. If attributes
are attached to an interface they will operate in the input and output parameters. The attributes
can manually be added to components or via an UML tool.
The usage of the attributes for a method f of a class C will look like:
[ invariant <expr> ]
class C {
[ precondition <expr> ]
[ postcondition <expr> ]
f();
}
The biggest problem, and maybe the most interesting part (although not mentioned much here)
of the method, is the problem of providing the mapping of the abstract state model in the
specification to a concrete state model in the implementation. Without this mapping the
mechanisms for adding pre- and post-conditions to the code will not be much meaningful.
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
Chapters 2 & 6
4.2
19
Exception Management
To be able to have multiple strategies what to do when a condition does not hold we can
introduce exception components. An application can specify what exception strategy it wants to
use by attaching one of these exception components.
Examples of different of actions to be taken when an exception is raised are:
•
Throw an exception
•
Write a log entry in a logging tool
•
Shutdown or abort application
•
Ignore
But how to exactly handle this is up to the user and beyond the scope of this work. We will only
provide the plug-in mechanism for client components for handling violation to the assertions.
5
Future Work
There are a lot of things to work on with the method. We are still not sure what exactly we want
to do. There are some different possible tracks to take. We can in next step choose to concentrate
more on the part of designing component in UML with OCL specified contract (through some
existing tool or a modification of an existing tool) and generate the OCL attributes automatically
for the objects. Another possibility is to code the OCL expressions manually and concentrate
more on the monitoring mechanism. We have investigate and run some test for how exactly we
shall get the needed information to monitor violations to the assertions. We have to precisely
define the mapping mechanisms for the mapping between the abstract state model in the
specification and the concrete state model of the implementation.
We have also to do further investigation on related work on DBC for the component world. For
instance, DBC is applied to components in the Catalysis method [13], we have to look up more
what have been done here.
6
Summary
We have in this paper discussed briefly the basic properties and needed mechanisms of Design
By Contract. Further, we have presented some practical implementations of these techniques.
We presented a proposal for a method with support for DBC and runtime monitoring of the
constraints of components based upon existed techniques. Since we using a standard language in
form of UML and OCL for specifying the components and implementing them on the .NET
platform, which probably going to be a well used platform in the coming years, there is a chance
that this method can help to bridging the gap between the (semi) formal method and the practical
application of this method.
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
20
7
Chapters 2 & 6
References
[1] Meyer B., Applying Design by Contracts, IEEE Computer, volume 25, issue 10, 1992.
[2] Meyer B., Eiffel: The Language , Prentice Hall, 1992.
[3] Bachman, F., Bass, L., Buhman, S., Comella-Dorda, S., Long, F., Seacord, R. C., and
Wallnau, K. C., Technical Concepts of Component-Based Software Engineering, report
CMU/SEI-2000-TR-008, Software Engineering Institute, Carnegie Mellon University,
2000.
[4] Kramer R., “iContract - The Java Design by Contract Tool”, In Proceedings of
Technology of Object-Oriented Languages, TOOLS26, IEEE Computer Society, 1998.
[5] Karaorman M., Hölze U., and Bruno J., “jContractor: A Reflective Java Library to Support
Design by Contract”, In Proceedings of Metal-Level Architectures and Reflection, Lecture
Notes in Computer Science, nr 1616, Springer Verlag, 1999.
[6] Plösch R., “Design by Contract for Python”, In Proceedings of Asic Pacific Software
Engineering Conference, IEEE Computer Society, 1997.
[7] Guerreiro P., “Another Mediocre Assertion Mechanism for C++”, In Proceedings of
Technology of Object-Oriented Languages, TOOLS33, IEEE Computer Society, 2002.
[8] Arnout K. and Simon R., “The .NET Contract Wizard: adding Design by Contract to
languages other than Eiffel”, In Proceedings of TOOLS 39, IEEE Computer Society, 2001.
[9] Thai T. and Lam H., .NET Framework, O´Reilly, 2001.
[10] Warmer J. and Kleppe A., The Object Constraint Language, Addison Wesley, 1999.
[11] OMG, OMG Unified Modeling Language Specification, report version 1.3, June 1999,
OMG, 1999.
[12] Cheesman J. and Daniels J., UML Components - A Simple Process for Specifying
Component-Based Software, Addison-Wesley, 2000.
[13] D’Souza D. and Wills A. C., Objects, Components and Frameworks: The Catalysis
Approach, Addison Wesley, 1998.
Andreas Sjögren: A Method for Support for Design By Contract on the .NET platform
Extended Report for I. Crnkovic and M. Larsson (editors), “Building Reliable Component-Based Systems”,
Artech House, July 2002, ISBN 1-58053-327-2:
Chapter 2: Specification of Software Components, F. Lüders, K.-K. Lau, S.-M. Ho
Chapter 6: Semantic Integrity in Component Based Development, E. J. Nordby, M. Blom
Download