[Python-ideas] Why is design-by-contracts not widely adopted?
Steven D'Aprano
steve at pearwood.info
Tue Oct 9 03:23:16 EDT 2018
On Mon, Oct 08, 2018 at 03:34:51PM -0400, Terry Reedy wrote:
> >> I said above that functions may be specified by
> >> process rather than result.
> >
> > Fine. What of it? Can you describe what the function does?
> >
> > "It sorts the list in place."
> >
> > "It deletes the given record from the database."
>
> > These are all post-conditions.
>
> No they are not.
Yes they are; they are textual descriptions of the post-condition, not
executable code, but since software contracts are both specification and
code, they certainly count as post-conditions.
By the way, you seem to have deleted one of my examples, leaving only
the two relatively simple ones. I don't understand why you deleted the
example, but here it is again:
"It deducts the given amount from Account A and transfers it
to Account B, guaranteeing that either both transactions occur
or neither of them, but never one and not the other."
> They are descriptions of the process. Additional
> mental work is required to turn them into formal descriptions of the
> result that can be coded.
Yes. That's called "programming". You might have done it from time to
time :-)
> Marko appears to claim that such coded formal
> descriptions are easier to read and understand than the short English
> description. I disagree.
What does "sort in place"? *Precisely*? How do you know if the software
*actually does* sort in place?
They are not rhetorical questions.
You can document that the function sorts in place, but if it actually
reverses a copy of the list, how would you know?
How do we know the software meets the specified requirements if you
haven't nailed down the specification, and if the specification exists
only in documentation and is never checked in the code?
Design By Contract is a methodology that helps ensure that the
specification is (1) nailed down and (2) actually checked.
> It is therefore not obvious to me that the
> extra work is worthwhile.
Tell me how you, personally, would find out whether or not the function
actually sorts the list in place. What process would YOU do?
As I see it, you either have to take it on trust, or you have to write
tests. Would you argue that the "extra work" to write tests are not
worthwhile?
> >>def append_first(seq):
> >> "Append seq[0] to seq."
> >[...]
>
> The snipped body (revised to omit irrelevant 'return')
> seq.append(seq[0])
>
> >>But with duck-typing, no post condition is possible.
> >
> >That's incorrect.
> >
> >def append_first(seq):
> > require:
> > len(seq) > 0
>
> seq does not neccessarily have a __len__ method
Yes it must, because I wrote the specification and I demand that it
must support len.
You lost your opportunity to write the spec yourself when you wrongly
said no post-condition is possible. I just showed that post-conditions
ARE possible, by writing some.
We could debate what those conditions are: can we just test for the
Sequence interface, using isinstance() and the Sequence ABC? Do I need
to bother checking that seq supports __getitem__?
But I'm not really interested in getting bogged down in the minutia of
what specific post-conditions need to go with this toy function. Its a
made-up example, so who cares? In practice, you specify in as much
detail as you need. You can always come back and add more detail later.
> > hasattr(seq, "append")
>
> The actual precondition is that seq[0] be in the domain of seq.append.
That is not part of the contract of the append_first function. It
doesn't care about any domain limits of seq. If an object could be
appended into seq[0] in the first place, surely it can append again
later. We don't have to try to detect every imaginable bizarre weird
piece of code intentionally written to perplex us.
> The only absolutely sure way to test this is to run the code body.
Sure. So what? The code body does actually get run you know. If it
raises an exception because seq can't append seq[0] to itself, that's a
bug that needs to be fixed.
> Or
> one could run seq[0] and check it against the preconditions, if formally
> specified, of seq.append.
That is not the responsibility of the append_first function.
> > ensure:
> > len(seq) == len(OLD.seq) + 1
> > seq[0] == seq[-1]
>
> Not even all sequences implement negative indexing.
I don't care. Since I wrote the spec, I demand that only those which
meet my requirements are used.
But okay, fine, I'll re-write it:
seq[0] == seq[len(seq)-1]
and then you can tell me off for writing un-Pythonic code, and then I'll
change it back.
> This is true for lists, as I said, but not for every object the meets
> the preconditions. As others have said, duck typing means that we don't
> know what unexpected things methods of user-defined classes might do.
That's not how we use duck-typing. When I call len(obj), I don't expect
that it will format my hard drive, even though obj.__len__ might do
that, and if it did, I would treat it as a bug. Why am I passing such an
object as input? That makes no sense. I *don't want* that behaviour, so
why would I pass an object that does that?
I wouldn't say "Oh well, that means that formatting my head drive must
be the correct behaviour for my application, good thing I have backups."
I would say "That's a bug. Fix it."
Here is the scenario: I am writing an application that has some specific
purpose. I don't know, let's say it is a payroll application. It talks
to a database and generates payroll reports or something. Whatever. The
point is it has some *concrete* purpose, it's not just Mickey Mouse toy
functions.
As part of that application, I need (for some unspecified reason!) to
take a sequence, extract the first item, and append it to the end of the
sequence. Hence you append_first function. Why? Who knows. Let's say it
makes sense in context. I trust you.
In context, the sequence I pass to that function is not going to be
"some arbitrary object that could do absolutely anything". It is going
to be from a tiny subset of *useful* sequences, specifically the ones
which actually do append the first item to the end of sequence. Why
would I write, and then use, a class that does something unhelpful? Am I
a malicious coder trying to get myself fired?
The purpose of the contract is to detect ACCIDENTAL bugs early, not to
defend against intentional self-sabotage.
--
Steve
More information about the Python-ideas
mailing list