2007-07-24

反政府的 API

勤務先のコードを見ていたら DeregisterXxx() という名前のメソッドがあった. そんな英語ないだろ ... とぐぐってみると, LKML のアーカイブがヒット. 投稿者の不満は私と同じだ. カーネルのコードに deregister_xxx () という関数がいくつもあるよ, いいの? という主旨. そうだそうだとスレッドを読み進めた私はある驚くべき事実を知る.

Rik van Riel の返信:

> "deregister" を使うに至った事情は何かあるんですか?
然り. 我々は反米国のテロリスト集団である.
辞書に無い新たな単語を大量に発明して米国経済を破滅に追いやろう.
連中の辞書産業に追従を許さず, 綴りを学ぶ余力を奪い,
英国との語彙戦争の中で溢れる語彙に沈め彼等の息の根を止めるのだ.

なんてこった! (若干誇張あり.) deregister にそんな意図があったとは ... 知らぬ間にずいぶん過激なソフトウェア開発に参加していたらしい. まきこまれ型サスペンスの気分. 日本支部としても doxygen からラを抜いて, パッケージはダメ字で 刷らねばなるまい. もっとも検索結果は "unregister" の 176 万件に対し "deregister" も 78 万件. Googlish の観点からはさほど革命的な趣味とも言えないな. この単語は...

API Design Matters

なんて話を書いてアッロードしようと思うもあまりの与太ぶりに気が引けてきた. のでもうちょっと話を続けてみる.

先月の ACMQ に "API Design Matters" という記事があった. 書いているのは C++ の分散オブジェクト環境を開発している ZeroC のエース Michi Henning. 色々と良い話をしているんだけれど, 登場するコードにギャフンとなってしまった.

Henning はダメな API の例として .NET Framework の Socket.Select() をとりあげている.

public static void Select (
  IList checkRead,
  IList checkWrite,
  IList checkError,
  int microSeconds);

この Select() は三つの IList を引数にとり, ready でないソケットを間引いて返す. 結果 ready なソケットが残る. Select() の引数は inout 変数として使われている.

この API が不味いのは明らかだ. 入力の引数に副作用があるのは嬉しくない. 意味的には list でなく set であるはずだ. など. ひととおりの批判を済ませたあとに Henning の示した代案は次のとおり:

public static int Select(
  ISet checkRead,
  ISet checkWrite,
  Timespan seconds,
  out ISet readable,
  out ISet writeable,
  out ISet error);

えーーーー. 散々偉そうなこといった末に out 引数かよ! しかも戻り値は ready なソケットの数らしい. えー... エラーの enum か, せめて timeout か否かの bool じゃないの. 結局知りたいのはそれだと自分でも言ってるのに. あんまりだ. がっかり. すばらしい演説がふっとんでしまった...

とはいえ Henning も "There are many other ways to fix the problems with Select()" と言っている. せっかくだから ruby を見てみよう.

ruby の IO.select は三つの配列を引数に取って, 配列の配列を返す.

# returns [[...],[...],[...]]
IO.select(reads[, writes[, excepts[, timeout]]])

やっぱり戻り値で返すのが筋だよねえ. (timeout すると nil が帰る.)

...という話を友達にしたら, えーーーー, と言われてしまった. 戻り値に配列はないでしょ. 名前つけないと中味がわからないじゃん, とのこと. たしかに. 丁度手元で select を試している. それらしくしてみよう.

 class Selection < Struct.new(:read, :write, :error)

   def select(timeout)
     ret = IO.select(read, write, error, timeout=0)
     (nil != ret) ? Selection.new(ret[0], ret[1], ret[2]) \
                  : Selection.empty
   end

   def self.empty() new([], [], []); end

 end

こんな感じで使う.

 ...
 sel = current_selection.select(timeout)
 sel.read.each { ... }
 ...

それっぽくはなった気はする.

それにしても不毛だなあ. もともと C の select() が色々とあんまりな作りだから, その上で頑張ってもたかが知れている. 先に引用した Henning の台詞も, 実は "...such as the approach used by epoll()" と続く. 不毛な争いはこのくらいにしておきたい. API design matters, and clustters in usual.