ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ABAP开发新范式:用XCO Libraries把数据字典操作变成积木式拼装

ABAP开发新范式:用XCO Libraries把数据字典操作变成积木式拼装

入行头几年,我一度觉得 ABAP 最不缺的就是"能干活但特别绕"的 API。建一张自定义表,要在 SE11 里一屏一屏地敲;程序里想看一下某个数据元素的属性,得先记住 DDIF_DTEL_GET 那套出参顺序。直到有一次做数据迁移准备,要把几十张结构相似的表、上百个数据元素统一校验和补齐定义,我才正儿八经研究了 SAP 的 XCO Libraries,然后发现一个事实:ABAP 开发里那类反复出现的对象级操作,其实早就可以像搭积木一样拼出来。

XCO Libraries 是 SAP 提供的一套 ABAP 开发库,它把数据字典对象、类、接口、服务等开发对象统一建模,提供从读取、修改到生成的一整套 API。它的价值不是"又一个工具类",而是改变了与 ABAP 开发对象打交道的方式。这篇文章不打算把每个类过一遍文档,而是从设计哲学讲起,再用我实际验证过的几个场景展开,适合在 S/4HANA 或 ABAP Cloud 上做新开发、做迁移、写自动化工具的 ABAP 开发者参考。

1. 为什么 ABAP 一直写"胶水代码":开发对象操作的分散之痛

1.1 一处元数据,三套 API

我见过太多这样的代码:要查一个数据元素引用了哪个域、搜索帮助是什么,于是先 SELECT 到 DD04L 拿 DOMAIN,再 SELECT 到 DD03M 拿组件信息,遇到激活状态还要绕进 DD09L 一类的底层表。除开底层表,光功能模块就有 DDIF_DTEL_GET、DDIF_DOM_GET、DDIF_TABL_GET 等一堆,每个功能的导入导出参数风格都不一样。你要么为每个对象类型单独写一套读取逻辑,要么用一个巨大的 CASE 分支把参数封装起来。

这种写法最大的问题不是"能跑",而是"谁跑"的时候说不清楚。新同事接手时,看到的是散落在程序里的 SQL 直查、函数调用、运行时类型判断三段式代码,每一段单独看都对,合在一起完全不知道在表达什么业务意图。我管这类代码叫"胶水代码"——它不是业务,也不是平台能力,只是为了把两种 API 黏在一起而产生的技术债。

1.2 当"对象操作"变成高频需求,问题就绕不过去了

如果对象操作一年只做两三次,写点胶水代码也就忍了。但在迁移项目、接口适配、ABAP Cloud 新开发这类场景里,"读一个对象、改一个对象、建一个对象"是会高频出现的。举个例子,数据迁移前置检查里要校验上百个数据元素的激活状态和短文本完整性;财务项目里要按模板建几十张结构相同的自定义表;CI/CD 自动化里要根据配置文件生成一批 DDIC 对象。这些任务一旦高频化,散装 API 带来的成本就不是"忍一下"能解决的。

我印象最深的是一次迁移准备:我需要把 100 个数据元素的"是否激活、短文本是否存在、引用的域是否有效、输出长度是否符合预期"逐项检查并汇总成 Excel。按老办法写,光是读取每个数据元素的元数据就要十几个变量、几十行参数准备代码,还得处理 DD04T 的语言文本。写完后整个 routine 近两百行,真正的业务判断只占其中一小部分。正是类似这样的经历,让我开始认真对待 XCO 的读写模型。

2. 积木式设计的内核:对象模型、流式 API 和三条主入口

2.1 把 ABAP 开发对象当成积木块:XCO 的对象化思维

XCO 设计的第一个关键是"对象化"。在传统方式里,一个数据元素是 DD04L 里的一行记录、DDIF_DTEL_GET 的一堆出参、运行时类型描述符里的一个片段。而在 XCO 里,它就是xco_cp_ddic->data_element( 'ZEXAMPLE' )返回的一个对象。

别小看这一步转变。一旦对象存在,你就能在它身上做更多动作:问它的内容,问它的文本,问它引用的域,甚至直接修改它。对象之间还能互相引用、嵌套。你可以读取一个表对象的字段列表,再对每个字段对象继续读取它的数据元素对象;也可以把一个数据元素对象作为生成表时的一个"字段积木"塞进 put operation。

这种设计很像乐高:每一块积木有标准的凸点和凹槽,单独看不出名堂,但拼接起来能表达复杂的结构。XCO 把"ABAP 开发对象"这个域里的所有标准接口统一成同一套积木规格,开发者不需要再背一堆函数签名。

