ABAP命名实战:用意图驱动替代匈牙利命名法,提升代码可读性

ABAP命名实战:用意图驱动替代匈牙利命名法,提升代码可读性 上个月帮财务模块做代码走查看一段从老系统迁移过来的计算程序。逻辑本身并不复杂但我花了将近十五分钟才搞清楚它到底在算什么。原因很俗套满屏的lv_、ls_、lt_、lw_每个变量名都在告诉我这是个变量这是个结构这是个内表却没有一个告诉我它存的是客户未清项还是订单行上的净价值。那时候我就想ABAP 开发圈里这套类型前缀的命名习惯是不是该重新聊一聊了。这篇东西我想讲的就是命名这件事具体说是用意图驱动的命名替换或改造传统类型前缀命名。它不是一篇空谈命名的理论文章而是会从 ABAP 语言特性、代码可读性的底层逻辑、真实重构案例、团队落地阻力几个角度展开适合正在写 ABAP、维护老报表的开发者也适合需要在团队里推动代码规范的组长或架构师。看完之后你能直接拿一套可操作的规则回去把你自己的代码、你们模块的公共函数慢慢盘顺。1. 为什么命名会成为可读性瓶颈1.1 类型前缀的来龙去脉ABAP 里的类型前缀圈内通常叫匈牙利命名法。这名字其实是从微软那边传过来的但 ABAP 开发者把它本地化得特别彻底。以至于很长一段时间里lv_开头就是局部变量ls_开头就是结构lt_开头就是内表lo_开头就是对象引用lr_开头就是引用变量。你要是写一个变量不带头老同事一眼就能看出来你是半路转行写 ABAP 的。这套规则的初衷并不蠢。在 ABAP 开发的早期IDE 没有现在这么智能代码区域没有变量类型悬浮提示调试器也没有方便的 Watch 面板。那时候你看到ls_so_item不需要点进任何窗口光靠名字就知道它是一个结构体里面应该装的是 sales order 的某项数据。这种看一眼就知道技术类别的效率在那个年代是真真切切存在的。另外一个原因和 ABAP 语言本身的形态有关。ABAP 的代码很长一个函数可能 500 行起步一个报表从变量声明到WRITE输出隔着几屏代码。变量声明的位置和实际使用的位置距离很远如果你不在名字里带上类型信息翻到第 300 行看到一个customer_name真不确定它到底是字符串还是结构体。所以类型前缀在那个年代不只是习惯它是代码可读性的一部分补偿机制。1.2 前缀为什么开始抢戏问题出在两条线上。第一条线是 IDE 和语言版本升级了但很多团队的命名习惯没跟上。现在的 ABAP Development Tools 里鼠标悬停就能看到字段类型调试器里自动列出当前作用域所有变量DATA(...)这种内联声明也早就普及了。局部变量的类型对读者来说几乎是在使用位置上唾手可得的信息。这时候前缀再占据名字的前几个字符属于重复信息而且还有代价。第二条线是 ABAP 的字长本来就紧张。ABAP 内部对象名最长 30 个字符方法名、变量名、组件名共享这个配额。你给名字前面加lv_三个字符加ls_三个字符加lt_三个字符真正留给业务含义的空间就少了 10%。碰到purchase_requisition_item这种二十多个字符的天然业务词前缀一占后面就只剩下缩写的余地了。但我觉得最核心的问题还不是长度而是解码路径变长了。读代码的人看到lt_open_orders这个名字第一反应是先解码lt这个前缀告诉自己这是个内表然后才去看后面的open_orders去理解业务含义。可是这是个内表这个信息在你读代码的时候本来就不需要专门去记。你关心的是这个内表里装的是哪些数据、循环出来之后要拿它做什么。前缀把注意力引到了技术类别上而不是业务意义上。当这种干扰大量出现时看代码就会觉得累——就像一个人跟你说话每句话前面都要加一句我下面要说的是名词/动词/形容词你很容易就被搞烦。1.3 可读性的真正目标所以我在团队里经常说一句话可读性的目标是让读者在最短时间内恢复你对业务模型的抽象。代码不是拿给机器看的机器只要语法对就能跑代码是拿给人看的人需要在字里行间读出这里在做什么业务判断。把目标定成恢复业务模型命名就有一个特别简单直接的判据一个变量出现在某一行代码里时读者能不能不查声明、不看上一屏就说出它在这个场景里承担什么业务职责。lv_cnt不行因为它没有业务含义invoice_count是可以的overdue_invoice_count更好因为它在具体场景里带了判断条件读者一看就懂这里统计的是什么东西。有人听到这可能觉得我在否定匈牙利命名法。其实不是。我反对的不是在名字里保留必要的信息而是反对把类型信息摆在业务信息前面。真正合理的做法是把两者的优先级换一下业务意图是第一位的类型信息如果需要再根据具体场景补在后面。比如overdue_invoices是个内表这件事读者从后面的语境基本都能猜出来根本不需要刻意把内表塞进名字里。2. 意图驱动命名的核心规则2.1 数据对象业务概念优先先说我建议的具体写法再解释为什么。对于普通局部变量、方法和类的数据声明我推荐下面的风格传统前缀写法意图驱动写法说明lv_kunnrsold_to_party直接写业务角色而不是表的字段名ls_so_itemsales_order_item单数名词表达一个结构体项lt_so_itemsales_order_items复数名词表达一组数据lv_netwrorder_net_value把缩写展开成完整业务词lv_flagis_credit_blocked用谓词性名字表达判断结果lw_log_handleapplication_log_handle去掉无意义技术括号这里面有一个关键想法结构体强调的是一个业务对象内表强调的是一批业务对象。英文里单复数本身就具备这层表达能力完全不需要ls和lt来额外标记。sales_order_item是一个对象sales_order_items是一堆对象读者从脑内英语的语感就能自动建立一个是单条、一个是集合的直觉这套映射比ls 和 lt 分别对应什么自然得多。字段符号也同理。ABAP 里 ASSIGNING 的换符号必须带尖括号这是语法强制不是命名问题。但尖括号内部的名字可以不用FIELD-SYMBOL(fs_xxx)这种前缀式写法直接用sales_order_item就行。语法上尖括号已经提供了这是字段符号的识别线索名字里再重复一遍就只剩浪费。2.2 方法与接口让调用点像一句话方法命名比变量命名更容易被忽略但它的可读性收益其实更大。因为方法是跨越类、函数组的边界在传递读者看调用点的时候往往没有上下文来辅助理解只有方法名这一条通道。我常用的方法是把调用点默认为一句自然语言的祈使句动词加业务对象。比如get_customer_open_invoices、post_goods_receipt、release_purchase_order、calculate_tax_amount。如果要表达条件判断用is_或has_开头is_order_fully_delivered、has_active_block。ABAP 老代码里常见的Z_GET_OPEN_INV这种缩写式命名我建议尽量换掉缩写省不了几个字符却要读者回去猜INV 是 invoice 还是 inventory。方法参数上也一样。我见过很多命名规范要求 IMPORTING 参数带I_前缀导出带E_改换带C_。这在某些大公司 PRG 规范里是硬性要求我不否认它有历史合理性但如果你问我个人建议我会说接口参数名跟着调用语义走不要带头。用IMPORTING iv_customer_id不如直接用IMPORTING customer_id。原因很直接开发工具和方法签名里已经声明了IMPORTING读者本来就知道它是输入参数再在名字里写一遍iv_属于重复信息。2.3 常量、类型与选择屏幕怎么处理常量是命名里比较特殊的一类。传统写法喜欢用c_前缀比如c_active、c_closed。我建议改成业务语义更完整的大写词比如status_active、status_closed、max_retry_count。ABAP 里常量名的字母大小写不影响语法但你可以在命名上约定常量用大写或首字母大写区分于普通变量。更重要的其实是让常量表达出它是什么状态/阈值而不是单纯它是个常量。类型定义TYPES的处境不太一样。一个类型往往要跨多个方法传递有时还要作为方法参数类型引用这时候在类型名里保留一个技术标记确实有意义。我建议保留一个短的_t后缀比如sales_order_item_t用来表达这是一个表类型单个结构类型可以直接用业务词比如sales_order_item或者加_s后缀看你团队习惯。这里我比较务实地保留了一个后缀因为类型的可复用性比局部变量强得多名字里透露一点类型类别对调用方法时理解接口定义的帮助更大。选择屏幕上的变量是另一类很顽固的地方。很多人给选择屏幕变量命名时直接沿用内部表字段名比如SO_BUKRS、SO_KUNNR导致 READ 出来之后还要再找个变量去接。我的习惯是选择屏幕变量也走业务语义比如sold_to_party、company_codes。然后在代码里sold_to_party so_kunnr这种转换就没有了直接拿业务变量用。2.4 ABAP 特殊符号哪些该留哪些该去这里必须承认ABAP 不是一门能让你完全靠语义行走天下的语言。它有一些语法层面的强制符号不属于命名讨论范畴比如字段符号的、引用变量的-、内表的[]。真正值得讨论的只有可选的部分。一是对象引用的命名。类属性的对象引用老规范通常要求mo_前缀比如mo_controller、mo_model。如果你团队的代码里到处都是mo_、ro_这种约定我建议至少把它们限制在真正的对象引用类型上同时把业务部分往后挪。比如mo_application_logger改成logger或者application_logger。理由还是那句类属性的上下文已经说明它是当前类的存储器类型信息在声明处有使用处 hover 也能看到不需要处处标记。二是内联声明。ABAP 740 以后DATA(...)很普及内联变量往往只在一小段代码块里使用生命周期短作用域小。这种变量过去被冠以lv_前缀显得尤其别扭因为读者只在很近的位置看到它业务含义几乎不需要额外解码。内联声明配合意图驱动的名字才会让代码真的向自然语言靠拢。三是从数据字典里带出来的工作区。比如SELECT-OPTIONS s_kunnr FOR kunnr可能你会顺手生成kunnr这个变量。这里我要特别提醒字典字段名是数据模型层面的命名它遵循的是 ERP 数据模型的缩写体系不是说它不好而是当你把它作为业务对象使用时最好给它一个业务角色名。kunnr换成sold_to_party读代码的人会更清楚这个字段在这里指的是售达方而不是收达方。3. 从老代码开始的一轮真实重构3.1 改造前一眼望去全是 lv_、lt_、ls_理论说太多容易飘我拿一段真实场景简化的代码来说。假想一个函数它的任务是找出所有已经逾期未交货的销售订单然后把订单号、合同号、逾期天数整理成一张列表输出。传统写法大致长这样DATA: lt_vbeln TYPE STANDARD TABLE OF vbeln, ls_vbeln TYPE vbeln, lt_vbak TYPE STANDARD TABLE OF vbak, ls_vbak TYPE vbak, lv_delivery_delay_days TYPE i, lv_netwr TYPE netwr, lv_kunnr TYPE kunnr. SELECT vbeln FROM vbak INTO TABLE lt_vbeln WHERE vbeln IN s_vbeln. LOOP AT lt_vbeln INTO ls_vbeln. SELECT SINGLE vbeln, kunnr, netwr FROM vbak INTO (ls_vbak-vbeln, lv_kunnr, lv_netwr) WHERE vbeln ls_vbeln-vbeln. 下面还有 50 多行判断交期的逻辑全部用 lv_ 系列变量 ENDLOOP.这段代码你让我评最大的问题不是语法或性能而是变量名一直在强调我是局部变量、我是内表、我是结构却完全没告诉我这份数据代表什么。lt_vbak和ls_vbak用了 5 个字符来表达内表和结构然后vbak这 4 个字符只是把表名复制了一遍。读者如果不懂vbak在 SD 模块里是什么表这个名字基本等于没有信息量。3.2 改造后语义浮出水面用意图驱动的方式重写一遍核心就变了。首先是变量名全部换成业务词其次是能内联就内联最后是中间状态变量尽量少。改造后大致长这样SELECT vbeln INTO TABLE DATA(open_sales_orders) FROM vbak WHERE vbeln IN s_vbeln. LOOP AT open_sales_orders INTO DATA(open_order). SELECT SINGLE vbeln, kunnr, netwr INTO (DATA(order_number), DATA(sold_to_party), DATA(order_net_value)) FROM vbak WHERE vbeln open_order-vbeln. DATA(delivery_delay_days) calculate_delivery_delay_days( order_number order_number ). IF delivery_delay_days 0. APPEND VALUE #( order_number order_number sold_to_party sold_to_party order_net_value order_net_value delay_days delivery_delay_days ) TO overdue_order_list. ENDIF. ENDLOOP.看到区别没有改造后变量名在使用位置上直接表达业务含义open_sales_orders、sold_to_party、order_net_value这些名字读下来代码本身就像一段带有描述性的文字。读者不再需要先翻译lv_netwr是净价值因为order_net_value已经告诉你了。语法层面还用了转义和内联DATA(...)把变量声明和使用放在同一视野内减少了上翻下翻的次数。有人会担心SELECT SINGLE里直接DATA(order_number)这种写法影响性能我实测过内联声明在运行时没有任何性能差异纯粹是编译期的语法糖。真正要关注性能的是别在一个LOOP里发大量SELECT SINGLE那是另一个维度的问题跟命名无关但很多人容易混在一起讨论。3.3 重构的落地顺序与操作细节这轮重构看起来简单真动起手来需要有顺序不然中途会改乱。我自己操作时一般分四步。第一步先做业务梳理。把你正在处理的这一小段业务逻辑写成一句自然语言。上面那个例子就是找逾期的销售订单记录它逾期多少天。然后从这句话里圈出名词订单、客户、净价值、逾期天数。这些名词就是你变量名的候选。第二步把变量按核心业务对象和中间临时缓冲两个层次分开。核心业务对象指的是要输出到列表或参与最终判断的数据比如overdue_order_list、delivery_delay_days。中间临时缓冲就是那些只用来搬运数据的结构比如传统写法里通篇都是ls_xxx的结构。能消除的中间缓冲用内联和字面量 APPEND 消除不能消除的也不要给它瞎起名直接叫它业务对象本身。第三步做方法提取。如果一段逻辑超过 15 行并且职责相对独立就把它抽成一个私有方法方法名用动词业务对象的结构。我这里就抽了calculate_delivery_delay_days把计算逻辑和主流程分开了。这样主流程变成一组清晰的调用序列读者跟着方法名走就行不需要看每一行细节。第四步把自己代入成新读者把重构后的代码从头到尾读一遍遇到任何一个名字要看第二眼才能反应过来的立刻回头改。这一步是最费时间的但也是效果最明显的。改的时候要注意变量改名不要顺手改业务逻辑两者要分开提交否则出了 bug 说不清楚是命名问题还是行为变化。4. 常见问题与排查技巧实录4.1 公司规范不让改怎么办这是我在交流时被问最多的问题。很多 ABAP 开发并不是想怎么命名就怎么命名企业里往往有一份格式规范规定变量必须带lv_前缀或者方法参数必须带i_、e_前缀。这种规范年限越长积重越深想推动改动很难。我的建议不是正面硬刚而是从增量入手。新开发的函数、新增的私有方法、新创建的处理类在这些全新的代码里率先采用意图驱动命名。你不需要把存量 10 万行的代码一下子都改掉只需要做到新代码不再制造新的读代码负担。等团队里积累了一定量读起来很流畅的新代码后在代码走查会上借这些例子谈收益比拿一份 PPT 讲理论有效得多。如果规范确实以文档形式锁死了lv_、lt_前缀还有一些折中办法。比如把前缀保留但把后面的名词换成完整业务词lv_kunnr改成lv_sold_to_partylt_vbak改成lt_open_orders。这样虽然前缀还在但业务语义已经比原来好了太多。我再补一句规范不是刻在石头上的它服务于代码维护。拿一次真实走过的 bug——因为lv_a和lv_a1看混导致数据串号——去跟规范制定者沟通通常比抽象讲可读性更有说服力。4.2 搜索与 Where-Used 的常见误区有人担心去掉前缀后变量名变成customer、orders这种常见英文词在 ABAP 开发环境里做全文搜索会命中一大堆无关结果反而不方便定位。这里我要泼一点冷水这种担心多数是把搜索引擎习惯套在 ABAP 工具上。ABAP 的代码搜索和 Where-Used 列表是按对象、按名称精确匹配的。你要找一个变量在哪些地方被使用直接右键跳转到使用列表配合名称过滤就算它叫customer结果里也不会有多少干扰项。反而是lv_customer这种带前缀的名字你去搜业务含义时还得先在脑子里去掉lv_再拆出customer多一步转换不说搜出来的还全是局部变量级的 customer跟类属性、方法参数里的同一概念完全对不上。真正影响搜索体验的是缩写不一致。同样是客户有人写kunnr、有人写customer、有人写cust。从信息检索的角度讲统一的业务术语表比统一前缀重要得多。如果团队有能力建议维护一份 ABAP 命名术语表规定哪些业务概念用哪些标准词。这比纠结一个lv_前缀值得多。4.3 30 字符长度限制下如何缩写ABAP 对象名最长 30 个字符这个限制在意图驱动命名下反而容易被触到。像purchase_requisition_approval_documents已经 36 个字符了超了。那我的建议是先保主干语义再考虑可识别性最后才是词典完整拼写。缩写也不是随意乱缩要从业务领域内已有的习惯里去取。比如采购领域purchase requisition大家默认写pr或purch_req销售订单sales order默认写so。用这些已经被业务认同的缩写不会给读者增加解码负担。反过来你自己发明pch_req_apprv_doc这种缩写就是给读者添堵。再看一个例子purchase_order_confirmation_number是 33 个字符超了。根据领域习惯缩写成po_confirmation_no依然保留完整语义。如果你连confirmation都嫌长po_conf_no在业务语言里也基本没歧义。关键在于缩写后的词在领域内部有公共理解而不是能省则省。4.4 命名意识比命名规范更值钱最后想提醒大家一个容易掉进去的坑把意图驱动命名当成一套静态检查规则去做结果会流于形式。静态检查器可以检查变量名是不是以lv_开头但它检查不出lv_a这个变量有没有业务含义。可读性本质上是关于语义的不是关于字符模式的。我见过有的项目把lv_前缀统一改掉之后新名字变成var1、var2、var3这比原来还难读。意图驱动命名的核心是每次起名时都回到那个问题这个名字在告诉读者业务上的什么事实。所以落地时比起在编码规范文档里加一条禁止使用类型前缀更有效的是在代码走查时对具体名字提问这个变量是干嘛的它为什么叫这个名字它在这个方法里的职责边界是什么这些问题会让写代码的人建立起命名意识而有命名意识的人无论拿到一套什么样的规范都会倾向于把业务语义放到名字的首要位置。规范是下限意识才是上限。我自己改完几个程序之后有个很直接的感受当变量名开始表达业务代码走查的讨论重心会从这个变量类型对不对自动转移到这个业务逻辑是否合理上。这个转变可能比命名本身带来的收益还要大。