Pros and cons of securing the contract of an interface through a subclass

7

This article presents a interface IList<T> containing Add(T item) and Count() methods. The interface contract expects that when an item is added, the Count() method reflects the new quantity of items in the list. This can even be defined in the interface documentation (a contract postcondition).

However, the interface itself, in most languages, does not guarantee that this agreement will be fulfilled.

He proposes to solve this by creating a subclass CollectionContract<T> that implements the contract fulfillment logic:

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 this approach can introduce unacceptable overhead at runtime even when this overhead is limited to builds of debug :

  

In computer science and software engineering, the terms and conditions   on a contract are often referred to as pre- and post-conditions.   While abstract classes could be used to validate 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 has a profound impact on class design. Moreover, contract compliance logic is making assumptions that make the subclass rigid and inflexible (such as assuming that the counter should be stored in a private field, and that this field must be of integer type).

Finally, it proposes that contract fulfillment be guaranteed through unit tests performed on the classes that implement the interface (although this is not an ideal solution since it is external to the contract and language).

My questions are:

  • What kind of situation does the commenter refer to when he talks about overhead at run time?

  • Are these situations where overhead occurs as a rule are difficult or easy to predict? (for purposes of helping decide when it is feasible to adopt the article solution)

  • Incidentally, answer me directly: is it always wrong to try to secure a contract the way the article does?

  • Even in languages that allow multiple inheritance?

(For anyone interested in the subject context, I came up with this article on interfaces starting this here and going through this one . href="https://simpleprogrammer.com/2010/11/02/back-to-basics-what-is-an-interface/"> this here is also interesting).

    
asked by anonymous 08.07.2016 / 22:16

1 answer

8

Ensuring that a contract is fulfilled is a desirable but not always feasible concept. For in the first place it is necessary to express the terms of the contract in logic, then it is necessary to prove that the desired logic has been implemented. And this "proof", when possible, causes at least a longer build time, at the most it requires that checks be done constantly at run time. And, of course, the amount of code that is subject to bugs increases (as the contract statement itself is also code, and is also subject to errors).

In this example above there is not too much overhead , because rarely would someone implement a list without an auxiliary field to store the count. But who left the comment is right to say that this restricts the use of the interface:

  • If someone who has implemented wants to add other methods to change the list, but does not have [write] access to the count field - since it is private - how to do it? It would be necessary to call one of the methods of this abstract class, which in turn would call again a method of the concrete class, which would finally do the service. These redirects would have an unnecessary overhead .

  • Another possibility would be not exist a method in the abstract class that does what one wants. For example, if the list implementation is a linked list (or even a tree), and you want a method that puts a new element in ordering, how do you do it efficiently? If you could implement yourself - with a cursor - and at the end update the count manually, blz, but without that access you get dependent on having a method in the abstract class that meets your needs.

  • If you want a thread-safe list, you must somehow atomic to "add an element and increment the count". If the implementation that has already come ready is not atomic, this reduces your options to implement this atomicity efficiently.

  • Does the contract say that by adding an element the count has to increase? What if the element can not be inserted in the list for any reason (for example, if the list does not accept repeated elements, and what is being added is already in the list)? From what I read in the documentation for IList.Add this is a possibility, and by trying to "enforce" the contract in this way the implementation further restricts the list types that can be created, more than the contract requires.

These are just a few examples. As a rule, it is difficult to answer in the abstract whether "it is always incorrect" or not to do so, but my suspicion is yes. For if something is so well defined as to leave no room for variation, this should not even be a contract - but a concrete implementation. If the interface exists as something abstract, it is because it has been predicted that it may need to be implemented in radically different ways. By restricting implementation, you put more "barriers" that the programmer will have to transpose by trying to do something different than originally intended.

P.S. Some contracts can indeed be secured, and there is no harm in doing so (although it is not mandatory). A good example is the type system itself: Static typed languages force the programmer to only combine variables, values, methods, classes, if they all have the correct "type", and the program does not compile if this can not be verified and proved. Already those with dynamic typing do not do this during compilation, but end up having a certain overhead at runtime. This would be a counterexample, where contract assurance [in principle at least] reduces the overhead rather than increasing it.

    
09.07.2016 / 00:11