| Register | FAQ | Calendar | Search | Today's Posts | Mark Forums Read |
|
#51
| |||
| |||
| 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?" Not quite. You wrote (emphasis is mine) : "For that interface, putting in the sets would expose the invariant to *outsiders* ." The invariant is *already exposed* to "outsiders" , merely by being *defined* in the type spec. What has the addition of set ops done to change this ?? (I define outsiders here as the users/inheritors of the Range type - if this is not what you intend the term to mean, feel free to provide your definition and we can re-discuss) > If we put sets in the class with conventional definitions What is the "conventional definition" ?? > then clients will have to make sure the > invariant stays true. That's supposed to be the Range class' job. Without the "conventional definition" being stated, who can say ?? But while I wait for that definition ... *** There is a relationship between invariants and pre/post conditions. FORALL type T, property P IN T : pre(P) = pre-conditions(P) AND invariant(T) post(P) = post-conditions(P) AND invariant(T) The Range *implementor* is required to ensure its invariants always hold. For any property that is accessed by a user, the Range implementor is also required to ensure that the post-conditions for that property hold. The *user* is required to ensure for any property accessed, that *pre-conditions(P)* for that property hold, *before* the property is accessed. > If you add "setLow( int L )" to the interface above, what will be its > postcondition? We have : pre-conditions(setLow) = {} post-conditions(setLow) = {} invariant(Range) = low() <= high() So: pre(setLow) = post(setLow) = (low() <= high() ) I have a Range instance R, that currently represents the range [2,3] . I do the following : R.setLow(4) ; What happens with the correctness artifacts ?? - pre(setLow) = true. obviously - post(setLow) = true. obviously What are the subsequent values of high/low after invoking setLow ?? UNDEFINED !!! The Range type has *no statement* of what those values will be. All we know is whatever the values are, low <= high. > If you make it: low() == L Now we have : pre(setLow) = low() <= high() post(setLow) = (low() == L) AND (low() <= high() ) I have a Range instance R, that currently represents the range [2,3] . I do the following : R.setLow(4) ; What happens with the correctness artifacts ?? - pre(setLow) = true. obviously - post(setLow) = true. obviously What are the subsequent values of high/low after invoking setLow ?? low = 4 high = UNDEFINED !!! The Range type has *no statement* of what the high value will be. All we know is whatever the values are, low <= high. > then what do you do if someone calls > myRange.setLow( myRange.high() + 2 ) ? See above. > If you add a precondition to the method that L <= high() Now we have : pre(setLow) = (L <= high) AND (low() <= high() ) post(setLow) = (low() == L) AND (low() <= high() ) I have a Range instance R, that currently represents the range [2,3] . I do the following : R.setLow(4) ; What happens with the correctness artifacts ?? - pre(setLow) = false obviously > then you are > literally requiring the client to ensure that low() <= high(). The user is required to provide a value for L less than or equal to the value of R.high *at that time* . > The class is the one that is supposed to do that. Supposed to do what (goto *** ) ?? > Of course you could weaken the postcondition of setLow(int) to something > like: low() == min( L, high() ) but then it won't pass the conventional > definition of a setter. Awaiting the "conventional definition" of a setter ... Regards, Steven Perryman |
|
#52
| |||
| |||
| Miguel Oliveira e Silva <mos{}det.ua.pt> 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. Which such a precondition, the invariant becomes the clients' responsibility. Yet, we both agreed that it is the Range class' responsibility to ensure the invariant... > 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. > > 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.) |
|
#53
| |||
| |||
| On 2007-01-12 16:06:08 -0600, "Anthony Paul" <anthonypaulo{}> said: > While I can see that the 'private' modifier has > its uses, I'm puzzled as to why it's advocated so much given that one > of the strong points of OO is extensibility. The Open-Closed Principle of OOD (See http://www.objectmentor.com/resources/articles/ocp.pdf) says that objects should be open for extension but closed for modification. In other words, you should be able to change what a module does without changing the module. Extensibility, in OO, is best achieved when you keep the code you are extending safe from modification. How do you protect code from the forces that would try to modify it? You keep the variables it depends upon private. If a variable is not private, then it is open to used in a way that the "owner" of that variable did not intend. Indeed, using a variable in an unintended way is the *only* reason to make the variable public or protected. But when you use a variable in an unintended way you likely force modifications into the owner. If, on the other hand, all the variables of a module are private, then no modification can be caused through unintended useage. Privacy does not preclude extensibility. You can create public or protected accessor methods that: 1) provide extenders access to certain variables, and 2) ensure that the extenders don't use the variable in an unintended way. For example, given a variable v used by a module m, such that v should never be negative. If you make v public or protected someone could set it to a negative number breaking the code in m and possibly forcing mofidication to m. However, if v is private but is accessible through getV and setV methods; and if the setV method throws an exception if you pass it a negative number, then m is safe, and extenders are forced to follow the rules that m expects. To be fair, while I am a big proponent of keeping variables private, I have also come to rely much more on my unit tests to enforce the appropriate use of variables. When the code enjoys 90+% unit test coverage those tests will uncover and prevent variable misuse. This softens the need for the compiler to enforce privacy. This is not to say that you shoudl not make your variables private, you should. It is to say that if you use TDD, the cost/benefit ratio changes, and you may find that you can soften access to some variables. -- Robert C. Martin (Uncle Bob)**| email: unclebob{}objectmentor.com Object Mentor Inc.* * * * * **| blog:**www.butunclebob.com The Agile Transition Experts**| web:***www.objectmentor.com 800-338-6716* * * * * * * * **| |
|
#54
| |||
| |||
| In article <eoq7nm$o8t$1{}aioe.org>, S Perryman <q{}q.net> 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?" > > Not quite. You wrote (emphasis is mine) : > > "For that interface, putting in the sets would expose the invariant to > *outsiders* ." > > > The invariant is *already exposed* to "outsiders" , merely by being > *defined* in the type spec. What has the addition of set ops done to > change this ?? > > (I define outsiders here as the users/inheritors of the Range type - if > this is not what you intend the term to mean, feel free to provide your > definition and we can re-discuss) Our definitions of "outsiders" is the same, but I meant something different by the word "exposed". I meant that the invariant can be broken by outsiders. > > If we put sets in the class with conventional definitions > > What is the "conventional definition" ?? void setLow( int v ) { low = v; } > > If you add "setLow( int L )" to the interface above, what will be its > > postcondition? > > We have : > > pre-conditions(setLow) = {} > post-conditions(setLow) = {} > invariant(Range) = low() <= high() This is a viable option. It says that setLow does not do anything at all. As such an implementation that does nothing for any value of L is completely correct. > So: > > pre(setLow) = post(setLow) = (low() <= high() ) > > I have a Range instance R, that currently represents the range [2,3] . > I do the following : > > R.setLow(4) ; > > What happens with the correctness artifacts ?? > > - pre(setLow) = true. > > obviously > > - post(setLow) = true. > > obviously > > > What are the subsequent values of high/low after invoking setLow ?? > > UNDEFINED !!! That is not how I understand contracts. As I understand it, there is an implied contract that any state not mentioned in the postcondition will remain invariant. > The Range type has *no statement* of what those values will be. > All we know is whatever the values are, low <= high. > > > > If you make it: low() == L > > Now we have : > > pre(setLow) = low() <= high() > post(setLow) = (low() == L) AND (low() <= high() ) > > I have a Range instance R, that currently represents the range [2,3] . > I do the following : > > R.setLow(4) ; > > What happens with the correctness artifacts ?? > > - pre(setLow) = true. > > obviously > > - post(setLow) = true. > > obviously > > > What are the subsequent values of high/low after invoking setLow ?? > > low = 4 > high = UNDEFINED !!! > > The Range type has *no statement* of what the high value will be. > All we know is whatever the values are, low <= high. Again, this may be a misunderstanding on my part. If it is, then you are correct, of course. |
|
#55
| |||
| |||
| Daniel T. wrote: > In article <eoq7nm$o8t$1{}aioe.org>, S Perryman <q{}q.net> wrote: >>The invariant is *already exposed* to "outsiders" , merely by being >>*defined* in the type spec. What has the addition of set ops done to >>change this ?? >>(I define outsiders here as the users/inheritors of the Range type - if >>this is not what you intend the term to mean, feel free to provide your >>definition and we can re-discuss) > Our definitions of "outsiders" is the same, but I meant something > different by the word "exposed". I meant that the invariant can be > broken by outsiders. OK. So this by definition means that an outsider can only break a type invariant when accessing a type property P, by not conforming to the pre-conditions defined for P (preconditions(P) ) . >>>If we put sets in the class with conventional definitions >>What is the "conventional definition" ?? > void setLow( int v ) { > low = v; > } This is a typical implementation. I wanted a typical specification (pre/post-conditions) . DT>If you add "setLow( int L )" to the interface above, what will be its DT>postcondition? >>We have : >>pre-conditions(setLow) = {} >>post-conditions(setLow) = {} >>invariant(Range) = low() <= high() > This is a viable option. It says that setLow does not do anything at > all. As such an implementation that does nothing for any value of L is > completely correct. Oh no. It says no such thing. The post-condition (correctly) states that whatever value low/high are set to as a result of being given L, low <= high. >>So: >>pre(setLow) = post(setLow) = (low() <= high() ) >>I have a Range instance R, that currently represents the range [2,3] . >>I do the following : >>R.setLow(4) ; >>What happens with the correctness artifacts ?? >>- pre(setLow) = true. >>obviously >>- post(setLow) = true. >>obviously >>What are the subsequent values of high/low after invoking setLow ?? >>UNDEFINED !!! > That is not how I understand contracts. As I understand it, there is an > implied contract that any state not mentioned in the postcondition will > remain invariant. 1. Sadly not. In the words of Liskov and Wing : Type Specifications Need Explicit Invariants Type Specifications Need Explicit Constraints Assume nothing. Specify as much as possible. 2. Here is an exercise that examines the notion of other "state not mentioned in the postcondition" remaining invariant. It is the old favourite of type substitutability ... Circle/Ellipse. type Ellipse do invariant : focusA() > 0 , focusB() > 0 pre: A > 0 post: focusA() = A setFocusA(A) ; pre: B > 0 post: focusB() = B setFocusB(B) ; end type Circle conforms to Ellipse do invariant: radius() = focusA() = focusB() setFocusA(A) ; setFocusB(B) ; end Circle c = new Circle(radius : 1) ; c.setFocusA(2) ; // #1 c.setFocusB(3) ; // #2 Q1. What are the contracts for Ellipse::focusA(A) etc as actually written ?? What are they for Circle::focusA(A) ?? Based on these contracts, what will the values of c.focusA() and c.focusB() at points #1/#2 ?? Do those values conform to the contracts of Ellipse/Circle::focusA(A) ?? Q2. Repeat Q1, but replace "contracts" with "implied contracts" and "as actually" with "when explicitly" . Q3. Is there a difference between Q1 and Q2 ?? If so, what is the difference ?? If there is a difference, how easy is it for a user of a type to discern such implied contracts ?? >>The Range type has *no statement* of what those values will be. >>All we know is whatever the values are, low <= high. >>>If you make it: low() == L >>Now we have : >>pre(setLow) = low() <= high() >>post(setLow) = (low() == L) AND (low() <= high() ) >>I have a Range instance R, that currently represents the range [2,3] . >>I do the following : >>R.setLow(4) ; >>What happens with the correctness artifacts ?? >>- pre(setLow) = true. >>obviously >>- post(setLow) = true. >>obviously >>What are the subsequent values of high/low after invoking setLow ?? >>low = 4 >>high = UNDEFINED !!! >>The Range type has *no statement* of what the high value will be. >>All we know is whatever the values are, low <= high. > Again, this may be a misunderstanding on my part. If it is, then you are > correct, of course. It may or may not be a misunderstanding (that remains to be shown) . If it isn't, we need to state your understanding precisely so that we can discuss things further. If it is, we still need to state your understanding precisely, so that we can see which parts of your understanding are correct, and which are not. Either way, we are going to get there. Regards, Steven Perryman |
|
#56
| |||
| |||
| On 2007-01-12 16:06:08 -0600, "Anthony Paul" <anthonypaulo{}> said: > Excuse my cynicism people... I've been holding this in for the past 20 > years. I will be a truly happy camper the day I see some science in > computer science. There is a huge difference between science and engineering and art. Any human endeavor worth doing must have elements of all three. Software development is no exception. The science part of software is meager, but rigorous. The "Halting Problem", the study of sorts and searches, the reduction of computing to turing machines, are all evidence that we have a functioning science. The engineering part of software is not so meager as the science; but is less rigorous. There are many books and papers written about engineering principles and design patterns, etc. These are neither more, nor less, definitive than in any other engineering practice. All engineers have certain dogmas and religions that they follow that have no scientific basis; but that are nonetheless empirically derived and communicated by social tradition. It is the art that is the most elusive, and perhaps the most *human* aspect of software. And it may be the most important. Software is a craft, a combination of science, engineering, and art. As such, it is an effort to create beauty. Beauty of function, beauty of form, beauty of utility, beauty of affordability. A good software developer *cares* about the software he writes that way an author cares about his words, or a painter cares about his brush strokes. A professional software developer continuously learns the science, the engineering, and the art forms of his/her discipline. Do not bemoan the state of software science and engineering. Do not complain that there is too much art. If there were not art, then software could be done by computers. -- Robert C. Martin (Uncle Bob)**| email: unclebob{}objectmentor.com Object Mentor Inc.* * * * * **| blog:**www.butunclebob.com The Agile Transition Experts**| web:***www.objectmentor.com 800-338-6716* * * * * * * * **| |
|
#57
| |||
| |||
| "Daniel T." wrote: > Miguel Oliveira e Silva <mos{}det.ua.pt> 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 |
|
#58
| |||
| |||
| Miguel Oliveira e Silva <mos{}det.ua.pt> wrote: > > > 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 I'm somewhat confused about this point. Is your PERSON class an example of an invariant being exposed or not? The comment above the code implies to me that it is. |
|
#59
| |||
| |||
| "Daniel T." wrote: > Miguel Oliveira e Silva <mos{}det.ua.pt> wrote: > > > > > 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 > > I'm somewhat confused about this point. Is your PERSON class an example > of an invariant being exposed or not? No. The invariant is correctly protected by the class itself. > The comment above the code implies to me that it is. (Sorry.) The invariant is exposed to clients (which I'm reading as possibly being compromised by clients) if the class has public writable attributes (which are not possible in Eiffel, but quite common in C++ and Java). Even when the supplier has a missing precondition in the specification (and implementation) of its class, the invariant is not exposed, because is still the supplier fault for not having a correct specification of its class. -miguel -- Miguel Oliveira e Silva DETI-IEETA, Universidade de Aveiro, PORTUGAL |
![]() |
| Thread Tools | |
| Display Modes | |
In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.