What are the best practices for Design by Contract programming
Asked Answered
C

5

9

What are the best practices for Design by Contract programming.

At college I learned the design by contract paradigma (in an OO environment) We've learned three ways to tackle the problem :

1) Total Programming : Covers all possible exceptional cases in its effect (cf. Math)

2) Nominal Programming : Only 'promises' the right effects when the preconditions are met. (otherwise effect is undefined)

3) Defensive Programming : Use exceptions to signal illegal invocations of methods

Now, we have focussed in different OO scenarios on the correct use in each situation, but we haven't learned WHEN to use WHICH... (Mostly the tactics where inforced by the exercice..)

Now I think it's very very strange that I haven't asked my teacher (but then again, during lectures, noone has)

Personally, I never use nominal now, and tend to replace preconditions with exceptions (so i rather use : throws IllegalDivisionByZero, than stating 'precondition : divider should differ from zero) and only program total what makes sense (so I wouldn't return a conventional value on division by zero), but this method is just based on personal findings and likes.

so I am asking you guys :

Are there any best practises??

Crustal answered 13/4, 2009 at 19:49 Comment(0)
S
7

I didn't know about this division, and it doesn't really reflect my experience.

Total Programming is virtually impossible. You could not guarantee that you cover all exceptional cases. So basically you should limit your scope and reject the situations that are out of scope (that's the role of the Pre-conditions)

Nominal Programming is not desired. Undefined effect should be banned.

Defensive Programming is a must. You should always signal illegal invocations of methods.

I'm in favour of the implementation of the complete Design-by-Contract elements, which is, in my opinions a practical and affortable version of the Total Programming

Preconditions (a kind of Defensive Programming) to signal illegal invocation of the method. Try to limit your scope as much as you can so that you could simplify the code. Avoid complex implementation if possible by narrowing a little bit the scope.

Postconditions to raise an error if the desired effect is not obtained. Even if it your fault, you should notify the caller that you miss your goal.

Invariants to check that the object consistency is preserved.

Staley answered 19/9, 2009 at 22:35 Comment(2)
really like the obvious but pertinent assertion re limiting scope so you can simplify the code...excellent advise!Borosilicate
Total programming is not impossible, OP just described it a bit weird. The goal is not to cover for all exceptional cases, but to cover all possible inputs. So if your input is a string that means your method has to care of that string being null, being empty, having non alphabetic characters, not being trimmed and so on to produce a result, typically by using a smart default or fallback. This is not very common, but a good example is something like a "calender" that interprets jan 32th as being 1st of february instead of throwing "invalid_day_of_month_exception".Gratiana
B
1

It all boils down to what responsibilities do you wish to assign to the client and the implementer of the contract.

In defensive programming you force the implementer to check for error conditions which can be costy or even impossible in some cases. Imagine a contract specified by the binarySearch for example your input array has to be sorted. you can't detect this while running the algorithm. you have to do a manual check for it which will actually bump the execution time an order of magnitude. to back my opinion up is the signature of the method from the javadocs.

Another point is People and frameworks now tend to implement exception translation mechanisms which is used mainly to translate checked exceptions (defensive style) to runtime exceptions that will just pop up if something wrong happens. this way the client and implementer of the contract has less to worry about while dealing with each other.

Again this is my personal opinion backed only with what limited experience I have, I'd love to hear more about this subject.

Bozarth answered 13/4, 2009 at 20:11 Comment(3)
You can check either array is sorted as precondition. We do this in the c++ with asserts.Finicky
checking the whole array is sorted takes more time than actually doing a binary search in it :)Bozarth
Just some thoughts : you could check preconditions only in certain modes, say for unittesting no? Further a precondition can be just stated and not tested, of courseCrustal
S
1

...but we haven't learned WHEN to use WHICH...

I think the best practice is to be "as defensive as possible". Do your runtime checks if you can. As @MahdeTo has mentioned sometimes that's impossible for performance reasons; in such cases fall back on undefined or unsatisfactory behavior.

That said, be explicit in your documentation as to what has runtime checks and what does not.

Scornful answered 13/4, 2009 at 20:22 Comment(0)
G
0

Like much of computing "it depends" is probably the best answer.

Design by contract/programming by contract can help development greatly by explicitly documenting the conditions for a function. Just the documentation can be a help without even making it into (compiled) code.

Where feasible I recommend defensive - checking every condition. BUT only for development and debug builds. In this way most invalid assumptions are caught when the conditions are broken. A good build system would allow you to turn the different condition types on and off at a module or file level - as well as globally.

The actions taken in release versions of software then depend upon the system and how the condition is triggered ( usual distinction between external and internal interfaces ). The release version could be 'total programming' - all conditions give a defined result (which can include errors or NaN)

To me "nominal programming" is a dead end in the real world. You assume that if you passed the right values in (which of course you did) then the value you receive is good. If your assumption was wrong - you break down.

Garman answered 15/4, 2009 at 12:53 Comment(0)
F
0

I think that test driven programming is the answer. Before actually implementing the module, you first create a unit test (call it a contract). Then gradually implement the functionality and make sure the contract is still valid as you go. Usually I start with plain stubs and mockups, then gradually fill out the rest replacing the stabs with real stuff. Keep improving and making the test stronger. At the end you end up with a robust implementation of said module plus you've got a fantastic test bed - coded implementation of the contract. Later on, if someone modifies the module, first you see if it can still fit the test bed. If it doesn't, the contract is broken - reject the changes. Or, the contract is outdated, - fix the unit tests. And so on.. Boring cycle of software development :)

Forehead answered 18/4, 2009 at 19:23 Comment(3)
While a good idea, I don't think it answers the question. You can't write tests for invalid values if you haven't decided how the system should respond to invalid values first.Wolverine
@Wolverine You can't. So, 1) decide, 2) start writing test for most basic invalid value 3) when test fails because of missing code, iterate code until test pass, 4) refactor tests and production code 5) decide about next invalid value and make another red-green-refactor iteration until you run out of possible invalid value groups to test and then start writing tests for happy cases, starting with most basic, as Uncle Bob advices :)Blighter
@Blighter That's just more explaining of TDD and still doesn't answer the question. It's about choosing between approaches for Design by Contract. You have to choose the approach before you can write the tests. For example, if you choose nominal, using the definition in the question, you wouldn't test the invalid values at all.Wolverine

© 2022 - 2024 — McMap. All rights reserved.