[Python-ideas] Why is design-by-contracts not widely adopted?
Marko Ristin-Kaufmann
marko.ristin at gmail.com
Sun Sep 30 02:17:49 EDT 2018
Hi,
I compiled a couple of issues on github to provide a more structured ground
for discussions on icontract features:
https://github.com/Parquery/icontract/issues (@David Maertz: I also
included the issue with automatically generated __doc__ in case you are
still interested in it).
Cheers,
Marko
On Sat, 29 Sep 2018 at 17:27, Stephen J. Turnbull <
turnbull.stephen.fw at u.tsukuba.ac.jp> wrote:
> Steven D'Aprano writes:
>
> > put (x: ELEMENT; key: STRING) is
> > -- Insert x so that it will be retrievable through key.
> > require
> > count <= capacity
> > not key.empty
> > do
> > ... Some insertion algorithm ...
> > ensure
> > has (x)
> > item (key) = x
> > count = old count + 1
> > end
> >
> > Two pre-conditions, and three post-conditions. That's hardly
> > complex.
>
> You can already do this:
>
> def put(self, x: Element, key: str) -> None:
> """Insert x so that it will be retrievable through key."""
>
> # CHECKING PRECONDITIONS
> _old_count = self.count
> assert self.count <= self.capacity,
> assert key
>
> # IMPLEMENTATION
> ... some assertion algorithm ...
>
> # CHECKING POSTCONDITIONS
> assert x in self
> assert self[key] == x
> assert self.count == _old_count
>
> return
>
> I don't see a big advantage to having syntax, unless the syntax allows
> you to do things like turn off "expensive" contracts only. Granted,
> you save a little bit of typing and eye movement (you can omit
> "assert" and have syntax instead of an assignment for checking
> postconditions dependent on initial state).
>
> A document generator can look for the special comments (as with
> encoding cookies), and suck in all the asserts following until a
> non-assert line of code (or the next special comment). The
> assignments will need special handling, an additional special comment
> or something. With PEP 572, I think you could even do this:
>
> assert ((_old_count := self.count),)
>
> to get the benefit of python -O here.
>
> > If I were writing this in Python, I'd write something like this:
> >
> > def put(self, x, key):
> > """Insert x so that it will be retrievable through key."""
> > # Input checks are pre-conditions!
> > if self.count > capacity:
> > raise DatabaseFullError
> > if not key:
> > raise ValueError
> > # .. Some insertion algorithm ...
>
> But this is quite different, as I understand it. Nothing I've seen in
> the discussion so far suggests that a contract violation allows
> raising differentiated exceptions, and it seems very unlikely from the
> syntax in your example above. I could easily see both of these errors
> being retryable:
>
> for _ in range(3):
> try:
> db.put(x, key)
> except DatabaseFullError:
> db.resize(expansion_factor=1.5)
> db.put(x, key)
> except ValueError:
> db.put(x, alternative_key)
>
> > and then stick the post-conditions in a unit test, usually in a
> > completely different file:
>
> If you like the contract-writing style, why would you do either of
> these instead of something like the code I wrote above?
>
> > So what's wrong with the status quo?
> >
> > - The pre-condition checks are embedded right there in the
> > method implementation, mixing up the core algorithm with the
> > associated error checking.
>
> You don't need syntax to separate them, you can use a convention, as I
> did above.
>
> > - Which in turn makes it hard to distinguish the checks from
> > the implementation, and impossible to do so automatically.
>
> sed can do it, why can't we?
>
> > - Half of the checks are very far away, in a separate file,
> > assuming I even remembered or bothered to write the test.
>
> That was your choice. There's nothing about the assert statement that
> says you're not allowed to use it at the end of a definition.
>
> > - The post-conditions aren't checked unless I run my test suite, and
> > then they only check the canned input in the test suite.
>
> Ditto.
>
> > - The pre-conditions can't be easily disabled in production.
>
> What's so hard about python -O?
>
> > - No class invariants.
>
> Examples?
>
> > - Inheritance is not handled correctly.
>
> Examples? Mixins and classes with additional functionality should
> work fine AFAICS. I guess you'd have to write the contracts in each
> subclass of an abstract class, which is definitely a minus for some of
> the contracts. But I don't see offhand why you would expect that the
> full contract of a method of a parent class would typically make sense
> without change for an overriding implementation, and might not make
> sense for a class with restricted functionality.
>
> > The status quo is all so very ad-hoc and messy. Design By Contract
> > syntax would allow (not force, allow!) us to add some structure to the
> > code:
> >
> > - requirements of the function
> > - the implementation of the function
> > - the promise made by the function
>
> Possible already as far as I can see. OK, you could have the compiler
> enforce the structure to some extent, but the real problem IMO is
> going to be like documentation and testing: programmers just won't do
> it regardless of syntax to make it nice and compiler checkable.
>
> > Most of us already think about these as three separate things, and
> > document them as such. Our code should reflect the structure of how we
> > think about the code.
>
> But what's the need for syntax? How about the common (in this thread)
> complaint that even as decorators, the contract is annoying, verbose,
> and distracts the reader from understanding the code? Note: I think
> that, as with static typing, this could be mitigated by allowing
> contracts to be optionally specified in a stub file. As somebody
> pointed out, it shouldn't be hard to write contract strippers and
> contract folding in many editors. (As always, we have to admit it's
> very difficult to get people to change their editor!)
>
> > > In my experience this is very rarely true. Most functions I
> > > write are fairly short and easily grokked, even if they do
> complicated
> > > things. That's part of the skill of breaking a problem down, IMHO;
> if
> > > the function is long and horrible-looking, I've already got it wrong
> and
> > > no amount of protective scaffolding like DbC is going to help.
> >
> > That's like saying that if a function is horrible-looking, then there's
> > no point in writing tests for it.
> >
> > I'm not saying that contracts are only for horrible functions, but
> > horrible functions are the ones which probably benefit the most from
> > specifying exactly what they promise to do, and checking on every
> > invocation that they live up to that promise.
>
> I think you're missing the point then: ISTM that the implicit claim
> here is that the time spent writing contracts for a horrible function
> would be better spent refactoring it. As you mention in connection
> with the Eiffel example, it's not easy to get all the relevant
> contracts, and for a horrible function it's going to be hard to get
> some of the ones you do write correct.
>
> > Python (the interpreter) does type checking. Any time you get a
> > TypeError, that's a failed type check. And with type annotations, we
> can
> > run a static type checker on our code too, which will catch many of
> > these failures before we run the code.
>
> But an important strength of contracts is that they are *always* run,
> on any input you actually give the function.
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180930/902eba11/attachment-0001.html>
More information about the Python-ideas
mailing list