尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

008、变量、常量与字面量——一次半夜割接的教训

008、变量、常量与字面量——一次半夜割接的教训 008、变量、常量与字面量——一次半夜割接的教训昨晚割接一个报表在测试环境跑得好好的上了生产就出“ABAP 4.2 溢出”查了一小时最后定位到一句看起来人畜无害的代码DATA(lv_count) 100000 * 100000.测试环境数据量小生产数据量大这行代码在一个循环里被反复执行。生产环境某一天累计的订单数超过了 100000两个整数相乘直接爆掉。同事挠头说“这不就是个大数吗”我说“大数你个鬼你连字面量是整数还是小数都没分清楚”。这事不怪同事ABAP 的变量、常量、字面量表面简单坑全在细节里。今天把这块掰开了讲全是实战里踩过的雷。从一条赋值语句说起ABAP 里写变量最常见的就是DATA关键字。你可以直接声明一个变量然后给它赋值DATA lv_name TYPE string. lv_name 老张.这里lv_name是变量老张是字符字面量。字面量就是写在代码里的一个具体值它不占内存地址编译时就确定了。变量是内存里的一个盒子可以换内容。声明变量的时候我习惯用内联声明DATA(...)省一行是一行。比如DATA(lv_count) 1.这个lv_count的类型编译器会根据右边的字面量自动推断。1是整数所以lv_count就是 I 类型。这种写法很爽但有个坑内联声明只能用于局部变量类属性、全局变量还是得老老实实用DATA加类型。字面量的两种形式一个坑一种死法ABAP 字面量分两种数字字面量和字符串字面量。数字字面量直接写数字比如100、3.14。字符串字面量用单引号括起来比如ABC。坑一数字字面量的类型不一定是 I。100在 ABAP 里是整数类型但100.5就是 P 类型定点数。你如果写DATA(lv_num) 100.5lv_num会被推断为 P 类型长度默认 8 字节。这本身没问题问题是 P 类型参与运算时小数位处理容易出幺蛾子。坑二字符串字面量是固定长度不是可变长度。ABC这个字面量的类型是 C 类型长度 3。如果你把ABC赋给一个STRING类型的变量没问题ABAP 会自动转换。但如果你把ABC赋给一个 C 类型长度 2 的变量对不起会截断而且不报错。这里踩过坑当年写了个接口字段长度配错了数据发出去对方收到的是AB直接对不上账。别这样写定义变量时用字面量直接给长度比如DATA lv_text(3) VALUE ABCD。你猜长度是 3 还是 4ABAP 会按字面量长度 4 来定义然后lv_text实际能存 4 个字符。这种代码留着过年吗迟早坑队友。常量的正确姿势以及为什么别用变量当常量常量用CONSTANTS声明CONSTANTS gc_max_rows TYPE i VALUE 100000.一旦声明不能再赋值。有人喜欢写DATA lv_max_rows TYPE i VALUE 100000然后在程序里只读不写美其名曰“常量”。其实你是在给未来埋雷——某天你或同事会在某个角落里给lv_max_rows重新赋值然后整个系统的逻辑就变了。调试的时候根本想不到是这里被改了。用CONSTANTS就是告诉编译器你敢改就编译报错。这是编译器帮你把关为什么要绕过它常量还有一点要注意CONSTANTS声明的时候必须赋初值不能先声明后赋值也不能只声明不赋初值。这和DATA不一样。另外常量没有内联声明写法你不能写CONSTANTS(xx) VALUE 1语法就不过。类型推断的读心术它猜的未必是你想要的ABAP 的类型推断逻辑有时候挺“笨”的。举个例子DATA(lv_result) 5 / 2.你知道结果是什么吗是 2还是 2.5在 ABAP 里5是 I 类型2是 I 类型/运算符的结果默认也是 I 类型或者精确除法会返回 P 类型这里要分情况。实际测试直接DATA(lv_result) 5 / 2lv_result类型是 P 类型值是 2.5。因为 ABAP 的/永远返回浮点数或定点数不是整数除法。如果你想得到整数 2需要用DIV操作符DATA(lv_result) 5 DIV 2. 结果是 2这个操作符很多从 Java 转过来的同事不习惯容易漏。更坑的是MOD取余数5 MOD 2结果是 1这个还好。关键问题是类型推断会根据整个表达式右侧的最终类型来决定变量类型。比如DATA(lv_price) 10.5. 你以为lv_price是字符串不它会被推断成C类型但实际值是10.5这里没问题。但是DATA(lv_total) 10 * 2.0.右侧一个 I 乘以一个字符串字面量ABAP 会把2.0转换成数字然后结果可能是 P 类型。你猜lv_total是 P 还是 I我告诉你是 P因为字符串转数字会产生 P 类型。这会导致后续和 I 类型的变量比较时系统自动转换有时候效率低了不说还可能因为小数位不同计算出 0.99999 这种结果。所以内联声明固然爽但涉及运算时最好明确指定类型别让编译器瞎猜。比如DATA(lv_count) 100. 明确是I DATA(lv_amount) 100.5. 明确是P但如果从数据库表字段赋值字段类型是 DEC 或 QUAN那就老老实实声明成对应类型别用内联。字符串字面量里的转义和连接一个双引号引发的血案ABAP 字符串字面量是用单引号包的这一点和其他语言不同。很多写惯了 Java 的人会在 ABAP 里写双引号注释但 ABAP 的注释是*开头整行或开头行尾。所以你在表达式里写双引号会被当成注释掉直接语法错或逻辑错。比如DATA(lv_str) 123.这里双引号会让123.变成注释整个语句变成DATA(lv_str) 编译报错。别用双引号当字符串ABAP 不认。字符串连接用运算符DATA(lv_a) hello. DATA(lv_b) world. DATA(lv_c) lv_a lv_b. 结果: hello world早期 ABAP 里可以用CONCATENATE但更简洁。要注意的是如果其中一个变量是字符串字面量另一个是字符变量可以直接连接。如果其中一个是空字符串结果会怎样ABAP 中会把空字符串当作存在不会自动忽略。比如lv_a 结果还是lv_a但如果你用|字符串模板空字符串会被忽略。这里容易搞混建议统一用别混用。字符串字面量里要表示单引号得写两个单引号DATA(lv_quote) its. 结果是 its这里踩过坑写成its语法直接错但报错信息可能指向下一行当时找了半天没找到。文本元素和常量别把注释当常量ABAP 里还有一种“字面量”叫文本元素Text Elements它可以在程序顶部定义然后用TEXT-001这种形式访问。很多人拿它当常量用比如TEXT-001 系统错误.其实文本元素不是变量也不能在代码里赋值。它是在程序属性里维护的主要用来做多语言翻译。你把它当常量用还得去 SE38 里维护不如直接CONSTANTS方便。我在项目里只把文本元素用于界面label逻辑代码里一律用常量。作用域与生命周期静态变量是个隐藏炸弹变量按作用域分全局和局部。全局变量在程序顶层DATA声明所有事件块可见。局部变量在方法、函数、事件块内声明。有个特殊的是STATICS它用于局部变量但生命周期是整个程序运行期不是块结束就释放。比如FORM test. STATICS lv_count TYPE i VALUE 0. lv_count lv_count 1. WRITE: / lv_count. ENDFORM.调用两次testlv_count分别是 1、2。如果你用DATA声明每次调用都归零。这个特性有些场景很实用比如记录循环次数。但这也是个炸弹如果某个函数被多线程或递归调用静态变量会互相干扰导致计数不对。我之前写一个递归解析树的函数用了静态变量累计节点数结果递归回退时计数乱了最后改成传引用参数才解决。所以用STATICS前先问自己这函数有没有递归或重入的可能字面量里的陷阱十六进制和二进制ABAP 字面量还有个特殊形式1234十六进制字面量以X...表示比如XFF。但如果你在代码里写DATA(lv_hex) FF这只是一个长度为 2 的字符串不是十六进制。只有类型为 X 的变量赋值时才需要XFF。DATA lv_byte TYPE x LENGTH 1. lv_byte XFF. 正确不要写lv_byte FF那会类型转换报错或者变成 ASCII 码的 ‘FF’两个字节 ‘F’ 和 ‘F’完全不是你想的 255。实际项目里处理二进制协议时这个很常用。如果你看到X0A别懵就是十进制的 10。类型转换的潜规则防不胜防ABAP 在赋值时会自动类型转换但转换规则很“暴力”。比如字符串赋给数字变量如果字符串不是合法数字运行时直接异常不会像 C 语言那样转成 0。不信你试试DATA(lv_num) abc.编译能过运行时报CX_SY_CONVERSION_NO_NUMBER。这个错误在调试时不常碰到因为大家一般不会把非数字字符串赋给数字变量。但有一种情况从内表或 ALV 网格取单元格值那种值有时是字符你直接赋给整数变量就炸了。所以赋值前先检查IS_NUMBER或TRY。反过来数字赋给字符串变量没问题会是其格式化后的字符串。但要注意数字格式化受系统语言环境的影响小数点可能是点也可能是逗号。在 SAP 里如果你用WRITE TO lv_str或者CONCATENATE格式化数字可能会得到1.000,50德语环境或1,000.50英语环境。做国际化系统时这个坑特别多。建议所有数字转字符串都用|{ lv_num }|或CONDENSE并且明确指定格式别依赖系统默认。从变量到对象引用那是个新维度ABAP 的对象引用变量用DATA LR_OBJ TYPE REF TO CL_SOME_CLASS。引用类型也是变量但它存的是地址。访问属性用-这和访问结构体字段的-不同。一个常见错误把两个引用变量直接比较IF LR_A LR_B比较的是地址是否相同不是内容。你需要比较对象内容时得写-EQ( )方法或者自己比较属性。这里我交过学费两个不同对象内容一样我以为是同一个结果IF LR_A LR_B返回假走了不该走的分支数据重复提交。个人经验能常量就不用变量能显式就不用推断整理三条给还在踩坑的同行第一条所有不会变的数字、字符串一律用CONSTANTS声明名字带GC_前缀一眼看出是全局常量。别在代码里到处写裸的字面量比如IF lv_status X这个X是什么后人还得猜。定义一个CONSTANTS gc_flag_x TYPE c VALUE X然后IF lv_status gc_flag_x代码自解释。第二条内联声明DATA(...)只用于一目了然的简单场景比如从方法返回的值直接赋给同类型变量。但凡涉及运算、函数调用、类型转换都写成显式DATA lv_xxx TYPE xxx。为什么因为类型推断在复杂表达式下经常给出不期望的类型后面还要强制转换不如一开始就写清楚。而且显式声明方便日后调整类型不用改表达式。第三条调试看变量类型时别猜直接在调试器里把鼠标悬停在变量名上或者用DESCRIBE FIELD查看。我见过太多同事对着DATA(lv_x) 123断言lv_x是整数结果它是 C 类型后面拿它做加法时被转来转去出现“1212”这种诡异结果。用DESCRIBE FIELD lv_x TYPE gv_type打出来一看什么都明白了。写代码这行变量名、常量名、类型的习惯比写业务逻辑本身更影响维护成本。今天这篇算是基础中的基础但基础不牢割接的时候流的汗都是当初省的红字。下次写赋值语句前多想一秒右边这个值到底是几进制是字符串还是数字是固定长度还是变长别让 ABAP 帮你猜你猜不到它有多笨。
返回列表