ククログ

株式会社クリアコード > ククログ > Ruby/groonga 0.0.2リリース

Ruby/groonga 0.0.2リリース

最新のgroongaに対応したRuby/groonga 0.0.2がリリースされました。

Ruby/groonga 0.0.2ではよりAPIが使いやすくなっています。

メソッドの個別化

groongaはgrn_objで抽象化されていて、ハッシュテーブルでも転置インデックスカラムでもgrn_obj_search()で検索できます。Ruby/groongaでもそれを踏襲してGroonga::Object#searchだけを定義して使いまわしていました。しかし、0.0.2ではGroonga::Hash#serachやGroonga::IndexColumn#searchなど、それぞれのオブジェクト毎に定義するようにしました。

こうすることにより以下のような挙動になるため、使いやすいAPIになりました。

  • grn_obj_search()に対応していないオブジェクト(例えばGroonga::Array)に対して#searchしようとするとNoMethodErrorと適切に問題を報告する。
  • 省略可能なオプション引数をより正確に検証して適切なエラーを報告する。
  • それぞれの#search毎にドキュメントを用意することができるので、適切な内容のドキュメントがかかれたAPIになる。

利用できないのであれば、メソッドが定義されていない方がよいAPIだと思います。無駄なものがない方が適切なAPIに誘導しやすくなります。

無駄なものはない方がよいということは、メソッドだけではなく、省略可能なオプション引数にも言えます。1つのメソッドで何でもやろうとすると余計なオプションまで受け付ける必要があります。あるいは、余計なオプションを排除するためのコードが増えてしまいます。こうならないために、適切な粒度で別々のメソッドを定義することが有効です。

例えば、オプション名を検証するコードは以下のように書けます。(エラーメッセージに入力値と問題となった値を両方含めていることにも注意してください。問題を解決するための重要な情報です。)

def search(options={})
  valid_keys = [:name, :path]
  invalid_keys = options.keys - valid_keys
  unless invalid_keys.empty?
    message = "invalid option name(s): #{invalid_keys.inspect}: #{options.inspect}"
    raise ArgumentError, message
  end
end

もし、1つのメソッドでたくさんの状況を考慮しなければいけないとこのようになります。

def search(type, options={})
  case type
  when :fast
    valid_keys = [...]
  when :remote
    valid_keys = [...]
  else
     raise ArgumentError, "invalid type: ..."
  end

  invalid_keys = options.keys - valid_keys
  unless invalid_keys.empty?
    message = "invalid option name(s): #{invalid_keys.inspect}: #{options.inspect}"
    raise ArgumentError, message
  end

  case type
  when :fast
    query = options[:query]
    ...
  when :remote
    remote = DRbObject.new("druby://#{options[:host]}:2929")
    remote.search(options[:query])
  ...
  end
end

これよりは、メソッドを分けた方がすっきりします。

def fast_search(options={})
  valid_keys = [...]
  ...
end

def remote_search(options={})
  valid_keys = [...]
  ...
end

...

あとは、総称的なメソッドを1つ用意すればメソッド分割前と同じように使えます。

def search(type, options={})
  case type
  when :fast
    fast_search(options)
  when :remote
    remote_search(options)
  ...
  end   
end

ここまできたらもう一歩です。オブジェクト指向プログラミングでcase whenやswitch caseで分岐している時はオブジェクトが足りない匂いを感じとってください。このような場合はそれぞれの条件毎にオブジェクトを作り、それぞれのオブジェクトで同じ名前のメソッドを定義します。

class FastSearcher
  def search(options={})
    ...
  end
end

class RemoteSearcher
  def search(options={})
    ...
  end
end

これで、条件分岐がなくなり、総称的なメソッドも定義しなくてもよくなります。それらは言語がやってくれるからです。

searcher = FastSearcher.new
searcher.search(:query => ...)

と、だいぶ遠回りをしましたが、Ruby/groonga 0.0.2では以上のようなAPI設計ポリシーに従って、オブジェクト毎にメソッドを実装するようになりました。これにより使いやすさが向上しています。

また、メソッドが分割されることにより、ドキュメントを書きやすくなります。読む側も読みやすくなります。

まとめ

Ruby/groonga 0.0.2はAPIの使いやすさが向上しています。これは、適切な粒度に実装を分割したからです。使いやすいAPIを検討しているのであれば、実装の粒度を細かくすることを検討してみてください。無駄がなくすっきりして使いやすいAPIになるかもしれません。