2.2 读、改、生成三条通道,以及它们的组合逻辑

XCO 的功能入口大致分三条通道。第一条是读通道,主要落在XCO_CP_*系列上,比如 DDIC 对象的内容读取、字段列表读取、文本读取。第二条是修改通道,针对已存在对象做属性更新,一般通过 put operation 完成。第三条是生成通道,对应XCO_GEN_*系列,从零创建域、数据元素、表、类、接口等对象。

三条通道共享同一个对象模型,这是一个很聪明的安排。你只需要掌握"用定位器拿到对象引用"这一个动作,后续无论是读、改还是生成,思路都是一样的:先定位对象,再描述你要做什么,最后执行。实际项目里还可以把它们组合起来——先读取一批现有对象的元数据做校验,再根据校验结果生成缺失的对象,整个链路用同一套风格写下来,比混用 SE11 手工操作、功能模块和 SQL 要顺畅得多。

2.3 fluent API 的自我表达:代码即设计意图

XCO 公开的接口大量采用流式(fluent)调法。比如读取一张表的所有字段,写出来大致是xco_cp_ddic->table( 'ZTABLE' )->fields->all->get( )。这个方法链读起来本身就是一句英文:从 DDIC 里取一张表,拿它的字段,取全部,拿回来。

这种表达方式最大的收益是"误读成本低"。老代码里SELECT domname FROM dd04l INTO lv_dom WHERE rollname = lv_name.这句话,新手要看半天才知道原来是在查域;而 fluent 链直接把意图写在了代码上。我自己的体会是,使用 XCO 后 Code Review 时讨论业务逻辑的时间明显变多了,解释 API 用法的时间变少了。

3. 环境检查与最小起步:确认版本、跑通一段 XCO 代码

3.1 版本与软件组件:先确认系统里到底有没有 XCO

XCO 不是一个可以从网上随便下载安装的第三方库,它是随 SAP 系统发布的软件组件。在 S/4HANA 2020 及更高版本中,XCO 作为标准组件预置;在 ABAP Cloud(也叫 Steampunk)环境里同样是标准库。如果你的系统还停留在 ECC 或者早期 Suite on HANA,很可能根本没有这个库。

最直接的检查办法:在 SE24 里输入XCO_CP_CORE_FACTORY或CL_XCO_CP_CORE_FACTORY,看类是否存在。如果还不放心,就写一行运行时检测:

TRY. cl_abap_classdescr=>describe_by_name( 'XCO_CP_CORE_FACTORY' ). CATCH cx_sy_class_not_found. " 系统未提供 XCO Libraries ENDTRY.

这一步值得花十秒钟做。我在网上看过不少抱怨"XCO 代码报方法不存在"的帖子,最后发现是系统版本太老。先确认环境,能省掉一大半弯路。

3.2 第一段 XCO 代码:读取一个数据元素的短文本

下面这段代码是我给团队新人准备的"hello world":读取一个数据元素的短文本并输出。

DATA(lv_name) = 'ZEXAMPLE_DTE'. DATA(lo_element) = xco_cp_ddic->data_element( lv_name ). DATA(ls_content) = lo_element->content( )->get( ). WRITE: / ls_content-short_text.

这段代码最有趣的地方在于,你完全不知道 XCO 内部是查了 DD04L 还是调了 DDIF,也不影响你使用。它把"读取数据元素内容"这件事封装得足够干净,这正是对象化带来的体验。

3.3 看懂这段代码的"拼装"结构

如果把这行链拆开,你会发现它由四层组成:xco_cp_ddic是访问器的根,所有 DDIC 相关的访问都从这里出发;data_element( lv_name )是定位器,根据名称锁定具体对象;content( )是视图选择,表示我们想看的不是别的,而是内容;get( )是最终的执行动作,把内容拉回 ABAP 结构。

这种层层剥开的方式就是搭积木。每一层方法返回的都是下一块积木,你可以选择接下去,也可以换一块。比如你拿到表对象后,可以不看 content,转去看 fields;拿到数据元素后,可以不看内容,转去看文本。对新手来说,先掌握"定位器->视图->get"这个三层骨架,XCO 的一半代码你都能看懂了。

4. 实战一:把散装的 DDIC 查询收编成一条 XCO 调用链

4.1 场景还原:数据迁移前的元数据体检

我遇到的真实场景是这样的:数据迁移之前,要检查 100 个数据元素的状态。检查内容包括:对象是否存在、是否已激活、短文本有没有漏配、引用的域是否有效、输出长度是不是我预期的长度。

