The most annoying thing when writing public APIs is that it's a lot of redundant typing. A good API implementation should check all arguments and return a useful error message if an argument is invalid. Good API documentation should document which arguments values are valid and which return values are possible. Both tasks are work-intensive and annoying for the developer, which is probably one of the reasons why there so many bad APIs.
Here is a Java snippet with Sun-style argument handling:
/**
* Sets the discount as percentage.
* @param percentage the discount as percentage
* @throws IllegalArgumentException if percentage < 0 or percentage > 100
*/
void setDiscountPercentage(float percentage) {
if ((percentage < 0.0) || (percentage > 100.0))
throw new InvalidArgumentException("percentage must be >=0 and <=100");
mPercentage = percentage;
}
/**
* Gets the discount as percentage.
* @return the discount as percentage. Guaranteed to be positive and not
* higher than 100.0
*/
float getDiscountPercentage() {
assert (mPercentage >= 0.0) && (mPercentage <= 100.0);
return mPercentage;
}
So is it possible to remove this redundancy and specify the constraints only once? I think so.
The obvious solution is to add a list of assertions to each argument. This is an attempt at a very compact syntax:
void setDiscountPercentage(float percentage(>=0.0,<=100.0)) {
mPercentage = percentage;
}
float(>=0.0,<=100.0) getDiscountPercentage() {
return mPercentage;
}
Each argument is followed by a list of comma-separated constraints that can be checked before executing the function. To make the syntax more crisp it is allowed to start the expressions directly with a comparison operator. They will then be executed with the function argument as first operand. It's also possible to use normal boolean expressions, for instance to check the length of a string:
void setId(String id(!=null, id.length() > 0, id.length() <= 10)) {
//...
}
The syntax looks pretty cryptic though, because the declaration becomes very long and the nesting of parentheses is confusing. An alternative would be to declare the assertions after the normal signature:
void setDiscountPercentage(float percentage)
with percentage: >=0.0, <=100.0 {
mPercentage = percentage;
}
(float percentage) getDiscountPercentage()
with percentage: >=0.0, <=100.0 {
return mPercentage;
}
void setId(String id)
with id: !=null, id.length() > 0, id.length() <= 10 {
// ...
}
It's a little bit more verbose, and for return value constraints this syntax requires named return values,
but I think it is much more readable than the first attempt. Here an example with more arguments and a return value tuple:
(int a, int b, int c) create3PositiveNumbers(int x)
with a: >0
with b: >0
with c: >0
with x: >0 {
return (x, x, x);
}
I think it's quite nice. A few random things:
- The constraints need to be shown in the API, so they must use only public symbols.
Otherwise the user would not be able to understand them. The compiler should enforce this. - If a reference argument is null, any operation on it will fail. But quite often
null is allowed as an argument, which would make the constraints quite ugly. Imagine
a setId() variant that allow nulls:void setIdAllowsNull(String id) with id: (id==null) || (id.length() > 0), (id==null) || (id.length() <= 10) { // ... }An easy solution is the following rule: if an argument is a reference value and it is null, it always passes all tests, unless the first constraint is "!=null". With this rule the constraints can be simplified to
void setIdAllowsNull(String id) with id: id.length() > 0, id.length() <= 10 { // ... } - Overridden virtual functions always inherit the constraints and can not add any new constraints (or even change the original ones)
- Just like regular assert statements it should be possible to disable the argument checks if performance is critical



contract programming
Nice topic/thread. And as most of posters are pointing out, the solution would be better left to an enhanced compiler.
At my work we use a set of macros that allow us to very easily do contract programming:
PRECONDITION
POSTCONDITION
INVARIANTS
A PRECONDITION is called on parameters of the method that need to comply to some rules specific to that method.
A POSTCONDITION ensures that some internal data (or data that is passed from and to the outside with intermediate tranformation) complies to some specific rules for that method.
INVARIANTS is a macro that is inserted at the beginning and at the end of _each_ method in the class. The macro calls a special method (that all classes have to implement) called ::testInvariants(). This method lists all conceiveable rules to be respected by all data members of the class.
There is some sugar related to constructors and destructors as well as uninitialized object using checking.
And so you get an extremely powerful and very flexible system for contract programming that helps us to shorten the development time by about 40% (guestimate). And the big punch line comes with the fact that, being macros, these are all preprocessed out in optimised mode, removing all checking bloat.
This way, programmer-side consistency is enforced. The user-side correctness of input data is handled by a different mechanism.