[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