如果用老方式,我会先查 DD04L 拿到技术属性,再查 DD04T 拿语言文本,遇到激活状态还要想办法和 DDIF 的返回做对齐。最痛苦的是"对象不存在"这种边界情况,底层表查不到记录,你还得判断到底是名字写错了还是真没建,最后写出来的代码分支多到没法看。

4.2 旧方案示例:不是不能写,是不想再看

传统的 DDIF_DTEL_GET 调用往往长这样:传入对象名和语言,导出状态、内容、参数文本等一堆结构。你没看错,光出参就有好几个,而且不同 SAP 版本里出参字段还有细微差别。至于直接查底层表,DD04L 的字段名、和 DD04T 的 join 关系、语言条件的处理,每一项都是隐藏细节。

这还不是最糟的。如果检查对象从数据元素扩展到表和域,你要么继续堆 DDIF_DOM_GET、DDIF_TABL_GET,要么把 SQL 条件再写一遍。无论哪条路,输出到 Excel 的那部分逻辑都只占很小比例,大部分代码都消耗在"把元数据取出来"上。

4.3 XCO 收编后的写法:一条链,统一动作

用 XCO 重写后,核心循环变得非常统一:

DATA(lt_names) = VALUE string_table( ( 'ZDTE001' ) ( 'ZDTE002' ) ( 'ZDTE003' ) ). DATA(lt_report) TYPE STANDARD TABLE OF zreport_line. LOOP AT lt_names INTO DATA(lv_name). DATA(lo_element) = xco_cp_ddic->data_element( lv_name ). DATA(ls_content) = lo_element->content( )->get( ). APPEND VALUE #( name = lv_name short_text = ls_content-short_text data_type = ls_content-data_type length = ls_content-length ) TO lt_report. ENDLOOP.

代码量比老写法缩了一大截,更重要的是循环体里的每一步都在表达业务意图:拿对象、读内容、收结果。XCO 内部对"对象不存在""未激活"这些情况的处理也统一了,我拿到 content 结构后只需要检查字段是否为空,不需要再单独判断一堆底层表返回值。

4.4 语言、批量与性能:实战里绕不开的细节

先说语言文本。XCO 读取短文本时默认跟随当前会话语言,如果用户是英文环境而对象只维护了中文文本,你看到的可能就是空值。要根据需求显式指定语言,不同版本里控制语言的地方不太一样,落地时一定要看方法签名,别想当然地以为默认会返回所有语言。

再说批量与性能。循环里逐个调用 fluent 链,对象少的时候非常舒服,对象上万的时候就要谨慎了。每次->data_element( name )->content( )->get( )都会经过多层对象创建,开销比直接 SELECT 要高不少。我的做法是:先按业务条件用 SQL 缩小候选集,再用 XCO 对候选集逐个补全元数据。混合使用不丢人,XCO 是积木,不是让人彻底扔掉 SQL 的银弹。

5. 实战二:用 XCO_GEN 批量生成数据元素与表结构

5.1 场景还原:财务项目里几十张"长得像"的自定义表

财务项目里经常出现这种需求:基于一套公共模板创建几十张自定义表,字段构成基本一致,都是 MANDT 加单据号加金额加期间再加两三个项目特有字段。手工在 SE11 里复制表最快,但问题在后续:模板表加一个公共字段,你得回到每张表里重复操作;模板改类型长度,还是重来一遍。这种"重复性基建",用代码生成是最合适的。

XCO_GEN 的价值就在这。你把表的定义写成代码,表的结构改动由代码统一驱动,几十张表一次循环搞定。这比我早年用 DDIF_TABL_PUT 时维护一堆参数结构要舒服太多。

5.2 用 XCO_GEN 生成一个数据元素

先生成数据元素,因为后续建表要引用它。示意代码如下:

DATA(lo_put) = xco_cp_generation->for_dtel->add_object( 'ZGEN_DTE' ). lo_put->set_short_text( 'Generated data element' ). lo_put->set_data_type( 'CHAR' ). lo_put->set_length( 10 ). DATA(ls_gen_result) = lo_put->execute( ).

注意,生成动作里我没有去指定域。实际项目如果这个数据元素需要关联域,就在 put operation 上加对应方法。XCO 的生成器允许你把"用什么数据类型、多长、短文本写什么"这些属性一块一块拼上去,最后调用execute( )一次性提交。

5.3 生成一张表,并把字段拼上去

接下来生成表,把刚才的数据元素作为字段引用进来:

