• No results found

3.5 Further Semantics of Transactional Methods

3.5.1 Semantics Illustrated Using Principal Cases

This section starts by introducing a base class. We use this class in the following sections to gradually build more complex object structures, and investigate the eect of the transactional methods on this structures. First, in Section 3.5.1.2 we look at the eects of local transactional methods. Next, in Section 3.5.1.3 we look at the eect of one transactional method calling another. In Section 3.5.1.4 we look at the eect when a transactional method calls another method in a dierent class. Finally, in Section 3.5.1.5 we examine the eect when using inheritance and overriding transactional methods in subclasses.

3.5.1.1 Base Class

The base class used for this explanation is implemented as shown in Figure 3.1.

+getState() : int

+setState(in state : int) : void +getOtherState() : int

+setOtherState(in otherState : int) : void +getReadOnlyState() : int +random() : int -state : int -otherState : int -readOnlyState : int ValueState

Figure 3.1: ValueState Class

This class encapsulates three attributes, accessible using the property concept. The attributes state and otherState are used as read/write properties, the third attribute readOnlyState is used as a read-property. According to the property concept, the necessary get and set methods are provided.

In Sections 3.5.1.2 to 3.5.1.5, a transactional method will change the state property non-deterministically and throw exceptions if the value of state crosses its acceptable range. The transactional methods do not change the otherState and readOnlyState properties, tests will assert their value is unchanged in com- pliance to the inertia axiom on mutators [8].

3.5.1.2 Simple Method

We start with a non-transactional method incrementState, as shown in Algo- rithm 21.

Algorithm 21 Common Implementation of incrementState

/** @throws IllegalStateException

* not(new.getState() in [0..10])

*/

public void incrementState() throws IllegalStateException { setState(getState() + random());

if ((getState() < 0) || (getState() > 10))

throw new IllegalStateException("State crosses [0..10]"); }

The method incrementState changes the state attribute of the implicit object. In the example, the method random() generates integer values in the range [1..5]. Since the method adds a non-deterministic value to the attribute state, there is a possibility that an IllegalStateException is thrown. In case of an exception, the common implementation of this method leaves the state attribute in a modied (dirty) state. So the calling object is informed about the exception, but the original state of the object is not restored, causing it to reside in an inconsistent state.

We could improve this implementation by using the strategy shown in Al- gorithm 22. Note that instead of using a throws Javadoc-tag, we now use a throwsClean Javadoc-tag. This way we inform callers that the state of the im- plicit object is unchanged in case of an IllegalStateException.

Algorithm 22 Clean Implementation of incrementState

/** @throwsClean IllegalStateException

* not(new.getState() in [0..10])

*/

public void incrementState() throws IllegalStateException { int oldState = getState();

setState(oldState + random());

if ((getState() < 0) || (getState() > 10)) { setState(oldState);

throw new IllegalStateException("State crosses [0..10]"); }

}

To accomplish the throwsClean eect, the developer has to record the old value of the state attribute and use this recorded value to set back the state attribute before throwing the exception. As is the case in Section 3.3.2, the provisioning of atomic behavior tends to signicantly increase the responsibilities of developers. Note also that the increase in code size is even higher when multiple

3.5. FURTHER SEMANTICS OF TRANSACTIONAL METHODS 57 elds are written, and that the technique of state recording must be applied every time transactional behavior is desired.

Finally, as we already mentioned in Section 3.3.3, throwsClean is part of an informal specication language, so no one can guarantee that the state is cor- rectly reverted in case of an exception. We expect that the developer of the incrementState method respects the Javadoc contract, but there is no possibil- ity to verify it using the Java compiler.

The implementation of this same method incrementState as a transactional method, uses the best of both worlds. As shown in Algorithm 23, it uses the com- mon code implementation of Algorithm 21, while maintaining the throwsClean contract of the implementation of Algorithm 22.

Algorithm 23 Transactional Implementation of incrementState

/** @throwsClean IllegalStateException

* not(new.getState() in [0..10])

*/

public transactional void incrementState() throws IllegalStateException {

setState(getState() + random());

if ((getState() < 0) || (getState() > 10))

throw new IllegalStateException("State crosses [0..10]"); }

Note that the transactional method incrementstate is on purpose imple- mented non-deterministically. Hence, when writing tests for this method, we are unable to predict whether an exception is thrown or not. Section 3.7 elaborates on how to write tests for non-deterministic methods. The entire test suites, val- idating the dynamic behavior of the transactional method incrementState are left out of this dissertation.

Mind that the samples in this dissertation leave out the abort-block entirely in all cases where default aborting behavior is used.

3.5.1.3 Nested Method, Same Object

In this section we explain additional aspects of the semantics of the transactional concept. We will illustrate its use with nested methods within the same class. As shown in Figure 3.2, the class called NestedMethods provides the methods nested and nesting. Only the nested method directly changes the implicit object, but both the nesting and the nested method throw an IllegalStateException. The random method generates integer values in the range 1..5, therefore the IllegalStateException is thrown 20% of the times the nesting and nested methods are executed. This way, both methods are possibly responsible for halting the execution of the nested method call.

+...() : ... +nested() : void +nesting() : void -... : ...

NestedMethods

void nesting( ) throws AbortException { nested();

if ( random() == 2 )

throw new AbortException("Nesting"); }

Figure 3.2: Nested Methods

The examined scenarios are listed in Table 3.1. Only the cases where an ex- ceptional result is reached are considered. The rst two columns of this table note whether the nested method, respectively the nesting method, is transactional or not. The third column shows which method throws the exception. Either it is thrown in the nested method, or it is thrown in the nesting method and it propagates to the nested. The last column shows the consistency of the implicit object.

nested method nesting method Exception in Object State

1 - - nested inconsistent

