Best Practices /
Pre And Post Conditions
Contract Coding
This is a style of coding in which your source code contains extra code that defines and tests the contract between functions (either free or in classes/structs).Another way of looking at the contract code is that its purpose is to detect logic errors and not data errors. Invalid data nearly always come from user input and that needs run-time checking in production code. Logic errors, that is, mistakes you have made, are often manifested by passing illogical data to your functions or generating wrong data in a function. The in-block, out-block, and invariant-block can thus be used to check that the data you generate, and the data you pass between functions makes sense according to the contract, and if it doesn't then you have probably made a wrong coding decision elsewhere in your code.
Preconditions
You shouldn't use preconditions to validate user input - because those "in" blocks will disappear altogether in a release build. You'll end up with code that only works in a debug build. Preconditions should never result in different behavior between release and debug builds. Besides which, what use is an assert message to an end-user?
No, preconditions exist to help you find your bugs. The way I use it, if the input is dependant upon user-input, then I should test for validity in the function body, not the precondition, and throw an exception or otherwise handle it if it turns out to be nonsense. The in-block is there to test whether or not my own code contains bugs.
In simple terms, the in-block asserts that the input is what I expect it to be. (And in the case of user-input, I expect it to sometimes be rubbish, and hence consider rubbish to be legal input). In this paradigm, every assert failure within an in-block represents one bug found in another part of code ... which then gives me the power to go and fix it.
|
Invariants
An invariant is a state that data must always be in immediately before a class function returns to it's caller.They're pretty easy to understand, in that they can only really be used for bug-finding.
|
Postconditions
The out-block is used to implement postcondition contract testing. This should be used to detect mistakes in your functions' logic, and not user inputted data. The idea being that if a function returns an unexpected value it is most likely to be as a result of your code's logic.
I find postconditions most useful of all. In the Int class (etc.bigint.bigint) they're used all the time. For example - there's a function which, given input x, returns the integer square root y of x, and the remainder r. The postcondition asserts that (y*y+r == x).
In other words, the postcondition will tell me if the function contains a bug. Simply calling the function a few times without seeing an assert gives me high confidence that the function is okay.
There is no "valid" or "invalid". Just "bug-free" or "not bug-free". That's what you're testing for.
And if I'm just doing a final check to verify the object is still sound, that should be done in a class invariant, no?
Yes it should. But that wouldn't have helped me with the square root example, would it? Postconditions serve a different purpose.
So my question is: what "best practices" have you been using in reality? I personally use Preconditions and copious amounts of asserts. But Invariants and Postconditions don't seem to offer real-world value to the code I'm writing.
They offer value to the person writing the code, not value to the person using the code.
|
Release vs. Debug Builds
All of these things will disappear into the ether in a release build. Never assume that an end user will have the benefit of these things in your code.
Do others find that writing/maintaining Invariants and Postconditions pays for themselves in the bugs they avoid?
Yes, but they don't avoid bugs - they find bugs. Your end-users avoid them, because you fix them before those other people even see them.
Based on: NG:digitalmars.D/5667.
The "promise" of post-conditions
I find postconditions most useful of all. In the Int class (etc.bigint.bigint) they're used all the time. For example - there's a function which, given input x, returns the integer square root y of x, and the remainder r. The postcondition asserts that (y*y+r == x).
It is more than that. For finding your own bugs, a plain assert would be good enough. The post-condition is more like a promise to the outside world. You are telling publicly: this function will do that. Better than any comment: the promise can even be checked.
From NG:digitalmars.D/5716:
Design by Contract in Eiffel
To my knowledge Design by Contract is an Eiffel concept by origin. If you want to see powerful use of contracts you should check out the Eiffel structures library. A powerful aspect of pre- and postconditions is that they can be inherited. Since in Eiffel you cannot assign a value to a field from outside the object, you will need to make a set method to do this. So you make a setter. Next someone inherits from your class, redefines the setter, adds event handling, but forgets to set the actual value. The postcondition will be inherited and the contract breach will manifest at runtime. This example, although trivial, shows that even in the simplest of cases, a postcondition is relevant. I'm not so sure it isn't an error (Arcane Jill thinks not), to close a file twice. It is an error in that it isn't possible to close a closed file, no transition takes place, however the outcome is acceptable. So it is an error without negative consequence other than bloat or (remotely possible) an uncorrected misunderstanding of the semantics of close (I can see it now: But the file was only half-way closed!) Whether bloat like this should be avoided through contracts is a matter of taste.
From NG:digitalmars.D/5683:
Design by Contract in D
It is design-by-contract: Both, pre- and post-conditions are part of the interface. There are several aspects to that fact. In D, there seems to be very little distinction between interface and implementation, both being coded in the same file.
In that case, the pre- and post-conditions should be put into the interface as well. They document how a function is to be used, and what it produces. It is understood, that pre- and postconditions should only ever depend on public functions and the function arguments.
One important aspect of pre- and post-conditions is, that they are inherited along with the interface. (The the chapter "contracts") An overriding function must keep the contract of the original. It may loosen the in-contract or tighten the out-contract.
Plain asserts in the body, on the other hand, have nothing to do with contracts, but are merely a kind of "checked documentation", helping in understanding the implementation code correctly.
Whether design-by-contract helps you in avoiding bugs depends on your coding style. It certainly is only interesting for libraries and larger projects that define clear interfaces between individual parts. For small projects, thinking about clear interfaces maybe more effort than it is worth.
Excerpted from NG:digitalmars.D/5715: