Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Contracts in Ruby - Vladyslav Hesal


Published on

Ruby Meditation #14
8th of April, 2017
Kyiv, Projector

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Contracts in Ruby - Vladyslav Hesal

  1. 1. Vladyslav Hesal Github @arantir Twitter @hesalx
  2. 2. Contracts in Ruby
  3. 3. What are contracts, how to implement them in Ruby, why it’s hard, and do you really need them
  4. 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
  5. 5. TL;DR ● Preconditions ● Postconditions ● Invariants
  6. 6. Simply Subclasses are allowed to: ● weaken preconditions (but not strengthen them) ● strengthen postconditions and invariants (but not weaken)
  7. 7. TypeScript?
  8. 8. TypeScript?
  9. 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... }
  10. 10. D Contract Programming in { ...contract preconditions... } out (result) { ...contract postconditions... } body { ...code... }
  11. 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. 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); } }
  13. 13. Ruby Contract Programming Contract Num => Num def double(x) x * 2 end github:egonSchiele/contracts.ruby
  14. 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. 15. Ruby Contract Programming def division(a, b) DBC.require(b != 0, "Divisor must be non-zero") a / b end github:matholroyd/dbc
  16. 16. Ruby Contract Programming pre { ...contract preconditions... } post { |result| ...contract postconditions... } def method ...code... end
  17. 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. 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. 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
  20. 20. Noticeable things def delegate(m, *args, &block) target.__send__(m, *args, &block) end Transparent arguments passing: ok way
  21. 21. Noticeable things Method visibility class Foo def foo; end def bar; end private :foo private def bax; end end class Bar < Foo private def foo; end end
  22. 22. Noticeable things Method visibility ● no callbacks ● dynamic visibility changing ● unrestricted subclass overrides
  23. 23. Noticeable things Accessing method arguments elsewhere Kernel#binding def get_binding(param) return binding end b = get_binding("hello") b.eval("param") #=> "hello"
  24. 24. Noticeable things Default methods arguments
  25. 25. Noticeable things Default methods arguments
  26. 26. Noticeable things Accessing method arguments elsewhere inheritance, prepend, include ??? UnboundMethod#parameters
  27. 27. Noticeable things def args_def(method) do |param| args << param[1] case param[0] when :opt # optional regular argument "#{param[1]} = nil" when :rest # splat regular arguments "*#{param[1]}" when :key # optional keyword argument "#{param[1]}: nil" when :keyreq # required keyword argument "#{param[1]}:" when :keyrest # splat keyword arguments "**#{param[1]}" when :block # block "&#{param[1]}" else param[1] end end.join(', ') end
  28. 28. Ruby Contracts Aspect class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{method_name}(#{args_def}) return super unless respond_to?(__method__, false) __contract__.contract_eval(self, __method__, binding) { super } end RUBY
  29. 29. def #{method_name}(#{arg return super unless respo __contract__.contract_e end