[Python-ideas] Simplicity of C (was why is design-by-contracts not widely)
Steven D'Aprano
steve at pearwood.info
Mon Oct 1 11:28:55 EDT 2018
On Sun, Sep 30, 2018 at 06:33:08PM +1000, Chris Angelico wrote:
> On Sun, Sep 30, 2018 at 6:03 PM Steven D'Aprano <steve at pearwood.info> wrote:
> >
> > On Sun, Sep 30, 2018 at 02:50:28PM +1000, Chris Angelico wrote:
> >
> > > And yet all the examples I've seen have just been poor substitutes for
> > > unit tests. Can we get some examples that actually do a better job of
> > > selling contracts?
> >
> > In no particular order...
> >
> > (1) Distance
>
> Don't doctests deal with this too? With the exact same downsides?
I'm not sure that I understand the question.
Yes, doctests are usually (but not always) close to the function too.
That's a plus in favour of doctests. The downside is that if you want to
show a metric tonne of examples, your docstring becomes huge. (Or move
it out into a separate text file, which doctest can still run.)
Sometimes its a real struggle to write a good docstring that isn't huge.
But on the other hand, a good editor should allow you to collapse
docstrings to a single line.
> > (2) Self-documenting code
>
> Ditto
Sorry, are you asking if doctests are self-documenting code?
Since they aren't part of the implementation of the function, no they
aren't.
> > (3) The "Have you performed the *right* tests?" problem
>
> Great if your contracts can actually be perfectly defined.
Why do you think the contracts have to be perfectly defined?
I keep reading people accusing pro-Contracts people of being zealots who
believe that Contracts are a magic bullet that solve every problem 100%
perfectly. I don't know where people get this idea.
Contracts are a tool, like unit tests. Just as a single unit test is
better than no unit tests, and two unit tests is better than one, same
goes for contracts. If you have an imperfect (incomplete, not buggy!)
contract, that's better than *no* contract at all.
If you would like to check five things, but can only specify three of
them in a contract, that's better than not specifying any of them.
> Every time
> a weird case is mentioned, those advocating contracts (mainly Marko)
> give examples showing "hey, contracts can do that too", and they're
> just testing specifics.
Can you give an example?
Of course, contracts *can* be specific (at least in Eiffel), the syntax
allows it. In pseudo-code:
def spam(x, y):
require:
if x is None:
y > 0
# implementation goes here...
The precondition checks that if x is None, y must be greater than zero.
On its own, that's not a very good contract since it tells us nothing
about the case when x is not None, but (as per my comments above) its
better than nothing.
How would you do this as a unit test? For starters, you need to move the
precondition into the function implementation block, giving it an
explicit raise:
def spam(x, y):
if x is None and y <= 0:
raise SomeError
# implementation continues...
Now you've given up any hope of disabling that specific check, short of
editing the source code.
Then you have to make a unit test to check that it works the way you
think it does:
def test_if_x_is_none_y_cannot_be_negative_or_zero(self):
# Naming things is hard...
self.AssertRaise(SomeError, spam, None, -1)
self.AssertRaise(SomeError, spam, None, 0)
Great! Now we know that if spam is passed None and -1, or None and 0, it
will correctly fail. But the unit test doesn't give us any confidence
that None and -9137 will fail, since we haven't tested that case.
Work-around: I often write unit tests like this:
def test_if_x_is_none_y_cannot_be_negative_or_zero(self):
for i in range(20):
# I never know how many or how few values to check...
n = random.randint(1, 1000) # or which values...
self.AssertRaise(SomeError, spam, None, -n)
self.AssertRaise(SomeError, spam, None, 0)
As David points out in another post, it is true that Contracts also can
only check the values that you actually pass to the application during
the testing phase, but that is likely to be a larger set of values than
those we put in the unit tests, which lets us gain confidence in the
code more quickly and with less effort. Rather than write lots of
explicit tests, we can just exercise the routine with realistic data and
see if the contract checks fail.
[...]
> > (6) Separation of concerns: function algorithm versus error checking
>
> Agreed, so long as you can define the contract in a way that isn't
> just duplicating the function's own body.
Sure. That's a case where contracts aren't really suitable and a few
unit tests would be appropriate, but that's only likely to apply to
post-conditions, not pre-conditions.
> Contracts are great for some situations, but I'm seeing a lot of cases
> where they're just plain not, yet advocates still say "use contracts,
> use contracts". Why?
For the same reason advocates say "Use tests, use tests" despite there
being some cases where formal automated tests are too hard to use.
Use contracts for the 90% of the cases they help, don't dismiss them
because of the 10% of cases they don't.
But honestly, if you prefer unit testing, that's great too. Nobody is
trying to say that unit tests suck or that you shouldn't use them. This
is about giving people the choice between contracts or unit tests or
both. When I say "Use contracts", what I mean is "Use as many or as few
contracts as makes sense for your application, according to your taste".
Right now, that choice is very restrictive, and I personally don't like
the look and feel of existing contract libraries. I would give my right
eye for Cobra-like syntax for contracts in Python:
http://cobra-language.com/trac/cobra/wiki/Contracts
--
Steve
More information about the Python-ideas
mailing list