8
This article presents an interface IList<T>
containing the methods Add(T item)
and Count()
. The contract of that interface expects that when an item is added, the method Count()
reflects the new number of items in the list. This can even be defined in the interface documentation (a post-condition of the contract).
However, the interface itself, in most languages, does not guarantee that this contract will be fulfilled.
He proposes to solve this by creating a subclass CollectionContract<T>
implementing the logic of fulfilling the contract:
public abstract class CollectionContract<T> : IList<T> {
public void Add(T item){
AddCore(item);
this.count++;
}
public int Count {
get { return this.count; }
}
protected abstract void AddCore(T item);
private int count;
...
}
One of the comments argues that such an approach could introduce a overhead unacceptable at execution time even when overhead is limited to builds of debug:
In computer Science and software Engineering, the Terms and conditions on a Contract are typically referred to as pre- and post-conditions. While Abstract classes could be used to validate that These conditions are satisfied through the use of the template-method Pattern, this approach can introduce Unacceptable run-time overhead Even when Limited to debug builds.
He also says that in languages without multiple inheritance, such as C# (and Java), this approach profoundly impacts class design. Moreover, the logic of fulfilling the contract is making assumptions that make the subclass rigid and inflexible (such as assuming that the counter must be stored in a private field, and that this field must be of the whole type).
Finally, it proposes that the fulfillment of the contract be guaranteed by means of unitary tests carried out on the classes that implement the interface (although this is not an ideal solution, since it is external to the contract and to the language).
My questions are:
What kind of situation does the commentator refer to when talking about overhead at runtime?
Such situations where the overhead occur are difficult or easy to predict? (for the purpose of helping to decide when it is feasible to adopt the solution of the article)
In fact, answer me directly: it is always incorrect to try to guarantee a contract in the manner of the article?
Even in languages that allow multiple inheritance?
(For anyone interested in the context of the subject, I came to this article on starting interfaces this one and passing by this other. And this one is also interesting).
One thing that occurred to me: when he talks about overhead in debug builds, he’s referring to assertions, like the ones in Java? Because otherwise I don’t understand how it can be proved the implementation of a contract at runtime in this type of language. But otherwise it all makes a lot of sense, if nobody has anything to add I’ll end up accepting the answer.
– Piovezan
@Piovezan I don’t know exactly what he’s talking about, but I understood it in a similar way to you. There are many forms of conditional compilation - for example when doing
if
with a false constant, many compilers do not generate the code, knowing it will never run. This allows you to create a debug build (putting the constant as true), with all the checks, but when it comes to putting the code into production runs in "dumb mode" - without checking or proving anything, as you noticed. P.S. I suggest to wait more before accepting, this type of question usually generates interesting answers.– mgibsonbr