DATA(lo_put_tab) = xco_cp_generation->for_tabl->add_object( 'ZGEN_TAB' ). lo_put_tab->set_short_text( 'Generated table' ). lo_put_tab->set_delivery_class( 'A' ). " 应用表,是否可维护按项目约定 lo_put_tab->add_field( 'MANDT' )->set_data_element( 'MANDT' ). lo_put_tab->add_field( 'ZDOCNO' )->set_data_element( 'ZGEN_DTE' ). DATA(ls_tab_result) = lo_put_tab->execute( ).

这个调用链特别像在搭积木:add_field( 'MANDT' )得到一块字段积木,再set_data_element( 'MANDT' )给这块积木指定引用的数据元素,拼接完成后整块插到表的定义上。

要说明一点:set_delivery_class、add_field、set_data_element这些方法名在不同版本里有细微出入,我写的是可读性优先的示意,落到你系统里以 ADT 自动补全和 SE24 看到的方法签名为准。这个提醒适用于本文所有 XCO 代码。

5.4 execute 之后发生了什么:传输请求、激活与顺序

execute( )不是简单地在内存里写点数据。它对 SAP 开发环境来说是一个真正的"建对象"动作,会创建对象目录条目、写 DDIC 仓库并触发激活。在传统 S/4 系统里,它还涉及传输请求:XCO 会尝试把一个传输请求分配给新增对象,或者把对象挂到已有请求上。这要求执行程序的用户至少有创建传输请求的权限,否则会在 execute 阶段直接报错。

第二个要注意的是对象之间的依赖顺序:先建域,再建数据元素,最后建表。如果表字段引用了一个尚未生成的数据元素,而数据元素又引用了一个尚未生成的域,生成会失败。我在项目里会把整个生成过程拆成三段循环,每段执行完检查返回结果,再进入下一段。

6. 实战三:JSON、字符串这些"小积木块"的使用边界

6.1 XCO 不止 DDIC:通用工具块的定位

很多同事以为 XCO 就是操作数据字典的,这是一个误解。XCO_CP 覆盖的范围比 DDIC 大得多,还包括 JSON、XML、字符串、日期时间等通用能力。它把这些工具也做成fluent 风格,让你在字符串处理、格式转换这些高频操作里获得和 DDIC 操作一致的体验。

在我看来,这些通用工具就是积木盒里的小块。你可以单独拿一块来处理字符串,也可以把字符串处理后得到的结果直接喂给 DDIC 读取链,拼成更复杂的动作。这种"工具之间的拼接能力",才是积木比喻真正到位的地方。

6.2 字符串与数字校验:告别 CO 操作符的可读性盆地

ABAP 老开发都知道,判断一个字符串是否全是数字,经典写法是IF lv_str CO '0123456789'.。这行代码能跑,但读起来费劲:CO 操作符需要你记得"只包含表内字符"这个语义,新同事还得现查。如果还要进一步处理空字符串、前导空格,代码就更绕了。

XCO 对这类工具操作做了统一封装,调用链读起来接近自然语言。以数字判断为例,思路是"取字符串工具,判断是否为数字",而不是"把一个字符串逐字符拿去和数字集比较"。具体方法名在不同版本有差异,但调用风格和 DDIC 那套是一致的:定位工具、描述动作、拿结果。字符串大小写转换、去空格、截断也是一样的体验。

6.3 JSON 与编码转换:新老代码之间的"积木接口"

我在做接口类程序时经常要处理 JSON 和编码转换。老方式里,JSON 序列化用 /ui2/cl_json 或 cl_tbx_json,编码转换用 cl_abap_conv_out_ce 之类,参数风格各自为政。XCO 为这类操作提供了一个更统一的入口:ABAP 数据转 JSON 字符串,或者 JSON 解析回 ABAP 结构,调用链同样干净。

比如 ABAP 转 JSON,大致思路是"从 JSON 工具出发,把 ABAP 内容转换出来",再调转换器拿到字符串。代码读起来比翻 /ui2/cl_json 的参数文档要直观。我记得有同事用这套方式给 F110 BADI 增强里的日志存 JSON,比手工拼字符串体面多了。

6.4 周边组件的版本注意点

必须坦白说一句:XCO 周边组件在不同版本里命名和细节变化比较快,我在上面给的 JSON、字符串代码是调用链示意,目标让你理解拼接方式,而不是逐字照抄。真正落地时,请以你系统里类浏览器显示的方法签名为准。

这条经验不是偷懒,是我自己踩出来的。我在两个 S/4 版本上见过同一功能的不同方法名,直接抄代码过去编译都过不了。XCO 的价值在于一致的 API 风格,具体签名要习惯用 ADT 的自动补全去确认。

7. 上线前后踩过的坑:版本、传输请求、性能与调试

