在日常的开发过程中, 我们常常要处理不同来源的数据. 数据可能来自不可靠的外部系统, 不可靠的用户输入和甚至设计有误的数据库表, 因此, 对数据有效性进行验证是必要的工作.
开源工具 ABAP Data Validator https://github.com/hhelibeb/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 位贡献者 https://github.com/larshp 和 https://github.com/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) https://gist.github.com/dperini/729294
- W3C Markup Validation Service (HTML) https://validator.w3.org/
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 的约束后, 动态编程会变得复杂, 也难以保持检查规则的简单性.
总之, 经过一番权衡之后我选择了当前的设计, 尽管它也存在某些缺点, 如果你有更好的想法, 请告诉我.
来源: https://www.cnblogs.com/hhelibeb/p/12206648.html