
干了十多年ABAP代码评审也做了几百轮如果问我哪种代码问题最能消耗团队时间我大概率会投“常量命名”一票。不是因为它会带来明显的性能问题而是它每天都在悄悄地增加大家的阅读成本、搜索成本和改错风险。这个主题看着小实则直接影响ABAP项目的交付效率和质量。今天这篇我就围绕常量命名这个话题把隐藏成本、踩坑案例、命名规范和实操重构一次讲清楚希望能给正在写ABAP的你一些可落地的参考。1. 先别急着写代码常量命名里的成本账1.1 你写的不是常量名是留给三个月后自己的暗号我印象很深的一次生产问题排查凌晨两点客户报了个订单状态错乱的问题。我拉出代码一看一段判断里写着IF status c_01.。这个c_01是什么打开状态还是关闭状态还是某种异常标记我只能顺着代码往上翻找它的定义最后在程序头部看到一行注释“c_01 打开”问题是这个注释还是一个多月前写的中间还有没有人改过完全不知道。那一刻你就能感觉到常量命名省下来的那几秒钟最终都要靠加班时间和精神损耗加倍还回去。写常量名时确实只花了几秒但一个常量在项目里可能被几十个地方引用每次阅读、每次维护、每次排查问题都要重新“破译”一遍它的含义。尤其ABAP系统大量承载着订单、物料、财务这类核心业务数据常量的语义一旦模糊改错一个值就可能导致生产数据异常这种兜底成本才是真正的“隐藏成本”。1.2 成本到底藏在哪几个环节我不是想制造焦虑而是希望把“命名不好”这件事具体化。经过这些年观察我认为常量命名带来的成本主要体现在四个环节成本环节具体表现最终影响阅读成本看到常量名要想半天它代表什么写代码和Review的速度变慢搜索成本想找一个业务含义对应的常量却搜不到关键词重复定义系统里出现多个“同义词”变更成本名字无法反映用途导致不敢动、不敢删逻辑越堆越乱重构阻力大协作成本新同事问东问西老同事反复解释团队知识传递效率低甚至出现理解偏差举个搜索成本的例子订单状态字段A程序里叫c_openB程序里叫gc_status_1C程序里直接写O。当你想统一处理订单状态时搜OPEN只能搜到A程序搜STATUS才能搜到B程序而C程序压根搜不到只能靠人肉翻代码。这种情况下常量命名不过关团队就永远在做“考古”工作。2. 盘点我踩过的常量命名坑每一条都是教训2.1 反模式一“前缀加数字”的全能命名法很多老项目里都能看到这种写法c_01、c_02、c_max、c_tmp。这种命名的好处只有一个写起来快。除此之外几乎全是问题。你无法从名字里判断这个常量属于哪个业务对象也无法判断它是什么类型、什么用途。最要命的是当常量的值需要调整时你根本不知道它还被多少地方引用着因为搜索c_01会出来一大片结果里面鱼龙混杂。我见过一个程序十几个c_01到c_20的常量分别代表不同的单据类型和状态。代码里到处是IF type c_05 OR type c_09.。到了后来原作者离职没人敢动这段逻辑只能在上层打补丁。这就是典型的“写时一时爽维护火葬场”。2.2 反模式二名字太短跟没写一样如果说c_01是“完全不负责”那MAX、MIN、TMP、FLAG这种就属于“负一半责任”。FLAG可能是布尔标志也可能是字符串标记甚至可能是错误代码。我记得有次看代码一个变量叫flag我往上找了三层才知道它标记的是“是否允许负库存”而它的值还是X和。这哪里是FLAG这分明是谜语。我后来带团队时定过一条规矩常量名禁止单独使用MAX、MIN、TMP、FLAG、STATUS这类词至少得带上业务对象比如gc_stock_neg_allowed、gc_max_line_items。没有业务含义的短词能做局部变量但绝对不能做全局常量因为全局常量的生命周期太长阅读频率太高。2.3 反模式三把类型塞进名字把重构路堵死ABAP传统命名习惯里有人喜欢在常量名里加类型标记比如c_str_name、c_int_count、c_bool_flag。这种把内部类型写进名字的做法短期看似乎有助理解长期看却是重构的绊脚石。你定义一个c_str_status刚开始是字符串型后来需求变了状态改成数字型按理说只需要改定义可因为名字里带着str你改类型的同时还得改名字不然看着别扭。类型是会变的语义不会变。“订单状态”永远是“订单状态”不管它是CHAR还是NUMC。所以我的建议是名字里表达业务语义就够了不用把技术类型的标签贴上去。除了类型作用域前缀比如g_、l_、s_可以保留但也要控制粒度否则同样会造成噪音。2.4 反模式四常量散落各处同一个含义系统里七八个还有一种很头疼的情况同一个业务含义的常量在不同程序、不同接口里各定义一份。比如订单状态“已关闭”在A程序里是gc_close在B接口里是c_status_close在C类里又变成co_closed。表面上看每个程序局部运行都没有问题但一旦要做跨程序的数据交换或者统一逻辑判断这些常量值是否一致就成了大隐患。我处理过一个物料凭证接口问题A系统的“过账”值是PB系统却用的是1两边程序各自用自己的常量名硬是没发现值不一致直到对账时产生了大量差异。如果这两个常量定义在一个公共接口里统一引用这种低级错误根就不会发生。常量不是私有财产它是业务语义的载体理应有一个明确的归属地。3. 让常量开口说人话的命名规范与设计模式3.1 一套能直接抄作业的ABAP常量命名公式我总结了一个适合ABAP项目的常量命名公式分享给团队用了效果不错前缀 业务对象 业务语义前缀方面全局常量建议用gc_Global Constant类里的静态常量可以用co_接口常量直接用接口名体现归属。业务对象是这条常量所属的领域比如订单order、物料material、库存stock、凭证document。业务语义是这个常量具体代表的含义要尽量具体比如open、closed、cancelled、negative。举个例子CONSTANTS: gc_order_status_open TYPE char1 VALUE O, gc_order_status_closed TYPE char1 VALUE C, gc_order_status_cancelled TYPE char1 VALUE X.你看这个命名哪怕没有注释也能猜到是订单状态相关的三个常量。写代码的时候IF order_status gc_order_status_open.读起来就像一句大白话“如果订单状态等于打开”。这就是“让常量开口说人话”的意义——代码自己会解释自己注释反而成了辅助。3.2 用接口和类来收拢常量别让它们四处流浪常量有了好名字还不够还得有好的“家”。我的习惯是跨程序、跨模块共享的业务常量统一放在专门定义的接口或者常量类里。比如INTERFACE zif_order_status. CONSTANTS: open TYPE char1 VALUE O, closed TYPE char1 VALUE C, cancelled TYPE char1 VALUE X. ENDINTERFACE.这样任何程序需要判断订单状态都引用zif_order_statusopen整个系统只有一个定义来源。改值的时候用Where-Used List一搜就知道影响范围再也不用担心“这里改了那里没改”。如果你用的是ABAP面向对象开发我更推荐用常量类。类的好处是可以加上方法做校验还可以写文档注释引用的时候也更清晰CLASS zcl_order_status DEFINITION PUBLIC FINAL. PUBLIC SECTION. CONSTANTS: open TYPE char1 VALUE O, closed TYPE char1 VALUE C, cancelled TYPE char1 VALUE X. ENDCLASS.引用方式变成zcl_order_statusopen语义同样一目了然。对于SAP新语法里比较火的枚举类ENUM我也试过用于固定值域的字段效果很好可以减少很多无效输入但要注意老版本兼容性问题不是所有系统都支持需要结合项目实际来选。3.3 下拉框、校验逻辑和数据库条件里的常量使用策略常量最该被用起来的地方其实是那些不起眼的“魔法值”。比如下拉框的显示列表有人直接写APPEND VALUE #( option O text 打开 ) TO lt_options. APPEND VALUE #( option C text 关闭 ) TO lt_options.这种写法能跑但就是把业务语义散落在界面逻辑里。更好的做法是这样APPEND VALUE #( option zif_order_statusopen text 打开 ) TO lt_options. APPEND VALUE #( option zif_order_statusclosed text 关闭 ) TO lt_options.旁边的人一看就知道这些选项来自订单状态的固定值域。数据库查询条件同理别在WHERE子句里写死O或者X全部替换成常量引用。这样做的额外好处是当业务方提出“打开状态多了一个中间态”的时候你只需要在接口里增加一个常量然后把相关逻辑梳理一遍而不是全局搜索字符串。3.4 老系统怎么“抢救”渐进式重构的折中方案如果你手里维护的是一个十几年历史的老系统成千上万个c_01、c_02已经遍布各个程序说实话想一夜之间全部改完不现实。我的建议是“新人新办法老人老办法”给系统做一个渐进式迁移第一步新代码完全禁止出现无业务含义的常量名代码评审一票否决。第二步针对高频核心模块比如订单、物料、财务先把公共常量抽取到统一接口里然后逐个程序替换引用。第三步替换完一个程序就跑一次完整的功能回归测试确认常量的值没有变化。这里必须特别提醒常量名可以变常量值不能变。因为数据库里存的历史数据都是按旧的值存进去的。你把C_01 O改成gc_order_status_open O值还是O数据才能对得上。如果顺手把O改成了1那就是事故。重构收益大风险也不小每一步都要稳。4. 实操实录如何把一段“天书”常量改造成“说明书”级代码4.1 原始代码长什么样为了更直观地说明问题我模拟一个典型的“魔法数字满天飞”的ABAP程序片段。假设这是一个简单的销售订单状态处理逻辑DATA: lv_status TYPE char1. lv_status vbak-status. IF lv_status c_01. 执行发送邮件操作 PERFORM send_mail. ELSEIF lv_status c_02. 执行发货操作 PERFORM delivery. ELSEIF lv_status c_99. 取消订单 PERFORM cancel_order. ENDIF.这段代码里c_01、c_02、c_99分别代表什么只有写这段代码的人当时知道。你现在要我加一个逻辑“订单打开的时候要同时更新日志”我根本不敢动因为我不知道c_01到底是打开还是关闭。万一弄反了所有订单状态都会受影响。4.2 逐行拆解问题点我把这段代码的问题逐个列出来c_01、c_02、c_99没有任何业务语义无法从名字判断含义。三个常量之间是什么关系是互斥的状态还是一个组合标记看不出来。常量定义在哪里如果散落在程序的不同位置阅读者要来回查找。c_99代表取消但从名字上看不出它和c_01、c_02属于同一个枚举域。这些问题单独看都不致命但叠加在一起就形成了巨大的认知负担。尤其是到了交接的时候新接手的人只能靠猜而这种“猜”恰恰是生产事故最常见的源头之一。4.3 重构后的代码现在我用命名公式和公共接口来重构这段代码。首先定义一个公共接口把订单状态固定值收拢起来INTERFACE zif_order_status. CONSTANTS: open TYPE char1 VALUE O, closed TYPE char1 VALUE C, cancelled TYPE char1 VALUE X. ENDINTERFACE.然后主程序里的逻辑就变成DATA: lv_order_status TYPE char1. lv_order_status vbak-status. IF lv_order_status zif_order_statusopen. 执行发送邮件操作 PERFORM send_mail. ELSEIF lv_order_status zif_order_statusclosed. 执行发货操作 PERFORM delivery. ELSEIF lv_order_status zif_order_statuscancelled. 取消订单 PERFORM cancel_order. ENDIF.对比一下逻辑完全没变但读代码的人不再需要来回找定义。zif_order_statusopen本身就是注释而且注释错了代码也过不了评审。这才是“代码即文档”的落地方式。4.4 重构前后的收益对比这个案例改造很小但收益是立竿见影的。我列个对比表维度改造前改造后可读性需要翻定义才能懂一眼看懂业务含义可搜索性搜索C_01噪音极大搜索zif_order_statusopen精准定位可维护性改代码靠猜风险高语义明确修改有据可依可测试性测试用例写不清楚状态条件清晰覆盖容易新人上手成本需要“前辈传帮带”看代码就能理解大部分逻辑这种重构不需要高深的技术纯粹是命名习惯的升级但对长期维护的收益却是巨大的。我甚至建议不需要等到系统发版你可以在日常修复BUG的时候顺手把遇到的这种“天书常量”改掉每次改一点积少成多。5. 排查与落地如何让团队不再写出天书常量5.1 快速揪出代码里的“魔法数字”和神秘常量就算你把自己的代码写干净了团队里总会有历史遗留问题。怎么快速找到那些藏在代码角落里的“魔法数字”我的做法是用ABAP Test CockpitATC配合Code Inspector的检查变体启用“Magic Number”检查项。这个检查会找出代码里直接使用的数字字面量比如IF status O里的O然后逐条评估该替换成常量的就替换。如果你不想配置ATC也可以在SE38的编辑器里用正则搜索高级数比较或者字符串赋值语句。比如搜索IF .* 然后逐条看是不是魔法字符串。这个方法虽然朴素但效果很好。我自己就靠这个方法在某个模块里一次扫出了四十多个需要替换的魔法值优先处理了其中涉及财务过账标志的几个后续对账问题的工单量直接降了一截。5.2 团队规范落地靠的是三件事光有方法不够还得让规范真正落到日常开发里。我总结了三件事亲测有效第一代码评审清单里明确加一条“常量命名检查”。Review的时候看到无业务含义的常量直接标注“不符合规范请修改”不用争论。第二团队wiki里放一份“常量命名示例表”把订单、物料、凭证、用户等高频业务对象的命名实例写得清清楚楚新人来了照抄就行。第三如果能接CI流程把ATC的Magic Number检查作为门禁不合格的代码不允许传输到测试系统。没有强制手段规范就容易沦为口号。5.3 一个保命技巧改常量前先查Where-Used List最后分享一个我自己踩过坑之后养成的习惯。不管是改常量的值还是把散落的常量统一到公共接口里动手之前一定要用Where-Used List查一遍引用范围。ABAP里右键点击常量名选择“Where-Used List”就能看到这个常量在哪些程序里被用到。如果是接口里的常量影响范围可能跨整个系统这时候不能只看当前程序要做全量评估。我几年前吃过一次亏把某个程序内部的c_01从O改成了1结果这个常量在另一个报表里也被引用那个报表的查询条件直接失效业务人员第二天早上才发现数据不对。从那以后我给自己定了个铁律常量改名、改值之前先跑一遍Where-Used List把影响范围写清楚再动手。谨慎一点永远不过分。我自己现在写代码有个习惯每写一个常量心里都会默念一遍如果三个月后我完全失忆了看到这个名字能不能立刻知道它是什么。如果不能我就换个名字直到它“开口说人话”为止。这个习惯不花多少时间但省下来的排查成本真的难以估量。希望这篇关于ABAP常量命名隐藏成本与实践经验的分享能让你在下次写常量的时候多留意一下那个即将留在代码里几十年的名字。