Re: Criteria to decide what is private?



"Daniel T." wrote:

Miguel Oliveira e Silva <mos@xxxxxxxxx> wrote:
"Daniel T." wrote:

To recap:

interface Range
{
// invariant: low() <= high()
int low();
int high();
}

I said that putting sets in the class would expose the invariant
and you asked, "which invariant?"

The invariant "low() <= high()." If we put sets in the class with
conventional definitions, then clients will have to make sure the
invariant stays true. That's supposed to be the Range class' job.

It is Range's responsibility to ensure its invariant, regardless of
its services being only queries ("getters") or queries and commands
("setters").

Agreed.

If by any chance an incorrectly used command might compromise the
invariant, then it is the class (Range) responsibility to attach a
proper precondition to that service (in which case, the client is
responsible to ensure, not the invariant, but the command's
precondition).

Now, keep in mind that if a precondition is not satisfied, that
represents a bug in the client, not the server (i.e., it is the client's
responsibility to satisfy the precondition.) So let's say that we put
the precondition "v <= high()" and a post condition "low() == v" where
'v' is the parameter of the method. low() == v and v <= high() therefore
low() <= high(). You are thus making the precondition the same as the
invariant.

It is not. The invariant applies to the object (internal) state.
The parameter "v" is not part of the object's state (there is
a substantial difference between the condition "before-action"
and the condition "after-action" when asserting the correctness
of a program).

The client should never be obligated to know the supplier's
implementation code (which is the one which might break
invariants). You are assuming that the client is assured that the
value of v will became low()'s value (that might not be true because
a precondition should fail before such disastrous event could ever
takes place).

The failure was due to a false precondition, not a false invariant.

Which such a precondition, the invariant becomes the clients'
responsibility.

No (never).

The invariant is the supplier responsibility. He may, however, choose to
impose preconditions to clients in some of its services in order to make
sure that it can meet its part of the contract (which is quite different
than saying that the invariant is the responsibility of clients).

It is the implementation code of setLow(v):

attr_low = v; // assuming a protected attribute 'attr_low' to represent low

that ensures the invariant. This code belongs to the supplier (not the client).

Yet, we both agreed that it is the Range class' responsibility to ensure
the invariant...

I certainly agree with that.
(I'm not so sure about you.)

In fact it is the presence of commands that makes the existence
of explicit (and testable as in Eiffel) invariants so important
for maximizing class reliability (correctness and robustness).

The invariant is exposed is when - as you correctly put
it - it is the clients responsibility to ensure it:
meaning when there exists a public writable attribute.

Lets take a look to a simple example (Eiffel syntax):

class PERSON

public

age: INTEGER; -- in Eiffel public attributes can only be used a "rvalues"

set_age(a: INTEGER) is
require -- precondition
non_negative_age: a >= 0
do
age := a
ensure -- postcondition
age = a
end;

invariant

age >= 0

end -- PERSON

You have demonstrated the exact same problem using only one variable.
The precondition is "a >= 0" and the postcondition is "age = a". a >= 0
and age = a therefore age >= 0. The invariant is expaosed to the client
and for exactly the same reason.

It is not. It is a precondition failure (not an invariant failure).
This property is extremely important to build reliable programs
because when a precondition fails the supplier object remains
in a stable state (its invariant holds), hence it can be used in the
future by other clients (which, for example in a concurrent
multi-threaded program, might even be part of programs
of different threads).

I advise you to read a good source on DbC (Chapters 6, 11
and 12 from Meyer's Object-Oriented Software Construction
2ed, would be an excellent choice).

If you add a precondition to the method that L <= high(), then you
are literally requiring the client to ensure that low() <= high().
The class is the one that is supposed to do that.

The client is not required to ensure the invariant (he is not
required to know what is happening inside setLow method
implementation). He is correctly required to ensure the method
precondition (which is L <= high() and not low() <= high() ).

The post-condition tells the client what is happening inside the setLow
method (the client doesn't know exactly how it is implemented, but he
knows abstractly what it does.)

The postcondition tells the client what is the desired result
of whatever happens inside the method's implementation
(and of any of its possible redefinitions in descendant classes).

It is (always) the class responsibility to ensure its invariant.
The client is required to observe preconditions, in which
case it is ensured (if the class is correct) that both the
method's postcondition and the class invariant will hold.

CLIENT: precondition

SUPPLIER: invariant and postcondition

Since (due to program errors) a client might try to use
objects in unstable states (which is meaningless, and
even if the precondition is true the class might be
unable to ensure postconditions) the full correctness
condition is:

{INV and PRE} routine-body {INV and POST}

The invariant which is required to be observed at
precondition time has nothing to do with current's
client use of the object. It is the consequence of
past object uses. On the other hand, the invariant
after the execution body is required to be met by
routine-body (see the difference?).

-miguel

--
Miguel Oliveira e Silva
DETI-IEETA, Universidade de Aveiro, PORTUGAL


.



Relevant Pages

  • Re: Opinions on the Law Of Demeter
    ... What you describe above is the command ... Since neither of these run afoul of the LoD, ... >> supposed to ensure its invariant, ... What I did was have Client ...
    (comp.object)
  • Re: Criteria to decide what is private?
    ... I said that putting sets in the class would expose the invariant and you ... precondition to that service (in which case, ... literally requiring the client to ensure that low<= high. ...
    (comp.object)
  • Re: Tell, Dont Ask
    ... That precondition forces the client to keep track of what order the messages are sent to Range and help ensure the Range object's invariants. ... The whole point was for Range to ensure its own invariant. ... But there is a big difference between a contract that requires the context to provide values in a specific order and a contract that requires the context to provide pairs of values in any order. ... Here my solution was constrained by separate setHigh and SetLow interface methods in the original example. ...
    (comp.object)
  • Re: Criteria to decide what is private?
    ... I said that putting sets in the class would expose the invariant ... It is Range's responsibility to ensure its invariant, ... proper precondition to that service (in which case, ... represents a bug in the client, not the server (i.e., it is the client's ...
    (comp.object)
  • Re: Tell, Dont Ask
    ... That is, when Stack::empty returns TRUE, then the client needs to do something other than processing an element. ... we can deal with that by having Stack::pop return NULL if the Stack is empty. ... Sure the invariant will be kept, but not because of any effort on the part of the Range class, users of Range are the ones that have to make sure the invariant holds... ... this approach fails when something else in the overall solution context says that the client really never should supply a value less than the current self.low. ...
    (comp.object)