ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ABAP新语法实战:内联声明、内表表达式与BAPI重构技巧

ABAP新语法实战:内联声明、内表表达式与BAPI重构技巧 1. 为什么新语法值得你重新审视开发习惯1.1 老语法到底让你多写了多少代码先说个最近的真实场景。项目里有个F110付款程序增强要看一段客户主数据校验逻辑我翻开老代码发现按ABAP传统写法一个简单的“取数-筛选-拼接报错串”写了一百多行。声明区里DATA、TYPE、FIELD-SYMBOL占了大半中间是一层套一层的LOOP内部再嵌套IF判断最后用CONCATENATE一段一段地把字符串拼起来。说实话这样的代码不是不能跑但每次业务方提一个小的打印格式调整我都需要从声明区开始往下捋很久生怕改漏一个中间变量。这不是个例。做ABAP开发超过五年的朋友应该都有体会老代码式的“声明三件套”DATA声明、赋值、再循环处理把程序员大量的精力消耗在“过程控制”上而不是业务本身。你明明只是想把一个内表按条件查询一下却必须写READ TABLE、找SY-SUBRC、再IF判断你明明只是想把几个字段输出成一行字符串却需要用WRITE TO加CONCATENATE来回折腾。新语法的出现概念上像把“如何一步步做”变成了“我要什么结果”代码从命令式变成声明式表面上只是写法不同实际上整个编码思维的出发点都变了。1.2 新语法的底层思路为“结果”服务而不是为“过程”服务ABAP新语法从NetWeaver 7.40开始大面积引入之后7.50、7.51又在几个方向上做了增强。很多人把它当作“语法糖”其实不完全准确。比如内表表达式的行内READ TABLE编译后不再需要你手工管理SY-SUBRC的读取顺序字符串模板在编译器层面就直接处理了字面量与变量的拼装内联声明则把作用域和生命周期交给了运行时变量随用随声明不会在程序开头堆一排用不到的DATA。这些特性背后是内核层面的优化不只是一个短写法的壳子。我用一个生活化的例子讲给你听。老语法像下厨房前先按菜单把葱姜蒜全部切好摆盘然后一道菜一道菜地炒中间哪个配料忘了切还得停下来补一刀新语法更像半成品净菜你要炒一盘青椒肉丝直接把对应料包倒进锅里就行。前者在流程繁琐时容易把“切葱”和“炒菜”搞混后者的核心是“让你专注于这盘菜的口味”。对于ABAP开发来说所谓口味就是业务校验逻辑、数据处理结果而新语法能让这些逻辑以更短的代码、更清晰的结构呈现出来。不过新语法不是银弹。我见过团队里有人为了用新语法而用新语法把原本一行能读懂的ASSIGN写成复杂的FOR循环推导式结果维护成本反而上去了。这说明我们得搞清楚每类语法的适用范围。接下来我从日常开发里最常用的三类基础能力讲起再延伸到BAPI调用、增强开发、ALV交互这些高频场景。2. 日常开发最常用到的三类新语法能力2.1 内联声明与内表表达式给内表处理“减脂”内联声明是大部分人接触新语法的第一站写法就是在赋值语句左侧直接写DATA(...)程序会自动推断右侧表达式的类型。举个例子从MARA表里按物料号读取一行SELECT SINGLE * FROM mara INTO DATA(ls_mara) WHERE matnr lv_matnr.这里我顺手用了一下SQL里的new syntaxINTO后加DATASELECT的条件参数也用LV_MATNR这是ABAP 7.40后SQL内嵌表达式的标准写法。要点是SELECT后面的字段和条件里的宿主变量前都要带符号否则有些版本会直接报语法错。内表表达式的使用频率更高。比如你要从内表里找一条满足条件的记录老写法要声明工作区、READ TABLE、判断SY-SUBRC、再取字段新语法可以这样DATA(ls_target) lt_item[ matnr 1000001 ]. 结果内联声明如果担心内表里没有这条记录会直接抛出CX_SY_ITAB_LINE_NOT_FOUND异常你可以先判断IF line_exists( lt_item[ matnr 1000001 ] ). 存在再读取安全又直观 ENDIF.这是我最想推荐给从老语法转过来的朋友的一个动作把“先读表再判断SY-SUBRC再取值”三步并成一步。实际项目里这种用法在清洗数据、主数据校验场景能少写很多代码。构造内表时VALUE #( ... )也很好用比如要给字段目录或者测试数据DATA(lt_test) VALUE ty_t_item( ( matnr A001 qty 10 ) ( matnr A002 qty 20 ) ).括号列表里的每一行就是一条记录不再需要一行一行APPEND。需要注意#号表示“按目标类型推断”在明确需要某个类型时会自动匹配一般直接写DATA(...)接收即可。2.2 字符串模板与“判断字符串是不是数字”的正确姿势字符串模板是7.40另一个大杀器用竖线和花括号把静态文本和变量拼在一起。以前拼报错信息可能要写三段CONCATENATE现在一行搞定DATA(lv_msg) |物料 { ls_mara-matnr } { ls_mara-maktx } 的数量校验不通过差异为 { lv_diff }|.这里有个细节容易踩坑字符串模板里如果变量为空拼出来就是空串不像老语法里有时会把空格带进去。也别在模板里写复杂的方法调用可读性会变差我一般只在模板里放简单变量和已算好的字段。再说热搜里经常有人问的“ABAP怎么判断字符串是否是数字”。老方案一般是用TRANSLATE把数字字符替换成空格再判断或者用CO仅包含运算符。新语法下我推荐这种写法DATA(lv_is_num) xsdbool( lv_input CO 0123456789 AND lv_input IS NOT INITIAL ).CO运算符判断左边字符串的每一个字符是否都出现在右边集合里因此只适用于纯数字字符组成的字符串遇到负号、小数点、前导空格都会判错。如果业务上要判断“数字格式”比如金额我更倾向于直接尝试转换TRY. DATA(lv_qty) CONVERT #( lv_input ). CATCH cx_root. 转不了说明不是合法数字 ENDTRY.几十个大项目下来我的建议是只要允许用户输入数字和标点就老老实实走转换方案如果场景限定为纯数字编号用CO运算符更简洁且没有异常开销。2.3 UTF-8转ANSI编码新语法下的“小事”其实有讲究ABAP系统内字符处理默认按系统代码页走项目里常遇到外部接口传入UTF-8字符串需要转成ANSI再写文件的情况。老做法要手动处理字符串拆分再转码非常难受。新语法配合类方法可以实现短小但正确的转换DATA(lv_xstring) cl_abap_codepageconvert_string_to_xstring( iv_source lv_utf8_str iv_encoding UTF-8 ). DATA(lv_ansi_str) cl_abap_codepageconvert_xstring_to_string( iv_xstring lv_xstring iv_encoding ANSI 实际取决于目标代码页比如1252 ).这里最重要的心得是ANSI不是一个固定标准Windows下用1252有的Unix环境用ISO-8859-1转码前一定要和对接方确认目标代码页。否则转出来最后可能中文变问号排查起来非常费劲。另外转完写成文件时最好用OPEN DATASET的ENCODING参数显式指定代码页不要默认系统代码页防止测试环境和生产环境系统代码页不同导致行为不一致。3. 用新语法重构BAPI调用的完整过程3.1 销售订单创建BAPI从参数准备的繁琐中解放出来BAPI_SALESORDER_CREATEFROMDAT2这类SD创建类BAPI老写法最烦的地方是参数结构多且互相牵连。正常流程要填充订单抬头、行项目、计划行、合作伙伴、返回表如果按传统声明和MOVE方式写光参数准备就要将近一百行。用新语法可以这样收敛DATA(ls_header) VALUE bapi_salesorder_header_in( doc_type OR sold_to lv_kunnr sales_org 1000 distr_chan 10 division 10 ). DATA(ls_headerx) VALUE bapi_salesorder_header_inx( doc_type abap_true sold_to abap_true sales_org abap_true distr_chan abap_true division abap_true ). DATA(lt_items) VALUE bapi_salesorder_item_in_tab( ( it_number 000010 material lv_matnr target_qty lv_qty ) ( it_number 000020 material lv_matnr2 target_qty lv_qty2 ) ).VALUE构造式把每一条行项目当成一个记录块代码量和语义清晰度都好很多。然后调用CALL FUNCTION BAPI_SALESORDER_CREATEFROMDAT2 EXPORTING order_header_in ls_header order_header_inx ls_headerx TABLES return DATA(lt_return) order_items_in lt_items order_items_inx lt_itemsx.这里我在TABLES参数里直接用了DATALT_RETURN这种内联声明语法上没问题省掉了前面单独一行声明。不过实战里我建议少用这种“一行声明”风格——代码评审时别人一眼扫过去容易忽略这个内表的后续用途还是拆开写更清晰。调用BAPI后立刻要判断返回消息。老写法要LOOP RETURN表再查TYPE字段新语法用line_exists一次判断IF line_exists( lt_return[ type E ] ). 有错误回滚 ROLLBACK WORK. ELSE. COMMIT WORK. ENDIF.如果要把错误消息拼成一段文本给前端展示字符串模板配合REDUCE最合适DATA(lv_err) REDUCE string( INIT msg TYPE string FOR ls_ret IN lt_return WHERE ( type E OR type A ) NEXT msg msg |\n{ ls_ret-message }| ).这种写法把“遍历、判断、拼接”浓缩成一段表达式尤其适用于BAPI返回表中要提取多个错误文本的场景。3.2 物料价格修改BAPI返回结果别只用SY-SUBRC做判断物料价格修改的BAPI常见的是BAPI_MATVAL_PRICE_CHANGE。这种涉及价格更新的场景业务上最怕“调用成功但返回警告”。因为价格修改往往分单据已过账和未过账两种情况同一BAPI在不同业务状态下返回结果差异很大。我处理这类BAPI时从不只看SY-SUBRC而是把RETURN表分类统计DATA(lv_e_count) REDUCE i( INIT cnt 0 FOR ls_return IN lt_return WHERE ( type E OR type A ) NEXT cnt cnt 1 ). DATA(lv_w_count) REDUCE i( INIT cnt 0 FOR ls_return IN lt_return WHERE ( type W ) NEXT cnt cnt 1 ).把REDUCE放进两行分类数量一目了然。价格BAPI还有一个常见的坑调用时物料号、工厂、价格类型必须全部精确匹配否则就算返回空结果也可能什么都没改。我在项目里用新语法处理时会把输入结构的字段先打印成字符串模板留日志比如|物料 { matnr } 工厂 { werks } 价格类型 { price_type }|。BAPI成功前先确认入参是这类“看似成了、实际没改”问题最有效的防线。3.3 工艺路线读取类BAPI封装一个通用结果判断器热搜词里出现的CP_BD_READ_ROUTING这类工艺路线读取BAPI属于主数据读取类。这类BAPI的特点是返回表结构类型多而且经常只返回部分工序。如果业务上需要“读取工艺路线再匹配某个工序号”传统写法要先定义一堆内表再循环匹配。用新语法可以这样组织CALL FUNCTION CP_BD_READ_ROUTING EXPORTING matnr lv_matnr werks lv_werks rout_versn 0001 TABLES routing DATA(lt_rt) operation DATA(lt_op).读取后直接在内表表达式中查找目标工序IF line_exists( lt_op[ vornr lv_opr ] ). DATA(ls_op) lt_op[ vornr lv_opr ]. 再取工序文本等 ENDIF.对这种高频出现的“读取BAPI 校验结果”组合我更推荐封装一个公共方法。签名可以设计成传入RETURN表和接受的消息类型列表输出整理后的文本METHODS check_bapi_result IMPORTING it_return TYPE bapiret2_tab iv_check_warning TYPE abap_bool DEFAULT abap_true RETURNING VALUE(rv_ok) TYPE abap_bool.方法体内用REDUCE统计错误和警告再返回综合结果。这个封装很薄但能让后续调用代码变成一行IF减少重复劳动。4. 新语法在增强开发中的落地套路4.1 F110付款运行BADI增强从上百行到五十行的改造思路F110是自动付款程序围绕它有大量增强点比如付款建议生成、付款运行前、过账前校验等。很多老增强都是挂在标准程序某个FORM后面做隐式增强代码风格往往延续了标准程序的老式写法。最常见的就是对一个付款建议内表做筛选老代码大概长这样声明三个内表、一个工作区然后循环、SELECT、内表追加、再循环。我用新语法改造过类似逻辑效果非常直接。例如在F110相关的BADI方法中业务要求只对特定供应商范围发付款建议老写法一般是循环LS_SELECTED_ITEMS逐条判断KUNNR范围满足条件再添加到结果表。用FILTER加FOR可以直接把筛选结果一步到位DATA(lt_filtered) VALUE ty_t_items( FOR ls_item IN lt_items WHERE ( kunnr lv_kunnr_low AND kunnr lv_kunnr_high ) ( ls_item ) ).这一步就把“循环-判断-追加”三件事合并了。接下来要给每个项目拼一个备注文本字符串模板又能派上用场LOOP AT lt_filtered INTO DATA(ls_f). ls_f-remark |付款建议项目 { ls_f-vblnr } 供应商 { ls_f-kunnr }|. MODIFY lt_filtered FROM ls_f TRANSPORTING remark. ENDLOOP.注意LOOP后的INTO DATALS_F是数行内联变量不需要再单独声明工作区。改完后的增强代码函数行数至少砍一半且每一行的意图都更接近业务描述。4.2 ME55审批增强校验用COND与REDUCE组织校验清单ME55涉及采购订单的批量审批常见的增强校验包括审批人权限范围、特定采购组的订单锁定、金额上限控制等。老写法做“多条校验汇总报错”时往往是一堆IF嵌套每个IF里改一个全局错误标记最后再根据标记决定是否失败。这个模式在新语法里可以收敛成“条件表达式汇总”。比如要判断“哪些行项目金额超预算”可以用REDUCE一次性统计超限数量DATA(lv_over) REDUCE i( INIT cnt 0 FOR ls_item IN lt_item NEXT cnt cnt COND #( WHEN ls_item-netwr lv_limit THEN 1 ELSE 0 ) ).如果还要针对不同场景生成不同错误提示COND表达式比IF连写更紧凑DATA(lv_check_result) COND string( WHEN lv_over 0 THEN |超限项目数量 { lv_over }请检查后再审批| WHEN lv_over 0 AND lv_warn_line abap_true THEN |存在警告行建议与申请者确认| ELSE space ).审批增强里关键的一点是不要因为用了新语法就丢掉“可追溯性”。我一般会在增强代码中把每个校验步骤生成一个内部校验结果内表最后统一用新语法汇总。业务人员问起来时我能直接列清楚哪条规则生效了比读一堆IF条件舒服得多。4.3 资产主数据与生产订单结算规则增强的校验模板资产主数据屏幕增强AS01/AS02通常采用“CI_”开头的客户包含结构或子屏幕实现。增量字段录入后需要在保存前校验。这类增强逻辑代码量不大但容易写散。新语法可以用来组织校验过程的中间结果。比如新增了“资产序列号”字段要求资产类别为“1000”时必须填写。老写法在增强代码里要声明局部变量、读主数据、再IF判断。新写法可以这样DATA(lv_filled) xsdbool( gs_ci_asset-serialno IS NOT INITIAL ). IF gs_ci_asset-anlkl 1000 AND lv_filled abap_false. MESSAGE e001(zz) WITH |资产序列号不能为空|. ENDIF.这里重点不是炫技而是让校验条件本身一眼可见第一行把“是否已填写”变成一个布尔值第二行直接基于业务条件判断避免在IF里写一长串AND条件。生产订单结算规则增强核心校验通常是“结算比例之和是否等于100%”“是否每个结算接收方都有效”。传统写法要LOOP两次一次算总和一次查无效对象。新语法的GROUP BY写法适合做分组统计但计算总和更直接的方式是用REDUCEDATA(lv_total) REDUCE p( INIT sum 0 FOR ls_rule IN lt_rule NEXT sum sum ls_rule-prcnt ).再配合检验有效性DATA(lv_invalid_lines) xsdbool( line_exists( lt_rule[ objnr space ] ) ). 存在结算对象为空的行把这些合成一个校验块逻辑紧凑且后续改动时只需要在某一行里调整。5. ALV交互增强从旧式回调到新语法简化5.1 构造字段目录一条一条APPEND的时代结束了凡是用过REUSE_ALV_GRID_DISPLAY的人基本都写过经典式的字段目录构造声明LVC_T_FCAT内表一条一条APPEND每条后面跟着两三个字段设置。十个字段就要三四十行代码。新语法用VALUE构造器可以把这段写得非常紧凑DATA(lt_fcat) VALUE lvc_t_fcat( ( fieldname MATNR ref_table MARA ref_field MATNR coltext 物料 ) ( fieldname MTART ref_table MARA ref_field MTART coltext 类型 ) ( fieldname WERKS ref_table MARA ref_field WERKS coltext 工厂 ) ).每一个括号就是一条字段目录记录字段名、参照表、标题可以一行一个。再配合SORT给字段排序完整度完全不输老代码。如果你的ALV带有编辑功能还可以用相同的VALUE方式构造LVC_T_LAYO布局结构DATA(ls_layout) VALUE lvc_s_layo( zebra abap_true cwidth_opt abap_true sel_mode A ).从可维护性角度来看这种把“字段目录定义”集中到一块的写法比分散在程序各处的APPEND更容易被接手的人理解。字段增删时也只需改动一行记录。5.2 F4帮助与ALV事件里的新语法写法REUSE_ALV_GRID_DISPLAY本身使用回调函数F4增强通常是在字段目录里设置F4AVAILABL后用I_CALLBACK_USER_COMMAND接收用户命令。旧式写法在FORM中要声明一堆P_I_UCOMM等参数再判断当前事件。新语法改造后的处理逻辑可以这样收敛FORM frm_user_command USING p_ucomm TYPE sy-ucomm p_fieldname TYPE lvc_fname. CASE p_ucomm. WHEN FCAT_MATNR. 触发F4先取当前光标行 PERFORM handle_f4_matnr. ENDCASE. ENDFORM.进入F4处理方法后需要读取光标所在行、调用帮助、更新单元格。这里可以体现内联变量和异常处理的组合优势FORM handle_f4_matnr. DATA(ls_cell) VALUE lvc_s_row( ). 读取当前行再调用搜索帮助返回值后更新内表 TRY. DATA(ls_row) gt_outtab[ ls_cell-row_id ]. CATCH cx_sy_itab_line_not_found. RETURN. ENDTRY. ENDFORM.需要注意的是REUSE_ALV_GRID_DISPLAY的传统回调方式里程序名和目标内表都必须提前正确传递否则F4事件根本不会触发。项目里常见问题是I_CALLBACK_USER_COMMAND指定的FORM名写错一个字母ALV不报错但事件也不响应。这时候用新语法反而不如老语法容易排查——因为内联声明让调用环境更黑盒了所以我在这种场景反而建议在增强FORM开头先用老式参数声明保持对ALV回调机制的直观映射。新语法不是哪里都能取代旧写法关键看你是否理解事件运行时怎么流转。6. 新语法落地时的版本与性能排查6.1 版本兼容性先查清楚别让代码上线前出丑新语法虽然好但最大的前提是目标系统版本支持。ABAP 7.40引入了一大波7.50又增加了GROUP BY相关能力7.51继续补充了一些表达式变体。如果客户系统还在ECC 6.0 EHP5左右的旧版本很多新语法根本过不了语法检查。我建议在项目开始时就查清楚系统版本和Support Package水平。可以通过查看SAP_BASIS组件版本确定。不同版本支持的主要新语法特性差异如下表所示特性类型引入版本典型示例内联声明与行内读取7.40DATA(...)、READ TABLE INTO DATA(...)内表表达式中括号查找7.40lt_itab[ key value ]VALUE构造器与字符串模板7.40VALUE #((...))、|{ var }|COND与SWITCH条件表达式7.40COND #( WHEN ... THEN ... )FILTER与FOR循环式内表构造7.40FILTER、FOR ls IN lt WHERE (...)(...)REDUCE聚合表达式7.40REDUCE #( INIT ... FOR ... NEXT ... )GROUP BY分组处理7.50LOOP AT ... INTO ... GROUP BY ...这里有个容易混淆的地方FOR表达式在7.40就能用但GROUP BY的完整实现是7.50以后的事。团队里如果有人在一台7.31系统上开发再用7.52语法做代码评审就会出笑话。我的习惯是在每个代码文件头部都标注一个简短的“语法版本要求”注释哪怕只有一行也能避免上线前的突兀报错。6.2 性能差异与调试陷阱新语法不是万能钥匙新语法代码量少不代表性能自动变好。我实测过一些场景结论是行内READ TABLE和内表表达式查找在底层仍然走相同的表索引逻辑性能与老READ TABLE基本一致。FILTER表达式在部分场景下会生成额外的临时内存大表循环时未必比传统LOOP条件追加快。REDUCE看着优雅但如果循环体内还做数据库查询性能瓶颈依然存在。调试时还有一个老开发者容易忽视的坑内联声明变量的作用域。在LOOP AT ... INTO DATA(LS_WA)中LS_WA每轮循环都会被重新创建如果你在一个CASE分支里改了这个变量回头在另一个分支再取它值未必是上一个循环的结果。类似的问题在“调用函数时直接写DATA(LT_RETURN)”的场景也容易让人迷路。所以我对团队的建议是方法级、功能级代码尽量用明确声明的命名变量只有在一个小函数里的局部内表才适合随手内联。6.3 迁移过程中的习惯调整比语法本身更重要从老语法转到新语法我踩过几次坑后给自己定了几个规矩新开发功能一律优先采用7.40之后的标准写法老功能的修改只在本次需求影响的代码段内做新语法重构不顺手把无关代码全面翻新每次代码评审看语法版本兼容性同时要求作者解释为什么某个场景用这个表达式而不是另一个。团队里刚开始推行时难免有人把FILTER写得非常复杂也有人把对象方法链接到一眼认不出对象是谁。我在项目里组织过一次“新语法重构分享”找了三段真实的旧代码让大家用新语法改写并解释思路。效果不错大家很快意识到新语法最大的价值是让代码里的业务逻辑浮出水面而不是比拼谁写的表达式更短。最终代码评审的标准也回到了两条业务可读性优先性能差异可控。语法新旧只是手段不是目的。我个人现在的习惯是拿到一个增强需求先不急着动手写代码而是先思考哪部分属于“数据准备”、哪部分属于“校验判断”、哪部分属于“结果输出”。数据准备阶段放心用FOR、FILTER、CORRESPONDING这类内表表达式校验判断阶段多用COND、REDUCE和line_exists结果输出阶段用字符串模板。这个分类思维比某个具体语法更值钱。只要框架清晰哪怕遇到旧版本系统不得不退回老写法你也能知道每个老步骤对应的是新语法的哪个意图改造起来不会抓瞎。
返回列表