41

I recently discovered Design by Contract (DbC) and I find it an extremely interesting way to write code. Among other things, it would seem to offer:

  • Better documentation. Since the contract is the documentation, it's impossible for one to be out of date. Additionally, because the contract specifies exactly what a routine does, it helps to support reuse.
  • Simpler debugging. Since program execution stops the moment a contract fails, errors can't propagate, and the specific assertion violated will presumably be highlighted. This offers support during development and during maintenance.
  • Better static analysis. DbC is basically just an implementation of Hoare logic, and the same principles should apply.

The costs, in comparison seem to be rather small:

  • Extra finger-typing. Since the contracts have to be spelled out.
  • Takes some amount of training to get comfortable with writing contracts.

Now, being familiar with Python primarily, I realize that it is in fact possible to write up preconditions (just throwing exceptions for inappropriate input) and it's even possible to use assertions to test again certain postconditions. But it's not possible to simulate certain features such as 'old' or 'result' without some extra magic that would ultimately be considered un-Pythonic. (In addition, there are a few libraries that offer support, but ultimately I get the vibe it would be wrong to use them, as most developers don't.) I assume that it's a similar problem for all other languages (except of course, Eiffel).

My intuition tells me that the lack of support must be a result of some kind of rejection of the practice, but searching online has not been fruitful. I'm wondering if someone can clarify why most modern languages seem to offer so little support? Is DbC flawed or overly expensive? Or is it just obsolete due to Extreme Programming and other methodologies?

6
  • Sounds like an overly complicated way to do test-driven programming, without the benefit of also testing your program.
    – Dan
    Commented Jan 6, 2012 at 0:06
  • 3
    @Dan, not really, I think of it more as an extension of the type system. e.g. a function doesn't just take an integer argument, it takes an integer which is contractually obliged to be greater than zero Commented Jan 6, 2012 at 0:15
  • 5
    @Dan code contracts significantly reduce the number of tests you need to do. Commented Jan 6, 2012 at 0:32
  • 25
    @Dan, I'd rather say that TDD is the poor-man's contracts, not vice versa.
    – SK-logic
    Commented Jan 6, 2012 at 7:43
  • In dynamic language you can "decorate" your objects with contracts based on an optional flag. I have an example implementation which uses environmental flags to optionally monkey patch existing objects with the contracts. Yes the support is not native, but it's easy to add. The same applies to test harnesses, they are not native but they are easy to add/write.
    – Raynos
    Commented Jan 16, 2012 at 14:02

9 Answers 9

10

Arguably they are supported in virtually every programming language.

What you need are "assertions".

These are easily coded as "if" statements:

if (!assertion) then AssertionFailure();

With this, you can write contracts by placing such assertions at the top of your code for input constraints; those at the return points are output constraints. You can even add invariants throughout your code (although aren't really part of "design by contract").

So I argue they aren't widespread because programmers are too lazy to code them, not because you can't do it.

You can make these a little more efficient in most languages by defining a compile-time boolean constant "checking" and revising the statements a bit:

if (checking & !Assertion) then AssertionFailure();

If you don't like the syntax, you can resort to various language abstraction techniques such as macros.

Some modern languages give you nice syntax for this, and that's what I think you mean by "modern language support". That's support, but its pretty thin.

What most of even the modern languages don't give you is "temporal" assertions (over arbitrary previous or following states [temporal operator "eventually"], which you need if you want to write really interesting contracts. IF statements won't help you here.

8
  • 1
    The problem I find with only having access to asserts is that there is no effective way to check postconditions on commands since you often need to compare the postcondition state against the precondition state (Eiffel calls this 'old' and automatically passes it to the postcondition routine.) In Python, this functionality could be trivially recreated using decorators, but it comes up short when it comes time to turn assertions off.
    – Ceasar
    Commented Jan 16, 2012 at 7:49
  • How much of the previous state does Eiffel actually save? Since it reasonably cannot know which part you might access/modify without solving the halting problem (by analyzing your function), it either has to save the complete machine state, or as a hueristic, only some very shallow part of it. I suspect the latter; and these can "simulated" by simple scalar assignments before the precondition. I'd be pleased to learn Eiffel does otherwise.
    – Ira Baxter
    Commented Jan 16, 2012 at 8:05
  • 9
    ... just checked how Eiffel works. "old <exp>" is the value of <exp> at entry to the function, so it is doing shallow copies at function entrance as I expected. You can do them too. I'll agree having the compiler implement the syntax for pre/post/old is more convenient than doing all this by hand, but the point is one can to this by hand and it really isn't hard. We're back to lazy programmers.
    – Ira Baxter
    Commented Jan 16, 2012 at 8:17
  • 1
    @IraBaxter No. Code becomes simpler if you can separate the contract from the actual logic. Also, if the compiler can tell contract and code apart, it can reduce duplication by a lot. E.g. in D you can declare a contract on an interface or superclass and the assertions will be applied to all implementing/extending classes, regardless of the code in their functions. E.g. with python or Java, you have to invoke the entire super method and possibly throw away the results if you only want the contracts checked without duplication. This really helps implementing clean LSP compliant code.
    – marstato
    Commented Aug 8, 2017 at 8:03
  • 1
    @IraBaxter You said its convenient; i say it adds a functionality that cannot be done by hand.
    – marstato
    Commented Aug 8, 2017 at 9:37
15

As you say, Design by Contract is a feature in Eiffel, which has long been one of those programming languages that is well respected in the community but which has never caught on.

DbC is not in any of the most popular languages because it is only relatively recently that the mainstream programming community has come to accept that adding constraints/expectations to their code is a "reasonable" thing to expect of programmers. It's common now for programmers to understand how valuable unit-testing is, and that percolated up to programmers being more accepting of putting in code to validate their arguments and seeing benefits. But a decade ago, probably most programmers would say "that's just extra work for stuff you know is always going to be ok."

I think that if you were to go to the average developer today and talk about post-conditions, they'd nod enthusiastically and say "OK, that's like unit-testing." And if you talk about pre-conditions, they'd say "OK, that's like parameter validation, which we don't always do, but, y'know, I guess it's ok..." And then if you talk about invariants, they'd start to say "Gee, how much overhead is this? How many more bugs are we going to catch?" etc.

So I think there's still a long way to go before DbC is very broadly adopted.

1
  • OTOH, the mainstream programmers had been used to writing the assertions quite for a while. The lack of a useable preprocessor in the most modern mainstream languages rendered this nice practice inefficient, but it is still common for C and C++. It is making a comeback now with the Microsoft Code Contracts (based on, AFAIK, a bytecode rewriting for the release builds).
    – SK-logic
    Commented Jan 6, 2012 at 7:46
8

My intuition tells me that the lack of support must be a result of some kind of rejection of the practice,...

False.

It's a design practice. It can be embodied explicitly in code (Eiffel style) or implicitly in code (most languages) or in unit tests. The design practice exists and works well. The language support is all over the map. It is, however, present in many languages in the unit test framework.

I'm wondering if someone can clarify why most modern languages seem to offer so little support? Is DbC flawed or overly expensive?

It's expensive. And. More importantly, there are some things which cannot be proven in a given language. Loop termination, for example, cannot be proven in a programming language, it requires a "higher-order" proof capability. So so some kinds of contracts are technically inexpressible.

Or is it just obsolete due to Extreme Programming and other methodologies?

No.

We mostly use unit tests to demonstrate that DbC is fulfilled.

For Python, as you noted, the DbC goes in several places.

  1. The docstring and the docstring test results.

  2. Assertions to validate inputs and outputs.

  3. Unit tests.

Further.

You can adopt literate programming-style tools so that you write a document which includes your DbC information and which generates clean Python plus unit test scripts. The literate programming approach allows you to write a nice piece of literature which includes the contracts and the complete source.

4
  • You can prove trivial cases of loop termination, such as iteration over a fixed finite sequence. It's generalized looping that can't be shown to terminate trivially (since it could be searching for solutions to “interesting” mathematical conjectures); that's the whole essence of the Halting Problem. Commented Jan 16, 2012 at 15:34
  • +1. I think you are the only one who has covered the most critical point - there are some things which cannot be proven. Formal verification may be great, but not everything is verifiable! So that feature actually restricts what programming language can actually do! Commented Jan 16, 2012 at 18:20
  • @DonalFellows: Since the general case can't be proven, it's hard to incorporate a bunch of features that are (a) expensive and (b) known to be incomplete. My point in this answer is that it's easier to eschew all of those features and avoid setting false expectations of formal correctness proofs in general, when there are limitations. As a design exercise (outside the language) lots of proof techniques can (and should) be used.
    – S.Lott
    Commented Jan 16, 2012 at 20:19
  • It isn't at all expensive in a language such as C++ where contract checking compiles out in the release build. And using DBC tends to lead to release build code that is more lightweight, because you put in fewer runtime checks for the program being in a legal state. I've lost count of the number of godawful codebases I've seen where numerous functions check for an illegal state and return false, when it should NEVER be in that state in a properly-tested release build.
    – Kaitain
    Commented Apr 29, 2019 at 23:50
6

Just guessing. Maybe part of the reason that it isn't so popular is becuase "Design by Contract" is trademarked by Eiffel.

3

One hypothesis is that for a sufficiently large complex program, especially ones with a moving target, the mass of contracts themselves can become as buggy and hard to debug, or more-so, than the program code alone. As with any pattern, there may well be a usage past diminishing returns, as well as clear advantages when used in a more targeted manner.

Another possible conclusion is that the popularity of "managed languages" is the current proof of design-by-contract support for those selected managed features (array bounds by contract, etc.)

1
  • > mass of contracts themselves can become as buggy and hard to debug, or more-so, than the program code alone I have never seen this.
    – Kaitain
    Commented Apr 29, 2019 at 23:51
3

DbC would be used more extensively if the contracts could be checked at compile time so that it would not be possible to run a program that violated any contract.

Without compiler support, "DbC" is just anther name for "check invariants/assumptions and throw an exception if violated".

4
  • Doesn't that run into the halting problem?
    – Ceasar
    Commented Jan 16, 2012 at 20:07
  • @Ceasar It depends. Some assumptions can be checked, others cannot. For example there are type systems that make it possible to avoid passing an empty list as argument, or to return one.
    – Ingo
    Commented Jan 16, 2012 at 23:28
  • 1
    Nice point (+1) although Bertrand Meyer in his part of "Masterminds of programming" mentioned also their system of random class creation and calling checking for violation of the contracts. So it's a mixed compile time/run-time approach, but I doubt this technique works in every situation
    – Maksee
    Commented Apr 14, 2013 at 9:40
  • That's true to some extent, although it should be a catastrophic failure rather than an exception (see below). The key advantage of DBC is the methodology, which actually leads to better-designed programs, and the assurance that any given method MUST be in a legal state upon entry, which simplifies much of the internal logic. Generally you should not be in the business of throwing exceptions when a contract is violated. Exceptions should be used where the program can LEGALLY be in state ~X, and the client code must handle this. A contract will say that ~X is simply illegal.
    – Kaitain
    Commented Apr 29, 2019 at 23:54
2

The reason that most mainstream languages don't have DbC features in the language is the cost to benefit ratio of implementing it is to high for the language implementer.

one side of this has already been looked at in the other answers, unit tests and other runtime mechanisms (or even some compile time mechanisms with template meta programming) can give you much of the DbC goodness already. Therefore, while there is a benefit it is likely seen as quite modest.

The other side is the cost, retro fitting DbC into an existing language is likely too big a breaking change and very complex to boot. Introducing new syntax in a language without breaking old code is hard. Updating your existing standard library to use such a far reaching change would be expensive. Therefore we can conclude that implementing DbC features in an existing language has a high cost.

I'd also note that concepts which are pretty much contracts for templates, and therefore somewhat related to DbC, were dropped from the latest C++ standard as even after years of work on them it was estimated they still needed years of work. These sort of big, broad, sweeping changes to languages are just too hard to get implemented.

1

I've simple explanation, most people (including programmers) doesn't want extra work unless they see it necessary. Avionics programming where safety is considered very important I've not seen most projects without it.

But if you are considering website, desktop, or mobile programming -- crashes and unexpected behavior sometimes isn't considered that bad and programmers will just avoid extra work when reporting bugs and later fixing them is considered adequate enough.

This is probably the reason I think Ada never picked up outside of aviation programming industry because it takes more coding work although Ada is awesome language and if you want to build a reliable system, it is the best language for the job (excluding SPARK which proprietary language based on Ada).

Design by contract libraries for C# were experimental by Microsoft, and they are very useful for building reliable software, but they never picked momentum in the market otherwise you would have seen them by now as part of core C# language.

Assertions are not same as fully functional support for pre/post conditions and invariant. Although it can try to emulate them but a language/compiler with proper support does 'abstract syntax tree' analysis and check for logic errors which simply assertions can not.

Edit: I did a search and following is related discussion that might be helpful: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-world-languages-scala

-2

Mostly the reasons are as follows:

  1. It's only available in languages which are not popular
  2. It's unnecessary since the same stuff can be done different way in existing programming languages by anyone who actually wants to do it
  3. It's difficult to understand and use -- it requires specialized knowledge to do it properly, so there are small number of people doing it
  4. it requires large amounts of code to do it -- programmers prefer to minimize the amount of characters they write -- if it takes long piece of code, something must be wrong with it
  5. the advantages are not there -- it just cannot find enough bugs to make it worthwhile
5
  • 1
    Your answer is not well argued. You are simply stating your opinion that there are no advantages, that it requires large amounts of code, and that it is unnecessary since it can be done with existing languages (the OP specifically addressed this issue!).
    – Andres F.
    Commented Jan 6, 2012 at 2:15
  • I'm not thinking about assertions etc as a replacement for it. That's not correct way to do it in existing languages.(i.e. it wasn't addressed yet)
    – tp1
    Commented Jan 6, 2012 at 2:38
  • @tp1, if programmers really wanted to minimise the typing, they would never fall into something as verbose and eloquent as Java. And yes, programming itself requires "specialised knowledge" "to do it properly". Those not in possession of such a knowledge just should not be allowed to code.
    – SK-logic
    Commented Jan 6, 2012 at 7:50
  • @Sk-logic: Well, it seems half the world is doing OO wrong simply because they don't want to write forwarding functions from member functions to the data members's member functions. It's a big problem in my experience. This is directly caused by minimizing the number of characters to write.
    – tp1
    Commented Jan 7, 2012 at 1:06
  • @tp1, if people really wanted to minimise the typing, they would not even touch the OO, ever. OOP is naturally eloquent, even in its best implementations, like Smalltalk. I would not say it is a bad property, eloquence helps sometimes.
    – SK-logic
    Commented Jan 7, 2012 at 6:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.