Skip to content

Instantly share code, notes, and snippets.

@sunaot
Last active August 29, 2015 14:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sunaot/6b696d80e188c611e739 to your computer and use it in GitHub Desktop.
Save sunaot/6b696d80e188c611e739 to your computer and use it in GitHub Desktop.

制約フレームワークを書かないと連載させてもらえない Web 雑誌があるようなので、昨日こんなの書いてた。 静的解析もできないし、ドキュメンテーションにも活きないので各方面の識者から怒られそうだが {} で書けるという見た目の点で許してほしい。

あとはつっこまれるとしたら、継承したときに事前条件、事後条件が強化される/緩和されるのあたり。全部やると大変なんすよ。invariant もないですね (method_added でイベントフックしてすべてのメソッドコールへ prepend かぶせるとかすればできるかな)。

class BeerLover
  extend Contracts
  attr_accessor :age

  def initialize(age)
    self.age = age
  end

  assert_pre :drink, -> { age >= 20 } # can call instance methods
  def drink
    'beer'
  end
end

class Calcurator
  extend Contracts

  assert_pre :tasu, ->(a, b) { a > 0 and b > 0 }
  def tasu(a, b)
    a + b
  end

  assert_post :hiku, ->(result) { result >= 0 }
  def hiku(a, b)
    a - b
  end
end

実装はこんなかんじ。

module Contracts
  class AssertionError < RuntimeError; end

  def self.extended(obj)
    obj.include AssertionHelper
    obj.const_set('PreAssertions', Module.new)
    obj.const_set('PostAssertions', Module.new)
    obj.prepend obj.const_get('PreAssertions'), obj.const_get('PostAssertions')
  end

  def assert_precondition(method_name, assertion)
    self::PreAssertions.instance_eval do
      define_method(method_name) do |*args|
        raise AssertionError.new(method_name) unless self.instance_exec(*args, &assertion)
        super *args
      end
    end
  end
  alias_method :assert_pre, :assert_precondition

  def assert_postcondition(method_name, assertion)
    self::PostAssertions.instance_eval do
      define_method(method_name) do |*args|
        result = super *args
        raise AssertionError.new(method_name) unless self.instance_exec(result, &assertion)
      end
    end
  end
  alias_method :assert_post, :assert_postcondition

  module AssertionHelper
    def assert condition
      unless condition
        raise Contracts::AssertionError
      end
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment