文章目錄
- 誰來做Validation
- 何時做Validation
- 如何表達錯誤
- 如何傳遞錯誤
- 如何關聯錯誤到發生錯誤的對象圖, 尤其是對象圖中非Root對象
- 一個額外的話題
- 誰來做Validation
- 何時做Validation
- 如何表達錯誤
- 如何傳遞錯誤
- 如何關聯錯誤到發生錯誤的對象, 尤其是對象圖中非Root對象
這裡的Validation指的是對進入到系統中的業務資料的校正(不包括Web應用中頁面資料在瀏覽器端的驗證)
誰來做Validation
資料的有效性不是自身所能決定的, 而是使用它的情境(Context)決定的, 因此, 每個Context應該有自己的Validation邏輯.
一個例子, 個人資訊殘缺, 比如婚姻狀況沒填, 但聯絡地址電子郵件等資訊完備, 那麼這個個人資訊到底是合法還是非法? 如果你的應用是個稅務相關的應用必須知道婚姻狀況則資料是非法的, 如果你的應用是CRM系統有客戶的連絡方式即可而婚姻狀況是可選的則資料就是合法的. 問題是你的應用是稅務應用但同時支援客戶關係管理, 那這段資料到底合法非法? 稅務應用只是年底的時候才有人用, 而客戶關係管理系統隨時都有人用, 假設資料是通過頁面提交的, 那這批資料到底該拒絕還是接受?
何時做Validation
通常有幾個時機, 對象被建立出來, 對象狀態改變, 以及對象被持久化. 公司專屬應用程式中同一份資料一般至少有兩種存在形式: 在資料庫中的持久化狀態, 以及在記憶體中以程式設計語言定義的對象形式存在. 那麼幾個時機:
- 手動建立對象, 就是應在建構函式中做驗證
- 架構幫忙建立對象, 比如從頁面Form綁定到Server端的對象時, 可以在綁定完成的那一刻做校正
- 存到資料庫裡那一刻
這就帶來一個問題: 可能要在三個地方做大體相同的驗證, 如何複用驗證規則?
另一個問題是: 在引入ORM的應用中, 程式設計語言寫好的驗證邏輯同樣可以應用在持久化到資料庫的那一顆, 那在SQL/DDL語句中定義的約束是否還必要?
如何表達錯誤
有兩個約束:
- 要提供易於使用者和技術服務人員理解的錯誤資訊
- 要提供儘可能豐富的資訊
常見的手段是用字串或者錯誤碼/ID, 這是不work的, 因為它們合并了錯誤本身和錯誤的表示:
出錯的地方可能距離需要展現錯誤的地方很遠, 或者有多種展現錯誤的介面, 或者有很多顯示方面的需求, 比如支援國際化, 報錯的地方是沒有能力也不需要知道錯誤是如何被展示的, 它要做的是儘可能報告關於錯誤的詳細資料, 包括違反了什麼規則, 出錯的欄位, 實際的值和期待的值等, 字串和錯誤碼/ID是沒有如此豐富的表達能力的
我們可以用對象來表達錯誤資訊, 對象的類型可以表示錯誤的類別, 對象的屬性/欄位可以攜帶各種與錯誤類型相關的資料. 然後在需要展現給使用者的那一刻, 再把對象翻譯成針對那個介面的顯示, 比如可以做國際化, 或者提供給程式員更技術化的描述. 而在錯誤資訊需要被顯示之前, 錯誤在系統中的傳遞, 都是以對象的形式進行的...
聽起來跟異常Exception很像?
幾個反例是.Net平台上的異常, 比如KeyNotFoundException, 它就不告訴你那個找不到的Key是啥, 還有Index越界, 就不告訴你index的值是多少, 還有資料庫連接逾時或者Transaction Timeout,死活不告訴你它等了多久逾時的, 讓你搞不清楚是你的逾時時間設的太短還是根本你的設定就沒生效
如何傳遞錯誤
收集參數, 輸出參數, Thread Local, 或者拋出異常然後合適的層次捕獲
如何關聯錯誤到發生錯誤的對象圖, 尤其是對象圖中非Root對象
給錯誤一個Key, 這個Key應該能表示出錯的對象在對象圖中的位置, 比如Key可以是欄位名稱中間用"."分隔, 級聯起來的字串. 注意這種形式的key應該是在調用驗證邏輯的地方組裝起來的, 而不應該是驗證邏輯本身, 因為驗證邏輯通常並不知道自己驗證的這個對象在父物件中的欄位名稱
一個額外的話題
很多驗證架構採用了基於Attribute/Anontation的方式, 這樣當一個對象在不同的Context有不同的驗證邏輯時就會很糾結, 因為它是以侵入的方式寫到對象的定義中的. 這恰恰從另一個角度說明了對象是不應該跨Context複用的, DCI才是王道