最新の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になりました。
利用できないのであれば、メソッドが定義されていない方がよいAPIだと思います。無駄なものがない方が適切なAPIに誘導しやすくなります。
無駄なものはない方がよいということは、メソッドだけではなく、省略可能なオプション引数にも言えます。1つのメソッドで何でもやろうとすると余計なオプションまで受け付ける必要があります。あるいは、余計なオプションを排除するためのコードが増えてしまいます。こうならないために、適切な粒度で別々のメソッドを定義することが有効です。
例えば、オプション名を検証するコードは以下のように書けます。(エラーメッセージに入力値と問題となった値を両方含めていることにも注意してください。問題を解決するための重要な情報です。)
1 2 3 4 5 6 7 8 |
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つのメソッドでたくさんの状況を考慮しなければいけないとこのようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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 |
これよりは、メソッドを分けた方がすっきりします。
1 2 3 4 5 6 7 8 9 10 11 |
def fast_search(options={}) valid_keys = [...] ... end def remote_search(options={}) valid_keys = [...] ... end ... |
あとは、総称的なメソッドを1つ用意すればメソッド分割前と同じように使えます。
1 2 3 4 5 6 7 8 9 |
def search(type, options={}) case type when :fast fast_search(options) when :remote remote_search(options) ... end end |
ここまできたらもう一歩です。オブジェクト指向プログラミングでcase whenやswitch caseで分岐している時はオブジェクトが足りない匂いを感じとってください。このような場合はそれぞれの条件毎にオブジェクトを作り、それぞれのオブジェクトで同じ名前のメソッドを定義します。
1 2 3 4 5 6 7 8 9 10 11 |
class FastSearcher def search(options={}) ... end end class RemoteSearcher def search(options={}) ... end end |
これで、条件分岐がなくなり、総称的なメソッドも定義しなくてもよくなります。それらは言語がやってくれるからです。
1 2 |
searcher = FastSearcher.new searcher.search(:query => ...) |
と、だいぶ遠回りをしましたが、Ruby/groonga 0.0.2では以上のようなAPI設計ポリシーに従って、オブジェクト毎にメソッドを実装するようになりました。これにより使いやすさが向上しています。
また、メソッドが分割されることにより、ドキュメントを書きやすくなります。読む側も読みやすくなります。
Ruby/groonga 0.0.2はAPIの使いやすさが向上しています。これは、適切な粒度に実装を分割したからです。使いやすいAPIを検討しているのであれば、実装の粒度を細かくすることを検討してみてください。無駄がなくすっきりして使いやすいAPIになるかもしれません。