7.1 坑一:用在了没有 XCO 的老系统上

这个坑最基础也最致命。我在一个还在用 ECC 核心组件的客户现场试过 XCO,程序一跑就是 exception,类根本不存在。查了半天不是代码问题,是系统里压根没有这个软件组件。后来我去每个项目的第一天就先跑一遍版本检测,把 XCO 是否存在作为环境检查的固定项。

如果你的项目暂时无法升级,但又想享受对象化操作,退一步的方案是封装一个薄工具类:XCO 存在时走 XCO,不存在时降级到传统 API。这种方式让我在混合环境里也敢在公共代码里用 XCO,而不是被单系统版本绑死。

7.2 坑二:循环里逐对象读取,性能被 fluent 链反噬

XCO 写起来很爽,但爽过头会付出性能代价。我在一个批处理里循环一万次读取数据元素内容,运行时间比预期长了不少。原因是每次调用链都会创建多层对象,累积下来开销不小。这不是 XCO 的 bug,而是任何高抽象框架都有的成本。

应对方法分两种:一是先缩小候选集,再用 XCO 逐个补全;二是如果业务只关心极少的字段,比如只要一个短文本,直接 SQL 查 DD04T 反而更朴素。我的底线是:单次循环少于几百个对象,放心用 XCO;上千甚至上万,先想想有没有更廉价的过滤路径。

7.3 坑三:生成对象时的传输请求与权限

XCO_GEN 的 execute 在传统 S/4 环境里会涉及传输请求。我第一次跑生成程序时,因为测试用户没有创建请求的权限,execute 直接报错。这不是代码逻辑问题,是权限没准备好。在项目里,跑 XCO_GEN 的用户通常要具备 S_CTS_ADMIN 或至少能创建请求的权限。

另外,如果你想把生成的对象放到指定请求上,需要提前在 put operation 里设置请求上下文。这个 API 的精确位置我不记忆了,但思路是明确的:生成动作更像一个正式的开发操作,而不是内存演练。跑批量生成前,先在测试系统单对象验证,再全量执行。

7.4 调试技巧:顺着 CL_XCO_ 前缀追进源码

XCO 的实现类名都以 CL_XCO_ 开头。遇到诡异行为,最直接的办法是断点停在自己的调用链上,然后 F5 追进实现类,看它内部到底调了什么。这个过程对我来说是很好的学习途径:XCO 对底层 API 做了很多版本分支处理,读它的实现,能顺带理解传统 API 的边界。

有一次我生成的表字段顺序不对,就是追进实现类才发现,XCO 底层读取字段列表时会按组件位置排序,而我的代码在 add_field 时没按预期顺序插入。这个结论在文档里不好找,但源码里一目了然。

8. 什么时候该用、什么时候别硬上:我给 XCO 划定适用范围

8.1 会优先考虑 XCO 的场景

新项目尤其是 S/4HANA 2020 以上和 ABAP Cloud 项目,对象级操作我会默认 XCO。迁移脚本和自动化工具里,只要涉及批量读取、校验、生成开发对象,XCO 能把代码量降一个量级。对团队来说,统一采用 XCO 风格还有额外好处:代码的可读性上限更高,新人上手成本反而更低。

8.2 不会硬上 XCO 的场景

存量老系统且没有近期升级计划,我不会为了风格优雅去引入一个不存在的库。一次性的简单读取,比如只是某个报表里顺手查一个短文本,直接 SELECT 一行的成本远低于搭一条 fluent 链。极端性能敏感的批量底层查询,我更倾向于 SQL 直查加索引,而不是高抽象封装。XCO 是积木,是工具,不是信仰。

8.3 一条实际的落地建议

给团队引入 XCO,我的建议是从三个最小场景开始:读取数据元素内容、读取表字段、生成一张表。三个场景分别覆盖读、写、生成的典型路径,练完基本就能掌握"定位器->视图->执行"的骨架。

我自己也是这么做的:把三个场景封装成一个本地工具类,先在工具类里写 XCO 调用,再让团队在这个类的基础上做扩展。跑通第一件事之后,剩下的 API 大概率会在自动补全里自然冒出来。

老实说,我第一次看到 XCO 时并不怎么惊艳,觉得不过是一层更漂亮的封装。直到迁移项目里,把几万行雷同的建表、补字段、校验元数据代码缩到十分之一,我才意识到这层封装的价值不在少打几个字,而是把脑力从 API 细节里解放出来,交给业务和架构。如果你正在新系统上做 ABAP 开发,挑一个小场景试一下,先别求全,跑通一块积木,后面的路自然就有了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进