DBFlute-1.1になったらStrict

DBFlute-1.1になったらどうなるシリーズ、
前回のに引き続きです。

DBFluteのJava8版というか1.1ではこうなる

今回は、地味ながらインパクト大きい話をします。
早速ズバリ...

CBでnullや空文字入れたら例外に!

します。(デフォルトで)

決断いたしました。
前回のブログで「まだ悩んでるのも」として、
取り上げていました。
//
// 1.0.x (いままで)
//
MemberCB cb = new MemerCB();

cb.query().setFoo_Equal(null); // 素通り
cb.query().setFoo_Equal(""); // 素通り
cb.query().setFoo_InScope(空リスト); // 素通り

cb.checkInvalidQuery(); // 例外にする!
cb.query().setFoo_Equal(null); // ★例外に

//
// 1.1 (これから)
//
MemberCB cb = new MemerCB();

cb.query().setFoo_Equal(null); // ★例外に
cb.query().setFoo_Equal(""); // ★例外に
cb.query().setFoo_InScope(空リスト); // ★例外に

cb.ignoreNullOrEmptyQuery(); // 今までと同じ仕様にする!
cb.query().setFoo_Equal(null); // 素通り
1.0.x系では、デフォルトが素通り (条件無視) でした。
何も設定しなかったのと全く同じです。
これは、検索画面での条件設定にフィットします。
テキストに何も入力されなければ条件にしない、
入力されたら有効な条件にする、という挙動がぴったり。

ですが、ロジック内での検索ではあまり意味がなく、
それどころか「全件検索事件」を招いてしまう可能性があると。
// 1.0.xでの最悪のケース
cb.query().setMemberId_InScope(list); // 空っぽ素通り
List<Member> memberList = memberBhv.selectList(cb);
memberBhv.batchDelete(memberList); // 全件削除
queryDelete()であれば全件削除は許されていません。
そもそもパフォーマンス的にも実装的にも
queryDelete() の方がgoodですが、
固定条件が一つでもあったらやっぱり削除できてしまいます。
// これも1.0.xでの最悪のケース
cb.query().setMemberId_InScope(list); // 空っぽ素通り
cb.query().setBirthdate_IsNull(); // 固定条件
memberBhv.queryDelete(cb); // 全件じゃないけどまじつらい

引数チェックのジレンマ

プログラミングを長くやってきた一個人としては、
それなりにでも引数チェックをしてほしい、
という気持ちがあります。

jfluteが若い頃、師匠から、
「外から来たデータは絶対に信用するな」
と教えられました。
常に引数の入出力では「あるのかないのか」を意識して、
「必要であれば引数チェックを、念のためのチェックを」
という感覚で、
「なのでそういうもんだから今の仕様で問題ないだろう、
 というか、とにかく検索画面で便利なんだから」
と正直、安易に考えてしまっていました。

いまでも、引数チェックに対する価値は同じく持っています。
jfluteが研修・フォローイングしている現場では、
耳にタコが引っ付くほど聞かされている話かと思います。

