在日常的開發過程中,我們常常要處理不同來源的數據。數據可能來自不可靠的外部系統、不可靠的用戶輸入和甚至設計有誤的數據庫表,因此,對數據有效性進行驗證是必要的工作。
開源工具ABAP Data Validator是一個使用ABAP開發的數據驗證工具,它可以簡化開發者在這方面的工作。本文將介紹它的用法和一些設計思路。
本文鏈接:https://www.cnblogs.com/hhelibeb/p/12206648.html
原創內容,轉載請注明
目的
具體而言,ABAP Data Validator將通過以下的思路簡化數據有效性驗證方面的工作:
- 提供統一的檢查接口,讓開發者通過單次方法調用就可以實現對數據的檢查。
- 將驗證邏輯集中實現,避免相似的檢查代碼分散在系統各處造成的邏輯不一致,從而降低相關程序的維護成本。
- 避免檢查過程中的潛在dump,減少開發者處理dump問題的精力花費。
為了實現以上目的,該工具實現了一些功能:
- 內置常見的驗證邏輯。
- 可配置的檢查規則。
- 可擴展的檢查程序。
- 異常的統一處理。
支持的檢查列表
ABAP Data Validator目前支持以下類型的檢查(持續更新中):
- 日期.
- 時間.
- 時間戳.
- 郵件地址.
- INT4.
- 正則字符串.
- URL.
- JSON.
- HEX.
- IMEI.
- GUID.
- BASE64.
- HTML (實驗性的).
此外,它也支持對內表字段的必填檢查、根據數據元素的類型進行檢查等功能,下文會詳細介紹。
使用
ABAP Data Validator支持多種檢查方式,下面會由簡單到復雜進行逐一介紹。
對單一字段的直接檢查
對於每種數據類型,ABAP Data Validator會有一個專門的檢查類,可以用這些檢查類的is_valid方法來檢查變量的值是否有效,就像使用abap的內置函數那樣。比如,要檢查一個字符串是否是有效的郵箱地址,可以用如下代碼實現,
IF zcl_adv_email_check=>is_valid( 'example@github.com' ). "do something ENDIF.
所有的檢查類都實現了接口zif_adv_check,因此它們都擁有靜態方法is_valid,如下圖(為了方便閱讀,圖中沒有包含全部的實現類),
對於類方法而言,is_valid是個別名,也可以不使用別名來調用接口方法:
IF zcl_adv_email_check=>zif_adv_check~is_valid( 'example@github.com' ). "do something ENDIF.
當然並不推薦這種方式,因為會讓代碼變長。
檢查類的命名規則是ZCL_ADV_類型名_CHECK。
使用參考數據元素檢查
為了便於使用,方法is_valid的定義十分簡單,只有一個輸入參數、一個返回值,不包含任何異常,這使得它無法勝任數量、金額等類型(一般而言是P類型,包含長度和小數位定義)字段的檢查。為了解決這一問題,ABAP Data Validator提供了參考數據元素進行檢查的用法。可以使用類zcl_adata_validator的方法validate_by_element使用該功能,示例如下,
DATA(result) = NEW zcl_adata_validator( )->->validate_by_element( data = data element = 'MENGE_D' "數量 ).
result是一個結構,包含兩個字段valid和type,如果data的值無法轉換為數字、或者轉換后溢出的話,result-valid將被賦值為abap_false(即空);如果data是一個有效值,那么rresult-valid將被賦值為abap_true(即'X')。
正常情況下,result-type總會被賦值為數據元素的類型,對P類型,則為固定值’PACKED‘(c_type_packed)。但如果輸入的數據元素無效的話,type則會被賦值為固定值’Invalid Type‘(c_type_invalid)。
目前支持以下類型的數據元素:
- 日期
- 時間
- 時間戳
- INT4
- GUID
- HEX
- DEC(P類型)
為了保持檢查方法的簡單性,validate_by_element方法的定義同樣不包含異常。任何異常都會在方法內部被處理。出現異常時,result-valid會被賦值為abap_false,type會被賦值為’Invalid Type‘。
內表檢查
ABAP Data Validator可以根據指定的規則對內表進行檢查,如下所示,將規則my_rules和內表uploaded_data傳入類zcl_adata_validator的方法validate,可以得到檢查結果內表results,
TRY. DATA(results) = NEW zcl_adata_validator( )->validate( rules = my_rules data = uploaded_data ). CATCH zcx_adv_exception INTO DATA(ex). DATA(msg) = ex->get_text( ). ENDTRY.
檢查規則rules用於設定檢查邏輯。
rules的類型定義如下,
TYPES: BEGIN OF ty_rule, fname TYPE name_komp, "字段名 required TYPE abap_bool, "必填 initial_or_empty TYPE abap_bool, "必須為空或初始值 user_type TYPE ty_spec_type,"檢查類型(參考類zcl_adata_validator的常量列表) regex TYPE string, "自定義正則表達式 regex_msg TYPE string, "自定義正則表達式檢查失敗消息 ref_element TYPE rollname, "參考數據元素 END OF ty_rule. TYPES: ty_rules_t TYPE HASHED TABLE OF ty_rule WITH UNIQUE KEY fname.
簡單的示例如下,
DATA: rules TYPE zcl_adata_validator=>ty_rules_t. *字段1為必填字段,類型是日期,字段2非必填,類型為郵件地址 rules = VALUE #( ( fname = 'FIELD1' required = abap_true user_type = zcl_adata_validator=>c_type_date ) ( fname = 'FIELD2' user_type = zcl_adata_validator=>c_type_email ) ).
擴展檢查功能
使用ABAP Data Validator自帶的檢查邏輯可以滿足很多場景下的基本檢查需要,但是你也許還有更多的檢查邏輯。有2種方式可以實現檢查功能的擴展。
- 通過rules-regex傳入正則表達式。
- 創建新的檢查類型:定義新的類型名,創建它的檢查類,實現接口zif_adv_check,將自定義檢查邏輯寫在is_valid方法的實現中。接着,將類型名和類名傳入zcl_adata_validator的構造方法constructor中。這樣就可以使用新的檢查類型了。
正則表達式示例:如果你不僅要檢查一個字段的值是否為郵件地址,還要驗證它是gmail郵箱,那么可以把正則表達式’gmail\.com$‘復制給rules-regex,
DATA: rules TYPE zcl_adata_validator=>ty_rules_t. DATA: cases TYPE ty_case_t. cases = VALUE #( ( field3 = 'ZZZ2@gmail.com') "正確,是gmail郵箱 ( field3 = 'ZZZ2@qq.com') "不正確,非gmail郵箱 ). rules = VALUE #( ( fname = 'FIELD2' user_type = zcl_adata_validator=>c_type_email regex = 'gmail\.com$' regex_msg = 'Only gmail supported') ).
或者創建一個新類型和它的檢查類,並且把它的檢查類配置傳入constructor,
DATA: check_class_config TYPE zcl_adata_validator=>ty_check_config_t. check_class_config = VALUE #( ( type = zcl_adata_validator=>c_type_new class = 'ZCL_NEW_VALIDATOR' ) ). TRY. DATA(result) = NEW zcl_adata_validator( check_class_conifg = check_class_config )->validate( rules = rules data = cases ). CATCH zcx_adv_exception INTO DATA(ex). DATA(msg) = ex->get_text( ). ENDTRY.
注意:constructor中已經包含了默認的檢查類配置和檢查消息配置,它們是Hard coding,一旦傳入自定義配置,它們就會被覆蓋。
可以按需對方法進行重定義、或者傳入自己的配置。比如,從數據庫或其它來源讀取配置,這樣可以在不修改既有代碼的情況下擴展程序的功能。
DATA: check_class_config TYPE zcl_adata_validator=>ty_check_config_t. SELECT * FROM my_config_table INTO TABLE @check_class_config. TRY. DATA(result) = NEW zcl_adata_validator( check_class_conifg = check_class_config )->validate( rules = rules data = cases ). CATCH zcx_adv_exception INTO DATA(ex). DATA(msg) = ex->get_text( ). ENDTRY.
項目地址
https://github.com/hhelibeb/abap-data-validator
我提交了絕大部分代碼,此外還有2位貢獻者larshp和FreHu,他們幫助修正了代碼格式和readme方面的一些問題。
歡迎參與這個工具的開發!如果你發現程序有任何問題,也可以在github提交issue告知我。
Q & A
怎樣定義有效性?
有效性的定義十分重要,如果有效的定義是模糊的,那檢查也失去了意義。
對於ABAP Data Validator,如果一個檢查類型存在對應的ABAP數據類型(比如日期,時間戳等),那么“有效”是指:
- 值可以直接被賦值給相應類型的ABAP變量,不產生異常,也不產生無意義的值。
以時間戳為例,
- 2021-12-21 00:00:00 "無效
- 19000000235959 "無效
- 20200101235959 "有效
2021-12-21 00:00:00’\可能在某些情境下是合理的時間戳值,但是對於ABAP Data Validator而言,它是無效的,因為將它賦值給時間戳類型的變量會導致dump。而'19000000235959'雖然可以被賦值給ABAP時間戳變量,但因為沒有實際意義,它同樣是無效值。
這種類型檢查的邏輯主要由SAP提供的標准功能實現,時間戳的檢查實際上由正則檢查、標准函數DATE_CHECK_PLAUSIBILITY和TIME_CHECK_PLAUSIBILITY組成。
如果一個檢查類型沒有對應的ABAP數據類型,ABAP Data Validator的檢查邏輯收集自一些相對權威的來源,這些檢查邏輯通常會符合行業標准。來源包括,
- Wikipedia (IMEI)
- Mozilla Developer Network (Email)
- regex-weburl.js (URL)
- W3C Markup Validation Service (HTML)
ABAP Data Validator的檢查結果可靠嗎?
絕大部分檢查邏輯是成熟的,且它們都包含單元測試。你可以在單元測試中加上自己的用例來驗證它們的行為是否符合你的期望。
HTML檢查是個例外,它是唯一一個需要調用外部API(https://validator.w3.org/)的檢查,而且w3.org提供的API本身也只是實驗性的。請只把它的檢查結果當作參考。
目前,我已經在工作中部分地應用了ABAP Data Validator,看起來工作良好。
為什么是多個類,而不是一個類、多個方法?
- 減少了全局類數量,方便記憶/不易產生命名沖突。
- 方法定義更靈活,不需要接受接口ZIF_ADV_CHECK的限制。
- 不同類型的檢查之間的耦合度增加,部署單個方法的更新時,可能會導致更多的LOAD_ PROGRAM CLASS_ MISMATCH異常。
- 不同開發者的ABAP服務器可能環境不同,如果個別方法不可用,問題將波及整個類,導致其它檢查也不可用。
- 失去了接口接口ZIF_ADV_CHECK的約束后,動態編程會變得復雜,也難以保持檢查規則的簡單性。