Diary?

2009-10-08
Thu

(02:45)

さて Caty 本体に入れる予定の機能のひとつに JSON データを RDB や Datastore に永続化するためのライブラリがあるのだが、どうしたもんかな。まずは Python に SQLite が付属してることからも RDB への永続化を考えており、そこでの問題は二つある。

  • JSON スキーマの変化にどう対応するか
  • 階層的データをどうマッピングするか

の二つだ。 Caty の利用者層及び利用される場面を考えれば、最初にスキーマをガッツリ決めてくださいってのは無理というか無謀。最終的にスキーマが決まるにしても、その途中ではスキーマの変化が起こりうる。そこで「スキーマ書き直す度に DB 作り直せ」ってのは、なんていうか有り得ないだろ。最低でもカラムの増減程度には対応しないと。

それに何より JSON スキーマではオプショナルな項目の存在を認めている。例えば以下のスキーマを考える。

type foo = object {
    "x": string,
    "y": integer
};

この場合、 x でも y でもない項目に対しては any 型が対応付けられ、任意のデータを格納可能となる。流石にそれはヤバいというのであれば、こういうアプローチも可能である。

type foo = object {
    "x": string,
    "y": integer,
    *: string
};

任意の項目名で string 型を格納可能となる。もしも他の項目を認めないなら、その場合は never 型を指定すればよい。デフォルトでは any 型なのはまあやり過ぎなんだが(せめて string か)、何にせよ割と緩めのスキーマではある。

つまりこれは実際にはスキーマレスな RDB の設計に近いのだが、そのスキーマレスな RDB の設計手法は既にいくつか知られているものがあり、データ数のオーダーが大したことなければ下記の論文のようなモデル写像アプローチが都合がいい。

おそらくこの論文のアプローチを元にした手法でいけるはず。配列・リスト・タプルをどう扱うか、検索する時のクエリ言語をどうするかが問題だが、まあアイディアはある。

あとは元のスキーマがアグレッシブに変化しても過去のデータが取り出せるような細工が必要かどうかだ。流石に元々 string だったのが object になりましたとかいうのはダメだろうから、多分そういう部分はデフォルト値を入れて、あとは普通に取り出せるというのが妥協点だな。

(14:44)

Caty のスクリプトのパーサにすげえバグ発見。今気がついて良かったというか、テストコードを書き直して初めて発覚した。いや、実際には Caty のバグと言い切っていいかは微妙なんだが。

Caty スクリプトのオプション引数の解析には Python の OptionParser クラスを利用しているのだがこいつがなかなかの曲者で、

  • デフォルトでは -h, --help, --version オプションを渡すと強制終了(sys.exit)
  • parse_args の引数として None を渡すと sys.argv を使い始める

と二つの罠に引っかかった。最初の奴はだいぶ前に直したんだが、二個目の奴はいままで気がつかなかった。どっちにしろ適切なメソッドをオーバーライドしたクラスを用意すればいいのだが、どうにも気持ちが悪いというかなんというか。

今回の問題の本質というのは、おそらく null 値が認められているのが原因だろう。問題になった OptionParser のメソッドは次のような定義になっている。

def _get_args(self, args):
    if args is None:
        return sys.argv[1:]
    else:
        return args[:]
    
def parse_args(self, args=None, values=None):
    rargs = self._get_args(args)
    :
    :

引数の値が None かどうかで処理を振り分けているが、ここでの問題は parse_args に None 値が入り込むことを防ぐ事が難しいということだ。もしも適当な文字列処理をした結果を parse_args に渡すとして、その値が None でないことをどうやって保証するのか。テストを書くのは確かにいいことだが、テストは大体において漏れるものだ。今回このテスト漏れが発生した理由は

  • Caty のコマンドインタープリタも Web サーバもコマンドライン引数なしで起動する
  • 以前に書いていたテストドライバの起動もコマンドライン引数ゼロだった

という条件が重なった結果だ。そして新しいテスト環境ではコマンドライン引数を使っての起動となったので、結果としてバグが発覚したわけだ。

いっちゃあなんだが、今主流となっているプログラミング言語の多くはいつの間にか紛れ込んだ null 値に対して無防備過ぎる。契約による設計をサポートした言語なら多少はマシかもしれないが、そちらもテスト漏れと同じ問題を抱えている。なので、殆どの言語では null 値との永遠に続く敗戦処理に苛まれ続けることになる。

この問題に対する解法は Haskell の Maybe モナドや OCaml の option あたりで、確か Scala にも Option 型があったと思うが、 null 値が使える時点で片手落ちというかダメだ。俺はもういい加減 null との戦いには終止符を打ちたいのだが、どうも C# の nullable や Java の Null dereference expressions を見ると、世界はどうも間違った方向に進んでいるようにしか思えない。

Creative Commons
この怪文書はクリエイティブ・コモンズ・ライセンスの元でライセンスされています。引用した文章など Kuwata Chikara に著作権のないものについては、それらの著作権保持者に帰属します。