This document discusses contracts in Ruby programming. It explains what contracts are from a design-by-contract perspective, how to implement them in Ruby through various libraries, and some of the challenges of doing so. It provides examples of contract programming in Ruby using preconditions, postconditions, and invariants to specify method contracts. It also notes some things to consider like method visibility, accessing method arguments, and default arguments.
3. What are contracts,
how to implement them in Ruby,
why it’s hard,
and do you really need them
4. TL;DR
The central idea of DbC is a metaphor on how elements of a software system collaborate with each other on the basis of mutual obligations and benefits. The metaphor comes from business life, where a "client" and a "supplier" agree on a
"contract" that defines for example that:
● The supplier must provide a certain product (obligation) and is entitled to expect that the client has paid its fee (benefit).
● The client must pay the fee (obligation) and is entitled to get the product (benefit).
● Both parties must satisfy certain obligations, such as laws and regulations, applying to all contracts.
Similarly, if a routine from a class in object-oriented programming provides a certain functionality, it may:
● Expect a certain condition to be guaranteed on entry by any client module that calls it: the routine's precondition—an obligation for the client, and a benefit for the supplier (the routine itself), as it frees it from having to handle
cases outside of the precondition.
● Guarantee a certain property on exit: the routine's postcondition—an obligation for the supplier, and obviously a benefit (the main benefit of calling the routine) for the client.
● Maintain a certain property, assumed on entry and guaranteed on exit: the class invariant.
The notion of a contract extends down to the method/procedure level; the contract for each method will normally contain the following pieces of information:[citation needed]
● Acceptable and unacceptable input values or types, and their meanings
● Return values or types, and their meanings
● Error and exception condition values or types that can occur, and their meanings
● Side effects
● Preconditions
● Postconditions
● Invariants
● (more rarely) Performance guarantees, e.g. for time or space used
9. TypeScript?
function foo(key, value) {
if (typeof key !== "string") {
throw new Error(`Expected key to be string.`);
}
if (typeof value !== "number") {
throw new Error(`Expected value to be number.`);
}
/// ...code...
}
function foo(key: string, value: number) {
/// ...code...
}
11. D Contract Programming
long square_root(long x)
in
{
assert(x >= 0);
}
out (result)
{
assert((result * result) <= x && (result+1) * (result+1) > x);
}
body
{
return cast(long)std.math.sqrt(cast(real)x);
}
12. D Contract Programming
class Date
{
int day;
int hour;
this(int d, int h)
{
day = d;
hour = h;
}
invariant
{
assert(1 <= day && day <= 31);
assert(0 <= hour && hour < 24);
}
}
14. Ruby Contract Programming
class BankAccount
attr_reader :balance
class << self
def less_than_balance?
all? positive_number?, clause {|n| n <= balance}
end
end
contract less_than_balance? => positive_number?
def withdraw(amount)
new_balance = @balance - amount
@balance = new_balance
return new_balance
end
end
github:bguthrie/handshake
15. Ruby Contract Programming
def division(a, b)
DBC.require(b != 0, "Divisor must be non-zero")
a / b
end
github:matholroyd/dbc
16. Ruby Contract Programming
pre {
...contract preconditions...
}
post { |result|
...contract postconditions...
}
def method
...code...
end
17. Ruby Contract Programming
class Math
pre {
assert x.is_a?(Numeric)
}
post { |result|
assert result.is_a?(Numeric)
}
def double(x, y)
x * 2
end
end
18. Noticeable things
def delegate(bounded_method, *args, **kwargs, &block)
# just maybe block
return bounded_method.(&block) if args.empty? && kwargs.empty?
# just regular args and maybe block
return bounded_method.(*args, &block) if !args.empty? && kwargs.empty?
# just keyword args and maybe block
return bounded_method.(**kwargs, &block) if args.empty? && !kwargs.empty?
# regular args and keyword args and maybe block
bounded_method.(*args, **kwargs, &block)
end
Transparent arguments passing: wrong way
19. Noticeable things
def method_missing(m, *args, &block)
r = true
target = self.__getobj__ {r = false}
if r && target.respond_to?(m)
target.__send__(m, *args, &block)
elsif ::Kernel.respond_to?(m, true)
::Kernel.instance_method(m).bind(self).(*args, &block)
else
super(m, *args, &block)
end
end
stdlib’s Delegator#method_missing