2 - - nesting inconsistent

3 - transactional nested consistent

4 - transactional nesting consistent

5 transactional transactional nested consistent 6 transactional transactional nesting consistent

7 transactional - nested consistent

8 transactional - nesting inconsistent

Table 3.1: Nested Methods: State of the implicit Object when Exception is Thrown

In the rst two rows none of the methods are transactional. This is the common implementation for Figure 3.2, leading to the inconsistent state of the implicit object.

On the third row the nesting method is made transactional and an exception is thrown in the nested method. The nested method will already have changed the object's state, but the transactional behavior of the nesting method sets this state back to the original value. This way, the implicit object turns back to the consistent state.

3.5. FURTHER SEMANTICS OF TRANSACTIONAL METHODS 59 Rows four through seven are cases in which there is a match between the place where the exception is thrown, and the transactional property of the correspond- ing method. In each of these cases the transactional behavior of the method sets the implicit object back to the consistent state.

The bottom row of the table shows a situation where an inconsistent object state is reached. But this situation is not surprising. The nested transactional method still runs in accordance to its transactional behavior. However, the nest- ing method is throwing the exception and the developer decided not to use the transactional behavior on it, hereby leaving the object in an inconsistent state. Whether this behavior is expected or not, the compiler supporting transactional behavior, might generate an error because it detects that transactional methods are called from non-transactional ones.

3.5.1.4 Nested Method, Dierent Object

In this section we illustrate the problems occurring when transactional and non- transactional methods are used on dierent objects (instead of on the same object as in Section 3.5.1.3). We illustrate these problems using the composite-pattern as shown in Figure 3.3. In this conguration an object of the class Whole is com- posed of one object of class Part. The class Whole has a non-deterministic method named incrementState that can throw exceptions. This method, in turn, calls the non-deterministic incrementState method of its containing Part-object. Be- sides, the incrementState method in the Part class can throw exceptions.

void incrementState( ) throws AbortException { getPart().incrementState();

} +getState() : int

+setState(in state : int) : void +getOtherState() : int

+setOtherState(in otherState : int) : void +getReadOnlyState() : int +random() : int +incrementState() : void -state : int -otherState : int -readOnlyState : int Part +getPart() : Part +incrementState() : void -part : Part Whole 1 1

Figure 3.3: Composition of Objects

In the examined scenarios we call the incrementState method of the Whole object. The common implementation of these methods leaves the Part object in

an inconsistent state, since both methods can throw exceptions based on dierent causes.

Again we can write out all the dierent combinations for making both incre- mentState methods transactional. Since this table looks very similar to Table 3.1 in Section 3.5.1.3, we leave it out of this chapter.

The main dierence between this section and Section 3.5.1.3, is the way the objects are reset to the consistent state. In the composition scenarios, not only the implicit object of type Whole has to be reset, but also the composed object of type Part. Section 3.6 explains how the state is set back for all objects involved in a transactional method.

3.5.1.5 Overriding Methods

Figure 3.4 shows a class Base with a non-deterministic incrementState method that changes the state of the implicit object. The class Extended is a subtype of the class Base; it overrides the method incrementState.

+getState() : int

+setState(in state : int) : void +getOtherState() : int

+setOtherState(in otherState : int) : void +getReadOnlyState() : int +random() : int +incrementState() : void -state : int -otherState : int -readOnlyState : int Base +incrementState() : void Extended

Figure 3.4: Overriding Methods in Subclasses

In the common implementation of both classes, an inconsistent object state can occur when the incrementState method changes the implicit object and throws an exception.

When using specialization to override methods in subclasses, care must be taken to respect the substitution principle of Liskov [74]. Table 3.2 shows what is allowed in accordance to the substitution principle.

3.5. FURTHER SEMANTICS OF TRANSACTIONAL METHODS 61 The substitution principle of Liskov states that at any time, for a given client, an object of a superclass can be substituted by an object of one of its subclasses, without the client noticing this. A client does not observe any dierence when a specic method that is applied to a superclass is replaced with the same method call on a subclass. When using the design by contract paradigm [81, 83], as a consequence of this principle pre-conditions can be weakened and post-conditions can be strengthened in a subclass. Other consequences for the specication of an overriding method are: reducing the number of thrown exceptions, strengthening the return type, widening access rights and argument types.

Base Extended object state

1 - - inconsistent

2 - transactional consistent

3 transactional transactional consistent

4 transactional - -

Table 3.2: Overriding Methods: State of the Implicit Object when an Exception is Thrown

The rst row illustrates the case for current object-oriented programming lan- guages. As already motivated in Section 3.3.1, transactional behavior is currently not supported, leading to inconsistent objects when method execution is halted. Method overriding does not alter this behavior, so the implicit object will reside in an inconsistent state.

The second row shows a transactional method overriding a non-transactional one. When an object of the Base type is replaced with an object of the Extended type, the eect of the incrementState method becomes stronger. This is because Liskov's substitution principle dictates that an overriding method should realize at least those post-conditions that were enforced on the overridden method. Even more important is that the overriding method is qualied as being transactional. This additional behavior will guarantee that the implicit object is reverted to a consistent state in case method execution halts. Thus, overriding methods can restore an object to a consistent state even though the overridden method is not declared transactional.

The third row illustrates a transactional method overridden by a transactional method. In this case the consistent state is guaranteed.

In the fourth row, the base class implementation of the method is transac- tional. The overriding method must also be implemented as transactional, to be in accordance with the substitution principle of Liskov. This can be com- pared with the visibility modier. The compiler generates an error if a developer reduces the visibility of methods in subclasses. In the same way, the compiler generates an error if the developer overrides a transactional method with a non-

transactional implementation. So the situation depicted in the fourth row of Table 3.2 will never occur in compiled programs.