Thursday, June 6, 2013

Contracts: some thoughts about "pre-conditions post-conditions gaps" between modules

I developed some ideas about a possible use of contracts and tests with relation to "incomplete" features:  features that are demonstrable, but not releasable.
The idea is that there should be some tests that will document the violation of contracts that makes the feature not complete, and their presence will work as a reminder about.

The syntax of the contract is from  contract for java  (despite I am using some expression involving the return object that are not supported yet - I mean it is not possible to access to the members and method of the "result" return object, but only evaluate expressions involving it when it is a scalar type).

The context:
a user interface collects data, producing a "User" object that will be stored in the repository.

Suppose that we know that duplicated entities like user will make the repository inconsistent in many ways (for example, because of bad working of other methods) and still the repository does not prevent storing duplicated entities at the moment and at the same there is one of its client that cannot ensure that no pre-existing user will be added. In that case we have simply a precondition of a supplier that is not fulfilled by some client. So for example there is a user interface used to fill the data, that passes to the repository a user that already exists, and he repository does not check it, and then it may end up in an inconsistent state.
If this is going to happen, we have a contract violation that can be detected at run-time (if running with contract checking enabled) because at least the Repository declared via precondition the fair use of the method to add new users.

If we want to make the system "coherent", i.e. remove what I called the pre-post gap we need to decide that somewhere the check must be done, for example choosing one of the following ways:
- the Ui interface avoids to send already existing user to the repository, and the repository does not have to check this duplication of users, that means that the pre-conditions remain the same, and we just know that clients will ensure it will be respected.
- the UiInterface can send the duplicated User to the repository and this one will allow it, so relaxing the original precondition, because will be able to manage it (for example raising an exception that the Ui need to catch and manage and so on...).

(additional note, 25th July '13:  actually in case the additional behavior that fills the gap is done by subclasses we can see that if the Ui can be subclassed, then it will just enforce its post-conditions, ensuring something that the father class didn't, i.e. avoid sending duplicated data, but if it will be the repository that specialises taking care of eventual duplication by a subclass,  then this subclass will weaken its pre-condition.  So concretely both the cases match the stronger post-conditions, weaker pre-conditions rule)

Without one of the above, we may have a mismatch between the post-condition of the client (the Ui) and the pre-condition of the supplier (the repository):

Let's call this situation a client/supplier post-condition/pre-condition gap (shortly post/pre-gap) that "certifies" that there is a feature (adding user) involving both the collaborators that is not complete (but at least is consistent for some demonstrable test cases).

It is not different from saying that these post/pre-gap are actually bugs, but it is ok in that way, and it looks to me that using contracts and tests that expects contract violations is like having a way to run the program with checking mechanism enabled, that inhibits them to propagate through the components of the system (with a sort of "mistake proofing"/poka yoke mechanism).


1) a unit test violates the precondition expecting the exceptional error condition (the test need to be run with contract checking enabled mode, of course).

2) about client and supplier, it is clear who of them promise/expects what, and where is the violation. It is just a matter of having a further conversation about how to attribute the responsibility to achieve the goal of removing the pre/post-gap at the end of the day.

So we have: the repository knows that no duplicated user should be added, but it (the repository) won't to check it:

An implementation of the User interface without post-pre gap would be like follows.
I'd say that this perfect match means that the post-condition of the client is dual of the pre-condition of the supplier:

(the User Interface ensures that the user object that he passes to the repository does not already exists in the repository - and I am sorry for writing this "train wreck" expression in the post-condition...).

Now let's go back in the situation where there is actually this gap. I said that a test must exist that remind us how it is possible to break the precondition, and this is one, where we assume that we are trying to add a new user corresponding to another one already existing:

So, as long as a feature involves a client and a supplier that have a post-condition, pre-condition gap (and so the post-condition is not perfectly the dual of the pre-condition) we have:

- the feature is still demonstrable (as long as the data used to demo do not create cases "inside" of some  post/pre gap)
- the feature can't be released,
- to release the feature, there is the need to work primary on the test that provokes the precondition error, to change that expectation, and the outcome may be a change on the responsibility (as long as we own both the objects) or changing their own contract (pre, or post condition, or both).
A consequence is that no product should be released if we know that there is such a gap, and the presence of such test is a reminder about.

Direction and other thoughts: I haven't investigated about comparing the complexity and cost of managing this approach instead of just writing the "right thing" (features completes, without post-pre gap) at the first place.
A part of this, it still may be that we have some "political" reason for declaring this "gap" with some specific "negative test" (expecting contract violation), particularly if we don't own some of the module involved.

Note (edited 25th of July '13): I fixed some typos and added new parts. Sorry but English is not my primary language.

No comments: