契約っぽいプログラミング

ふとD言語の仕様を眺めていたら、契約プログラミングがサポートされているらしい(http://www.kmonos.net/alang/wnd/whats.ja.html#dbc)ので、かなり心惹かれた。
あと、コンパイルがとても楽そうなのも素晴らしい。良い意味で節操がなさげなのもまぁ良いのだけれど、ライブラリがまだ弱そうなのが二の足を踏ませる。

ライブラリの充実度合いでプログラミング言語を選ぶのは、へたれプログラマーな気がするけど、私はへたれプログラマーなので何の問題も無かったりする。どうせインフラの面倒を見たり、開発ガイドラインを作ったりして、仕事でプログラムをろくに書いていない俺なんて俺なんて俺なん…

で、Rubyで動的にごにょごにょすれば契約っぽいことができるのではないか、と思いやってみた。とりあえずinブロックだけ試してみたけれど、Rubyだとinが予約語なのでRailsっぽくbeforeで。あと、急に眠たくなってきたので結構適当。

module Assertion
  require 'runit/assert'
  include RUNIT::Assert

  def self.included(mod)
    def mod.before(&assert)
      define_method(:before_assert, assert)

      def self.method_added(name)
        return if
          (/(.+?)_with(?:out)?_assertion/ =~ name.to_s and method_defined?($1)) or
          (method_defined?("#{name}_with_assertion") and
           method_defined?("#{name}_without_assertion")) or
          name == :initialize

        define_method "#{name}_with_assertion" do |*args|
          before_assert
          send("#{name}_without_assertion".intern, *args)
        end

        alias_method("#{name}_without_assertion", name)
        alias_method(name, "#{name}_with_assertion")
      end
    end
  end
end

とかModuleを作っておいて、適当なクラスにincludeしてbeforeブロックに検証内容を書いておくと…

class Hoge
  include Assertion

  before do
    assert(@age >= 18)
    assert(@name.length >= 8 && @name.length <20)
  end

  def initialize
    @age = 20
    @name = 'july juillet'
  end

  attr_accessor :age, :name
end

h1 = Hoge.new
puts h1.age
h1.age = 17
puts h1.age # => `assert_block': <false> is not true. (Test::Unit::AssertionFailedError)

h2 = Hoge.new
puts h2.name
h2.name = 'foobar'
puts h2.name # => `assert_block': <false> is not true. (Test::Unit::AssertionFailedError)

という風にエラーチェックができた(本当はエラーメッセージの行番号がわかりづらいのだけど内緒)。

outブロック的なものは、飽きてしまったので省略。

それにしても、もうちょっとすっきり書けないものか… 特にメソッド名空間を汚しているのが嫌だなぁ。