[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