ですが、数年に一度ほど全件検索事件が耳に入ると、
「ええ...なんでチェックしないのぉー(><」
という落胆の思いと、
「DBFluteでなぜ防いてあげられないか...」
というjflute自身に対する怒りの思いの板挟み。

引数チェックは必要、でもそれは理想、
こういうことは他にもたくさんあるかと思います。
そこに全て寄りかかってはいけない。
最後の砦として、
ConditionBean が安心できる
インターフェースになるべきだろうと。

安全・安心・スピードのフレームワーク

DBFlute、というかConditionBeanの特徴、
それはやはり「安心・安全・スピード」です。

DBFluteはやっぱりここがいい、的な話をしてくれたとき、
多くのDBFluteユーザーからもらっている言葉です。

安全だから安心で、スピードが出せる

DBFluteは、とても弱いフレームワークです。
これからも現場で活躍していくためには、
しっかりと「DBFluteとは?」をjflute自身も見極めて、
将来のためにその特徴を最大限活かす仕様にしようと決めました。
たくさん、たくさん、考えました。

デフォルトって重要

cb.ignoreNullOrEmptyQuery()を用意しても、
その存在に気付かない人というのは必ずいらっしゃいます。

「if文書かないとエラーになるってうざいなぁ」

(いやそれは ignoreNullってのがあってですねー><)

「えっ、わざわざ ignoreNull とかやんなきゃいけないの?」

って言われるのは目に見えてますが、
if文を書き過ぎても大きなトラブルにはつながりにくいですが、
ノーチェック検索は大きなトラブルにつながりやすい。
なので、覚悟します。
ダサいインターフェースと言ってくださいm(_ _)m。

昔、Railsの作者の方が雑誌のインタビューで言っていた言葉を
何度も思い出します。
「デフォルトっていうものがどれほど重要か、みなわかってない」
と、ちょっと昔のこと過ぎてニュアンスが正確じゃないですが、
DBFluteを運用してきてずっとその言葉が身に沁みるのです。

もとに戻せます

もちろん、1.0.x系からの移行のためにも、
dfpropでデフォルト挙動をを簡単に戻せます。

littleAdjustmentMap.dfprop にて、
isNullOrEmptyQueryAllowed で変更できます。
# littleAdjustmentMap.dfprop

; isNullOrEmptyQueryAllowed = true
で、デフォルトで Allowed にした上で、
ここの ConditionBean だけはチェックしたい、
というときは、

MemberCB cb = new MemberCB();
cb.checkNullOrEmptyQuery();

とすれば、チェックされるようになります。
既にある checkInvalidQuery() と同じです。
もし、こちらのメソッドをたくさん使っていて、
移行が大変であれば、同じくdfpropにて、
isCompatibleConditionBeanOldNamingCheckInvalid
で、古い名前のメソッドを復活させることができます。

画面の検索条件のときは ignoreNull

まあ、とにかく今後は、
(dfpropで特に何も設定していなければ)
画面入力の検索条件のときはこれを呼ぶ!
cb.ignoreNullOrEmptyQuery()
と思って頂ければと。

ignoreで始まるメソッドは、これだけです。
誤解の無いように「無視する」という表現から
そのまま取りました。

また、この null や空文字の無効なQueryに対して、
もともと InvalidQuery という概念名を付けていましたが、
NullOrEmptyというベタベタな名前をあえて付けました。

これを知らないプログラマーがソースを見たとき、
想像が付きやすいように、目に付きやすいように。

これを知らないプログラマーがメソッド補完したとき、
「欲しいの、これじゃないかな?」
って言う風に、気が付きやすいように。

単純で覚えやすいように
 -> nullと空文字のquery()を無視

誤解がないように
 -> あくまでquery()のお話、空文字も含む

言葉で発しやすいように(!?)
 -> いぐのあーぬる (おあえんぷてぃー)

長過ぎず短すぎずでこの名前になりました。

細かいけどFromToのお話

関連して、
FromToの「片側だけ設定」の扱いがちょっと変わります。
MemberCB cb = new MemerCB();

FromToOption option = new FromToOption().compareAsDate();
cb.query().setXxx_FromTo(null, 2014-09-26, option); // ★例外に
cb.query().setXxx_FromTo(2014-09-26, null, option); // ★例外に
cb.query().setXxx_FromTo(null, null, option); // ★例外に

option.allowOneSide(); // 片側設定をOKに
cb.query().setXxx_FromTo(null, 2014-09-26, option); // <= 26日
cb.query().setXxx_FromTo(2014-09-26, null, option); //  >= 26日
cb.query().setXxx_FromTo(null, null, option); // ★例外に

cb.ignoreNullOrEmptyQuery();
cb.query().setXxx_FromTo(null, null, option); // 何もなかったことに
Queryのチェックがあろうがなかろうが、
今まではFromToでの片側だけの設定が許されていましたが、
デフォルトでは、許されなくなります。
これも Strict にするポリシーの一環です。
明示的に allowOneSide() とやった場合のみ片側設定できます。

こちらも、もちろん dfprop で元に戻せます。
littleAdjustmentMap.dfpropにて、
isCompatibleConditionBeanFromToOneSideAllowed
で、チェック状態でも片側に null を入れることができます。
# littleAdjustmentMap.dfprop

; isCompatibleConditionBeanFromToOneSideAllowed = true

上書き設定も例外に

もう一つ、Strictにした仕様があります。
上書き禁止です。
#
# 1.1
#
MemberCB cb = new MemerCB();

cb.query().setXxx_Equal(3);
cb.query().setXxx_Equal(4); // ★例外に

cb.enableOverridingQuery(); // 今までと同じ仕様にする!

cb.query().setXxx_Equal(4); // 4で上書き
cb.query().setXxx_Equal(5); // 5で上書き
こちらはあんまりインパクトは少ないと思われますが、
時々この挙動に乗っかってる実装を見かけます。

意図しているならいいのですが、
予期せぬ上書きによるバグも時々見かけるので、
安全よりの方に倒させて頂きます。

「あれっ、したら ConditionBean の再利用は?」

上書きを使って ConditionBean を再利用するケースが
ありますが、どうしてもというなら enable メソッドで。

ですが、そもそもConditionBeanの再利用は、
ArrangeQuery がオススメです。
 => where句の再利用 | DBFlute

なので、デフォルトでは例外とさせて頂きます。

Strict but 戻せる

Strictなインターフェースを追求しながら、
でも、結局 dfprop で戻せるので互換は保てる、
(既存プロジェクトに適用できる)
というような感じです。

1.1の前に1.0.x

1.1の前に、これらのオプションを入れておいて、
まだ OFF の状態でリリースする 1.0.x を準備します。

1.0.x系でも新規の開発で1.1を見越しての利用であれば、
dfpropでこれらのプロパティに逆に false を設定して、
1.1と挙動に同じにしてもいいかもしれません。