Why Is "Design by Contract" Not More Popular?
Coryoth writes "Design by Contract, writing pre- and post-conditions on functions, seemed like straightforward common sense to me. Such conditions, in the form of executable code, not only provide more exacting API documentation, but also provide a test harness. Having easy to write unit tests, that are automatically integrated into the inheritance hierarchy in OO languages, 'just made sense'. However, despite being available (to varying degrees of completeness) for many languages other than Eiffel, including Java, C++, Perl, Python, Ruby, Ada, and even Haskell and Ocaml, the concept has never gained significant traction, particularly in comparison to unit testing frameworks (which DbC complements nicely), and hype like 'Extreme Programming'. So why did Design by Contract fail to take off?"
Comment removed based on user account deletion
Who cares if it's popular? If it solves your problem, use it.
My other car is first.
Design by contract seems like a lot of extra work and runtime cost for something that might once in a while catch a bug in already-deployed code. Lighter weight methods like static typing catch (certain kinds of) errors before the code is even compiled; unit testing is usually done before code is deployed, and with the express aim of exposing incorrect behavior in corner cases.
Just what niche is design-by-contract supposed to fill? It's heavyweight, costly at runtime, undirected, and likely to catch bugs only after deployment -- too-little-too-late. Maybe it's unpopular because it's a poor tradeoff.
A good business analyst will answer the question "What is it that we (the client) need." They help make sure that the excellent code you write, when it's doing as you were hired to write it to do, solves problems instead of making them.
A good architect will help establish clear separation of authority, giving team members more autonomy to go do what they're good at without having other peoples fingers in their pies or needing to leave their area of scope.
There are a lot of people with pieces of paper from a school that are terrible at these things, and they muddle along leaving wreckage behind them. But that doesn't dismiss the value of having someone competent in those roles when you can find them.
-1 Uncomfortable Truth
You know the aphorism about how any CS problem can be solved by another layer of indirection -- except the problem of too many layers of indirection. That's what design-by-contract is. Instead of having the intrinsic type-safety checks and the social trust that the code author has run unit tests if necessary and makes the code do something reasonable, design-by-contract formalizes all this and makes you specify conditions on the code manually. That's quite a bit of effort for relatively little advantage. The popular design-by-informal-agreement works almost as well and doesn't have the extra, unneeded layer of indirection.
Design by Contract, writing pre- and post-conditions on functions, seemed like straightforward common sense to me.
Yes, it is, and that's why everybody does it. It simply isn't called "design by contract" by most people, since it isn't actually design and isn't a contract. You also don't need language support for it. And people generally do this sort of thing in two parts: some conditions are always checked, but most are only checked in test frameworks.
I feel that the reason why design by contract (DBC from now on) isn't popular is because the entire point of the paradigm is that it doubles or triples your code length without adding any actual information; first, you tell the computer what should be true so you can do what you're going to do, then you tell it what to do, then you tell it what you should have done. That's a lot of typing just to make sure the computer fucks up in exactly the way you told it to.
Admittedly, I haven't programmed much in any language that has built in support for DBC, but from exercises in classes (I'm a CS major) I've found that generally it's sort of a waste of time at worst and a duplication of effort at best.
Regardless, the theory remains: if you can write pre- and postconditions for a function, you already know what the function is supposed to be doing so you might as well have spent your time writing the function and doing something else.
For instance, consider some list class's addElement function, with some (sorta) DBC assertions:
(And I apologize for no indenting, but the tabs got stripped out in preview so I'm assuming they're not there when I post)
Of course, this is an overly simple example and I'm probably not even doing it right; however, hopefully it's close enough that you can see what I mean. All of the assertions are semantically redundant; they don't add any meaning to the code. In fact, I don't think it's possible for that to be true in DBC; if an assertion somehow adds information to the code, it's not an assertion any more.
assert(condition) is your friend. It's not called a contract, it's not design (but a very good practice!) and it does the job well.
Apart from "it's too hard", I think Unit Testing has overtaken DbC as an approach.
/ DbcAndTesting.html and particularly the postscript.
- You can write unit tests in any language, with or without a framework. (I saw a "mini-framework" for C that consisted of three macros and a coding convention.)
- In a test, you can specify assertions before and after each method call. It's a little more tedious to represent classic DbC assertions, but the Abstract Test pattern among others allows you to collect common code.
- You can strip out assertions in production code simply by leaving test code out of the product.
- Unit tests also run scenarios automatically, without an extra "test driver".
The one thing Unit Testing *can't* do is check production code as it's running. On the one hand, that's great at catching conditions you never thought of. On the other hand, customers tend to get annoyed if their app shuts down. I'm sure there's some work on partial in the Eiffel world, but so far I haven't seen any.
See also http://onestepback.org/index.cgi/Tech/Programming
A lot of functions in real world programs just don't fit this model. Especially in GUIs. Or functions that manipulate internal data structures. Only the most trivial programs (or contrived exercizes of academics) strictly fit the functional, no-side-effects model for all functions. And if you can't apply this method to your entire program, you are going to find a more flexible way to verify behavior.
Those people who think they know everything are a great annoyance to those of us who do. (Isaac Asimov)
I'd agree with much of your post, but I think there's an unwritten assumption about programming style in what you wrote: you seem to be restricting your scope to imperative languages with mutable state (talking about locks and threading, for example).
If you're working in a language that doesn't permit generally mutable state, it's much easier to use concepts of design by contract, essentially because all you have to do is check that when you've finished constructing a new value, it is valid for whatever type it has. Of course, such languages have disadvantages as well.
I suspect that a great deal of work in programming languages over the next few years is going to focus on how to identify and localise side-effects more explicitly. Pure functional languages that don't allow mutable state at all seem to be quite inefficient, and have fundamental problems for high performance applications that have yet to be resolved. Things like the monads widely used in Haskell today provide some powerful features like mutability but built on a much sounder base than many of today's imperative languages, but at the cost of horrendous syntactic overheads, which kinda spoils one of the big advantages of adopting a functional language: conciseness.
However, multi-core and multi-processor machines are fast becoming mainstream, and loose imperative programming languages have failed to provide satisfactory tools to take advantage of these architectures. I expect this to drive a general move towards more declarative rogramming styles in the industry. Meanwhile the academics, who have seen it all before, will be working on more powerful models of scoping and side effects, well beyond the glorified block scope/lambda calculus stuff that most of today's mainstream programming languages are effectively built on. Once we start getting programming languages with more powerful ways to signify when it is acceptable for what sorts of side effects (including changes in state) to occur, we'll have the sort of foundation needed for your ideas about being inside/outside an object, and compilers will have the sort of framework needed to optimise DbC checks so they're only applied when they're really needed and don't carry unfortunate performance penalties.
If you disagree, post your argument. (-1, Overrated) isn't your personal censorship tool for views you don't like.
Design by contract, like most formal method approaches, doesn't scale to interesting levels. If you are working on a 200KLOC project on a tight schedule, the last thing you can afford yourself is increasing time spent per line of code by equipping classes, loops and methods with pre & post conditions. And you would need to do this on a substantial scale to make a significant impact on overall quality. I'm sure most projects could boost quality significantly if you double their budgets but then doing so is unacceptable in most real life situations. Good enough involves balancing a lot of factors and quality is just one of them.
It's great if you can specify that a piece of code is a 100% correct implementation of a given specification but in real life the requirements are sketchy at best & keep changing during development. So, you are likely to end up with the wrong system if you don't adjust your interpretation of them to reality during development. Besides, pre and post conditions need maintenance too if you are doing maintenance on your code, so effectively they increase the cost of what is the single most expensive development activity already: maintenance.
Besides there are other, much more useful tools for improving code quality: unit testing, integration testing, static code checkers, compile time type checking, inspections & reviews are all part of the toolkit of an experienced software engineer and largely remove the need for more formal approaches. Additionally clustering and redundant setups are a far cheaper way of guaranteeing uptime than proving the system to be correct. Risk management is better than trying to avoid risk at all cost.
And finally, the value of 100% correctness is overrated. Most commercial software functions acceptably despite the approximately 10 bugs per kloc. In theory disaster could strike any second, in practice it is a rare event that it does and the consequences are quite manageable usually. Of course things do go spectacularly wrong sometimes and usually people then find out a lot was wrong with the overall development process aside from not applying design by contract. So even then, the added value of design by contract is very questionable. You can't compensate for general incompetence with a couple of pre and post conditions.
Jilles
Here are several reasons why DbC has not caught on, ordered descending by importance:
1. Writing a contract is often just as difficult as writing its implementation, with the same potential for mistakes. Also, there seem to be no guidelines whether contracts for higher-level procedures should repeat some of the stuff already present in contracts for lower-level procedures which they invoke. The required case-by-case decisions are arbitrary and annoying.
2. Lack of language support. Without language support and inheritance, you will be forced to copy-paste contracts, increasing the risk of inconsistency. Unofficial language extensions like JML do not integrate well with development tools.
3. Lack of education. DbC is relatively unknown, increasing the risk that future maintainers will be baffled by your code. DbC requires deeper understanding of OO and a more systematic approach than informal unit testing.
4. Personality of the inventor. Meyer's strong opinions and attempts to push Eiffel/DbC onto the market made other academics hold back their endorsement.
if a test can be expressed as a constraint then it is useful to simply express that as a contract,
Us table-heads who like to shift the processing burden to the database instead of application languages would point out that this resembles database constraints and triggers.
Table-ized A.I.
How interesting for you to put these 2 questions together! Didn't your 2nd question just answered your first question (at least in theory)?
Now in practice, we obvious will be start developing before we got a complete set of requirements, which bring us to...
The big difference between code + unit tests vs contracts (at least from my understanding and experience), is that code and tests are "constructive" in nature, while contracts are mainly "prohibitive" in nature. By "constructive", I mean code and tests tell you something the program will do, vs "prohibitive" which means what the program will not do.
A piece code to parse a string "1234" into an integer 1234 and with test to call it using "1234" and asserting the result == 1234, now that tells you what the code does. The test does not tell you what other things the code might also do, such as it maybe the code can also handle -ve numbers ("-1234"), or formatted numbers ("1,234"), or even decimals "1,234.56" (if the return type is general enough to support it, such as returning type Number in Java).
When you put in assert() into the code, however, it tells what the code does not do. assert(result is integer) tells us the parse does not handle decimals, assert(string contains only numbers) tells us the code does not handles formatted strings, etc.
Comparing the two, code and tests are "constructive" as they give more features to your program, while contracts are "prohibitive" as they restrict what you program can do. More contracts you add, more things you prohibit your code from doing.
When the requirements change ("we have to handle dollar signs in front too, but no more decimals"), the now unused decimal feature and its tests can be ignored, but any contract ("string only contains [0123456789.,-]") that blocks the new requirement has to be removed. Guess which one, tests or contracts, make more problem for the team as the development progress and requirements change?
Oliver.
Unfortunately Mr. Meyer runs up against the halting problem. If the definition of a function, or the contract, says that a parameter must never be the NULL pointer, there are two choices: the code must check whether that pointer is NULL, or it must be proven that that pointer can never possibly be NULL. The second is, with the current state of the art, impossible no matter what language constructs are around to help. That means the check has to be done, the only question is whether it's done by the caller or the called function. And the first rule I learned is to eliminate redundancy, which means that given a choice of doing a check in one place or doing it in a large number of places you do it in the one place.
Design by contract is many things, a large number of them good, but it is not a replacement for error-checking.