Question

I have a large project that is build contract based, where all functions verify input parameters and return values. But there are also cases where they for example verify that a call to a function is not null(while the same function also verifies that it doesn't return null).

And I am starting to think, what is the real value in this? When it comes to verifying input from users or databases and such I am all on board, but when it is in the domain it feels rather forced an unnecessary.

It is kind of like just removing all unit tests and testing in production instead with all the asserts/checks.

And with these checks you as a user of the function does not really get anything out of it because to get the contract you would have to go into the code and check all assertions to understand the contract(for example if null is allowed or not) and if a check fails, it throws an exception because of bad programming that needs to be solved either way.

So what is the real benefit of writing code like this? And isn't it more useful to have the contract in the documentation of the function so that it is easy to read, and the verification in unit tests to "verify" that there are no programming errors?

Was it helpful?

Solution

A functions/methods contract is a very real thing. I like to use sqrt(x) as an example:

  • x must be real
  • x must not be null
  • x >= 0
  • return value not null
  • return value >= 0

Invoking a function not compliant with the contract or using it's return value in a way the contract does not guarantee leads to errors and bugs, inevitably.

The next thing to realize is that continuing a task with flawed information is far worse than crashing. If a bug appears and you continue running the application with the incorrect data from that function you risk polluting/corrupting your data. That corruption can spread aroudn your application like a virus, causing crashes in other places and corrupting even more data. If its more subtle and you fail to notice the problem in time you may not even be able to recover from a backup.
That is way it's better to fail fast/early and just tell the user "sorry, we can't do this for you right now, please come back tomorrow".


A lot of points in the contract of most everyday code can be inferred (e.g. parameters and return values not null unless stated), a function does not modify its parameters unless the name cleary indicates it (addTo, ...).

But in every larger project there is a considerable amount of contract elements that a user of the function can only make out by reading the entire code. No matter how hard you strive towards always programming by the conventions you defined, there will always be some functions.

Now, depending on how important it is for the program to crash rather than output garbage (e.g. avionics), you want to prevent contract violations (because, as stated, they inevitably lead to bugs and errors). To do so there are basically three options available to the software engineer:

  • expect users of code to read it thoroughly before using it
  • document the contract in the documentation comment and expect users of code to read that documentation before using it
  • code the contract into the function and crash whenever it is violated

The first option almost never works. People usually only read other code when it's not doing what they expect it to in their current task.
The second option is only slightly better, depending on the programmers working with the code. Some read docblocks, others don't even know they're there and instead spend ten minutes researching on the internet what the docblock could have told them in 10 seconds.
The third option doesn't give the user a choice. If the function is not invoked correctly, the program crashes before further harm can be done. If the function doesn't behave well the output assertion will crash the program.

Then also there is the thing with changes in the contract. Those usually happen unconsiously. But when a functions contract changes a lot of its usages have to be adjusted, too. Another way to look at this: documentation (especially contracts) is code that never runs. It is therefore doomed to rot pretty quickly. And rotten code will not do you any good.

With option 1 there you are out of luck unless it triggers a bug covered by your testsuite. Same goes for option 2.
Option 3 will make your testsuite fail in a lot of places unless you adjusted the contract. That will at least give you a chance to notice you just changed the contract and adjust client code accordingly.


Extremes are almost never the best option as goes for excessive use of such assertions. IMO it is important that you define conventions project-wide (or codebase-wide). You can then use assertions for the cases where you deviate from the convention. Note that, for the example sqrt(x) this would still leave assertions like such:

  • x must be real
  • x >= 0
  • return value not null
  • return value >= 0

To address you question regarding unit tests: contract assertions are in no way a replacement for unit tests. They complement each other. Just look at sqrt(x) again: Asserting that the output is not null and >= 0 does not at all test whether sqrt(4) == 16.

Licensed under: CC-BY-SA with attribution
scroll top