[Python-ideas] Why is design-by-contracts not widely adopted?

Steven D'Aprano steve at pearwood.info
Fri Sep 28 23:47:22 EDT 2018


On Sun, Sep 23, 2018 at 07:09:37AM +0200, Marko Ristin-Kaufmann wrote:

> After the discussion we had on the list and after browsing the internet a
> bit, I'm still puzzled why design-by-contract was not more widely adopted
> and why so few languages support it.
[...]

> *. *After properly reading about design-by-contract and getting deeper into
> the topic, there is no rational argument against it and the benefits are
> obvious. And still, people just wave their hand and continue without
> formalizing the contracts in the code and keep on writing them in the
> descriptions.
> 
> * Why is that so?
[...]

You are asking a question about human psychology but expecting logical, 
technical answers. I think that's doomed to failure.

There is no nice way to say this, because it isn't nice.

Programmers and language designers don't use DbC because it is new and 
different and therefore scary and wrong. Programmers, as a class, are 
lazy (they even call laziness a virtue), deeply conservative, 
superstitious, and don't like change. They do what they do because 
they're used to it, not because it is the technically correct thing to 
do, unless it is by accident.

(Before people's hackles raise too much, I'm speaking in generalities, 
not about you personally. Of course you are one of the 1% awesomely 
logical, technically correct programmers who know what you are doing and 
do it for the right reasons after careful analysis. I'm talking about 
the other 99%, you know the ones. You probably work with them. You've 
certainly read their answers on Stackoverflow or The Daily WTF and a 
million blogs.)

They won't use it until there is a critical mass of people using it, and 
then it will suddenly flip from "that weird shit Eiffel does" to "oh 
yeah, this is a standard technique that everyone uses, although we don't 
make a big deal about it".

Every innovation in programming goes through this. Whether the 
innovation goes mainstream or not depends on getting a critical mass, 
and that is all about psychology and network effects and nothing to do 
with the merit of the idea.

Remember the wars over structured programming? Probably not. In 2018, 
the idea that people would *seriously argue against writing subroutines* 
seems insane, but they did. As late as 1999, a former acquaintance of 
mine was working on a BASIC project for a manager who insisted they use 
GOTO and GOSUB in preference to subroutines.

Testing: the idea that we should have *repeatable automated tests* took 
a long time to be accepted, and is still resisted by both developers and 
their managers. What's wrong with just sitting a person down in front of 
the program and checking for bugs by hand? We still sometimes have to 
fight for an automated test suite, never mind something like test driven 
development.

ML-based languages have had type inference for decades, and yet people 
still think of type checking in terms of C and Java declarations. Or 
they still think in terms of static VERSUS dynamic typing, instead of 
static PLUS dynamic typing.

I could go on, but I think I've made my point.

I can give you some technical reasons why I don't use contracts in my 
own Python code, even though I want to:

(1) Many of my programs are too small to bother. If I'm writing a quick 
script, I don't even write tests. Sometimes "be lazy" is the right 
answer, when the cost of bugs is small enough and the effort to avoid 
them is greater. And that's fine. Nobody says that contracts must be 
mandatory.

(2) Python doesn't make it easy to write contracts. None of the 
solutions I've seen are nice. Ironically, the least worst I've seen is a 
quick and dirty metaclass solution given by Guido in an essay way back 
in Python 1.5 days:

https://www.python.org/doc/essays/metaclasses/

His solution relies only on a naming convention, no decorators, no 
lambdas:

class C(Eiffel):
    def method(self, arg):
        return whatever
    def method_pre(self, arg):
        assert arg > 0
    def method_post(self, Result, arg):
        assert Result > arg


Still not pretty, but at least we get some block structure instead of a 
wall of decorators.

Syntax matters. Without good syntax that makes it easy to write 
contracts, it will never be anything but niche.


(3) In a sense, *of course I write contracts*. Or at least precondition 
checks. I just don't call them that, and I embed them in the 
implementation of the method, and have no way to turn them off. Nearly 
all of us have done the same, we start with a method like this:

class C:
    def method(self, alist, astring, afloat):
        # do some work...

using nothing but naming conventions and an informal (and often vague) 
specification in the docstring, and while that is sometimes enough in 
small projects, the third time we get bitten we start adding defensive 
checks so we get sensible exceptions:

    def method(self, alist, astring, afloat):
        if not isinstance(alist, list):
            raise TypeError('expected a list')
        if alist == []:
            raise ValueError('list must not be empty')
        # and so on...


These are pre-conditions! We just don't call them that. And we can't 
disable them. They're not easy to extract from the source code and turn 
into specifications. And so much boilerplate!

Let's invent syntax to make it more obvious what is going on:

    def method(self, alist, astring, afloat):
        requires:
            isinstance(alist, list)
            alist != []
            isinstance(astring, str)
            number_of_vowels(astring) > 0
            isinstance(afloat, float)
            not math.isnan(afloat)
        implementation:
            # code goes here


Its easy to distinguish the precondition checks from the implementation, 
easy to ignore the checks if you don't care about them, and easy for an 
external tool to analyse.

What's not to like about contracts? You're already using them, just in 
an ad hoc, ugly, informal way.

And I think that is probably the crux of the matter. Most people are 
lazy, and don't like having to do things in a systematic manner. For 
years, programmers resisted writing functions, because unstructured code 
was easier. We still resist writing documentation, because "its obvious 
from the source code" is easier. We resist writing even loose 
specifications, because NOT writing them is easier. We resist writing 
tests unless the project demands it. We resist running a linter or 
static checker over our code ("it runs, that means there are no 
errors").

Until peer pressure and pain makes us do so, then we love them and could 
not imagine going back to the bad old days before static analysis and 
linters and unit tests.



-- 
Steve



More information about the Python-ideas mailing list